Commit f18c7733 by Hao Chen

mCotton version 1.0, last version

parents

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

mqttDbUrl=mongodb://mongodb:3001/mqtt
mqttHost=localhost
\ No newline at end of file
mcotton-v1
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding">
<file url="PROJECT" charset="UTF-8" />
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="JavaScriptLibraryMappings">
<file url="file://$PROJECT_DIR$" libraries="{mcotton-v1 node_modules}" />
<file url="PROJECT" libraries="{meteor-packages-auto-import-browser}" />
<includedPredefinedLibrary name="Meteor project library" />
</component>
</project>
\ No newline at end of file
<component name="libraryTable">
<library name="mcotton-v1 node_modules" type="javaScript">
<properties>
<option name="frameworkName" value="node_modules" />
<sourceFilesUrls>
<item url="file://$PROJECT_DIR$/node_modules" />
</sourceFilesUrls>
</properties>
<CLASSES>
<root url="file://$PROJECT_DIR$/node_modules" />
</CLASSES>
<SOURCES />
</library>
</component>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.meteor/local" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="mcotton-v1 node_modules" level="project" />
<orderEntry type="library" name="meteor-packages-auto-import-browser" level="project" />
</component>
</module>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectInspectionProfilesVisibleTreeState">
<entry key="Project Default">
<profile-state>
<expanded-state>
<State>
<id />
</State>
<State>
<id>Data flow issuesJavaScript</id>
</State>
<State>
<id>JavaScript</id>
</State>
</expanded-state>
</profile-state>
</entry>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="SvnConfiguration" maxAnnotateRevisions="500" myUseAcceleration="nothing" myAutoUpdateAfterCommit="false" cleanupOnStartRun="false" SSL_PROTOCOLS="sslv3">
<option name="USER" value="" />
<option name="PASSWORD" value="" />
<option name="mySSHConnectionTimeout" value="30000" />
<option name="mySSHReadTimeout" value="30000" />
<option name="LAST_MERGED_REVISION" />
<option name="MERGE_DRY_RUN" value="false" />
<option name="MERGE_DIFF_USE_ANCESTRY" value="true" />
<option name="UPDATE_LOCK_ON_DEMAND" value="false" />
<option name="IGNORE_SPACES_IN_MERGE" value="false" />
<option name="CHECK_NESTED_FOR_QUICK_MERGE" value="false" />
<option name="IGNORE_SPACES_IN_ANNOTATE" value="true" />
<option name="SHOW_MERGE_SOURCES_IN_ANNOTATE" value="true" />
<option name="FORCE_UPDATE" value="false" />
<option name="IGNORE_EXTERNALS" value="false" />
<configuration useDefault="false">$USER_HOME$/.subversion</configuration>
<myIsUseDefaultProxy>false</myIsUseDefaultProxy>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/mcotton-v1.iml" filepath="$PROJECT_DIR$/.idea/mcotton-v1.iml" />
</modules>
</component>
</project>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
# This file contains information which helps Meteor properly upgrade your
# app when you run 'meteor update'. You should check it into version control
# with your project.
notices-for-0.9.0
notices-for-0.9.1
0.9.4-platform-file
notices-for-facebook-graph-api-2
1.2.0-standard-minifiers-package
1.2.0-meteor-platform-split
1.2.0-cordova-changes
1.2.0-breaking-changes
1.3.0-split-minifiers-package
1.4.0-remove-old-dev-bundle-link
# This file contains a token that is unique to your project.
# Check it into your repository along with the rest of this directory.
# It can be used for purposes such as:
# - ensuring you don't accidentally deploy one app on top of another
# - providing package authors with aggregated statistics
1i2mpst15k82uo1l9t7ne
# Meteor packages used by this project, one per line.
# Check this file (and the other files in this directory) into your repository.
#
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
##############################
# Base
##############################
meteor-base@1.0.4
session@1.1.6
blaze-html-templates@1.0.4
mongo@1.1.10
random@1.0.10
##############################
# Basic
##############################
reactive-var@1.0.10
underscore@1.0.9
pauldowman:dotenv
iron:router
jquery@1.11.9
check@1.2.3
# tracker
# logging
#reload
#ejson
#spacebars
##############################
# Performance
##############################
meteorhacks:aggregate
meteorhacks:fast-render
##############################
# Account
##############################
accounts-password@1.2.12
# accounts-facebook
# accounts-twitter
# accounts-weibo
# accounts-github
# accounts-google
accounts-base@1.2.9
alanning:roles
twbs:bootstrap
accounts-ui@1.1.9
# ian:accounts-ui-bootstrap-3
# mrt:accounts-admin-ui-bootstrap-3
jparker:crypto-hmac
jparker:crypto-sha256
##############################
# RESTful API
##############################
nimble:restivus
##############################
# MQTT
##############################
mkarliner:mqtt
# mkarliner:mosca
##############################
# CoAP
##############################
# enteye:coap
##############################
# schemas
##############################
aldeed:collection2
dburles:collection-helpers
##############################
# File & images uplaod
##############################
cfs:standard-packages
cfs:filesystem
# cfs:gridfs
# cfs:s3
cfs:ui
# cfs:autoform
raix:ui-dropped-event
cfs:graphicsmagick
mrt:numeral
##############################
# UI
##############################
aldeed:autoform
pcuci:bootcards
rcy:nouislider
sacha:spin
less@2.7.4
# yogiben:autoform-modals
# ajduke:bootstrap-tagsinput
# materialize:materialize
momentjs:moment
meteorhacks:async
##############################
# Visualization
##############################
d3
mrt:nvd3js
limemakers:three
pauloborges:mapbox
browser-policy@1.0.9
##############################
# Data Dump
##############################
udondan:jszip
##############################
# Production
##############################
# meteorhacks:kadira
standard-minifier-css
standard-minifier-js
browser
server
METEOR@1.4.0.1
accounts-base@1.2.10
accounts-password@1.2.14
accounts-ui@1.1.9
accounts-ui-unstyled@1.1.12
alanning:roles@1.2.15
aldeed:autoform@5.8.1
aldeed:collection2@2.9.1
aldeed:collection2-core@1.1.1
aldeed:schema-deny@1.0.1
aldeed:schema-index@1.0.1
aldeed:simple-schema@1.5.3
allow-deny@1.0.5
autoupdate@1.2.11
babel-compiler@6.9.0
babel-runtime@0.1.10
base64@1.0.9
binary-heap@1.0.9
blaze@2.1.8
blaze-html-templates@1.0.4
blaze-tools@1.0.9
boilerplate-generator@1.0.9
browser-policy@1.0.9
browser-policy-common@1.0.10
browser-policy-content@1.0.11
browser-policy-framing@1.0.11
caching-compiler@1.1.6
caching-html-compiler@1.0.6
callback-hook@1.0.9
cfs:access-point@0.1.49
cfs:base-package@0.0.30
cfs:collection@0.5.5
cfs:collection-filters@0.2.4
cfs:data-man@0.0.6
cfs:file@0.1.17
cfs:filesystem@0.1.2
cfs:graphicsmagick@0.0.18
cfs:http-methods@0.0.32
cfs:http-publish@0.0.13
cfs:power-queue@0.9.11
cfs:reactive-list@0.0.9
cfs:reactive-property@0.0.4
cfs:standard-packages@0.5.9
cfs:storage-adapter@0.2.3
cfs:tempstore@0.1.5
cfs:ui@0.1.3
cfs:upload-http@0.0.20
cfs:worker@0.1.4
check@1.2.3
chuangbo:cookie@1.1.0
coffeescript@1.0.17
d3@1.0.0
dburles:collection-helpers@1.0.4
ddp@1.2.5
ddp-client@1.2.9
ddp-common@1.2.6
ddp-rate-limiter@1.0.5
ddp-server@1.2.10
deps@1.0.12
diff-sequence@1.0.6
ecmascript@0.5.7
ecmascript-runtime@0.3.13
ejson@1.0.12
email@1.1.16
geojson-utils@1.0.9
hot-code-push@1.0.4
html-tools@1.0.10
htmljs@1.0.10
http@1.1.8
id-map@1.0.8
iron:controller@1.0.12
iron:core@1.0.11
iron:dynamic-template@1.0.12
iron:layout@1.0.12
iron:location@1.0.11
iron:middleware-stack@1.1.0
iron:router@1.0.13
iron:url@1.0.11
jparker:crypto-core@0.1.0
jparker:crypto-hmac@0.1.0
jparker:crypto-sha256@0.1.1
jquery@1.11.9
less@2.7.4
limemakers:three@0.75.0
livedata@1.0.18
localstorage@1.0.11
logging@1.1.14
mdg:validation-error@0.2.0
meteor@1.2.16
meteor-base@1.0.4
meteorhacks:aggregate@1.3.0
meteorhacks:async@1.0.0
meteorhacks:collection-utils@1.2.0
meteorhacks:fast-render@2.14.0
meteorhacks:inject-data@2.0.0
meteorhacks:meteorx@1.4.1
meteorhacks:picker@1.0.3
minifier-css@1.2.13
minifier-js@1.2.13
minimongo@1.0.17
mkarliner:mqtt@0.3.0
modules@0.7.5
modules-runtime@0.7.5
momentjs:moment@2.14.4
mongo@1.1.10
mongo-id@1.0.5
mongo-livedata@1.0.12
mrt:numeral@1.5.3
mrt:nvd3js@0.1.0
nemo64:bootstrap@3.3.5_3
nemo64:bootstrap-data@3.3.5
nimble:restivus@0.8.11
npm-bcrypt@0.8.7_1
npm-mongo@1.5.45
observe-sequence@1.0.12
ordered-dict@1.0.8
pauldowman:dotenv@1.0.1
pauloborges:mapbox@2.2.3_2
pcuci:bootcards@1.0.0_6
promise@0.8.3
raix:eventemitter@0.1.3
raix:ui-dropped-event@0.0.7
random@1.0.10
rate-limit@1.0.5
rcy:nouislider@7.0.7_2
reactive-dict@1.1.8
reactive-var@1.0.10
reload@1.1.10
retry@1.0.8
routepolicy@1.0.11
sacha:spin@2.3.1
service-configuration@1.0.10
session@1.1.6
sha@1.0.8
simple:json-routes@2.1.0
spacebars@1.0.12
spacebars-compiler@1.0.12
srp@1.0.9
standard-minifier-css@1.1.8
standard-minifier-js@1.1.8
templating@1.1.14
templating-tools@1.0.4
tracker@1.1.0
twbs:bootstrap@3.3.6
udondan:jszip@2.4.0_1
ui@1.0.11
underscore@1.0.9
url@1.0.10
webapp@1.3.10
webapp-hashing@1.0.9
0
\ No newline at end of file
FROM cuckoohello/meteord:onbuild
This diff is collapsed. Click to expand it.
# README (V0.3.3)
# This project is deprecated.
According Meteor is hard to support the pressure from
IoT devices, I will rewrite it with Express and React, and add more awesome features.
The new project will be stored on mCottonExp.
因为 meteor 在大数据压力下表现不太好,所以计划重写 mCotton , 新的服务器会放在 mCottonExp 中.
# What's New
v 0.3.3, 20160728
This project is deprecated.
Tested on meteor 1.4.
Account admin has bug. If you want to set your admin, you can change the file : /app/server/fixtures.js
v 0.3.2, 20160415
1. Better UI
2. Update to meteor 1.3.1
3. Update Vulture Egg to latest Webgl
4. Better Performance
5. Device data dump.
v 0.3.1, 20151117
1. Share device to public
2. Integration Map for all public device, and for project
3. WebGL smart Egg
4. Update UX of devices and projects
v 0.3
1. Support MQTT
2. Admin can edit Modules, Projects, and Devices.
3. Add mCotton introduce in Home page.
# To DO
[todo.md](todo.md)
# How to Start
## mCotton 安装使用方法
mCotton 是 server , httpclient*.js 是访问他的Node.js客户端
### 快速安装 mCotton Server(Develop Mode)
1. 安装 meteor, 命令为:
curl https://install.meteor.com | sh
2. 展开 mCotton.zip ,并 进入 mCotton 目录
3. 更新meteor, 执行:
meteor update
4. 安装所需的 NPM 包在安装过程中, mosca 会出现安装 zmq 相关的问题, 这个不必理会.
meteor npm install
5. 修改 /app/lib/collections/0_fsstore.js, 修改 SYS_BASE 为您本地可以访问的一个目录,用于存储上传的图片文件.
// SYS_BASE = "/data"; // all upload images and files will be stored in this folder, please make sure you have access rights.
SYS_BASE = "/Users/yourhome/folder";
6. 启动Server, 命令为 :
meteor
7. Reset MongoDB
meteor reset
### 创建 mCotton 的账号
1. 访问 http://localhost:3000 , 用你的邮箱注册一个账号。此账号后面还需要使用。
2. 使用此账号登陆
例如,此账号可以为:
var useremail = "iasc@163.com", pwd = "123456";
![docs/mcotton_01.png](docs/mcotton_01.png)
** On meteor 1.4, Account admin has bug and can't used.
If you want to set your admin, you can change the file : /app/server/fixtures.js
# Projects
## Wifi气象站
详情参见 [weather_station.md](docs/weather_station.md)
## 我的城市
详情参见 [my_city.md](docs/my_city.md)
## Smart Vulture Egg
详情参见 [egg.md](docs/egg.md)
\ No newline at end of file
/**
* Created by chenhao on 15/5/16.
*/
Accounts.ui.config({
// passwordSignupFields: 'USERNAME_ONLY'
passwordSignupFields: 'USERNAME_AND_OPTIONAL_EMAIL'
});
\ No newline at end of file
/**
* Created by chenhao on 15/7/20.
*/
// Session helpers
//
// {{#if sessionEquals 'foo' 'bar'}} //where foo is session key containing a value and bar is test value
// {{getSession 'foo'}} //returns session keys value
//
(function () {
if (typeof Handlebars !== 'undefined') {
Handlebars.registerHelper('safe', function (field) {
if (! (_.isString(this[field]))) {
return '';
}
return new Handlebars.SafeString(this[field]);
});
//Handlebars.registerHelper('user', function () {
// return Meteor.user();
//});
//
//Handlebars.registerHelper('getSession', function (key) {
// return Session.get(key);
//});
//
//Handlebars.registerHelper('sessionEquals', function (key, value) {
// var myValue = Session.get(key); //Workaround Issue #617
// if (typeof(myValue) === 'boolean') {
// //Workaround Issue #617
// return Session.equals(key, (value == 'true'));
// }
// return Session.equals(key, (myValue === +myValue)?+value:value); //Workaround Issue #617
// //return Session.equals(key, value); //When Issue #617 is resolved
//});
//Handlebars.registerHelper('findOne', function (collection, query, options) {
// //console.log('findOne: '+collection + ' '+query);
// var myCollection = eval(collection);
// if (myCollection instanceof Meteor.Collection) {
// var myQuery = JSON.parse(query);
// var myOptions = (options instanceof Object)?undefined: JSON.parse(options);
// //console.log(myCollection.findOne(myQuery));
// if (myQuery instanceof Object) {
// return myCollection.findOne(myQuery, myOptions);
// }
// console.log('{{findOne}} query error: '+query);
// throw new Error('Handlebar helper findOne: "'+collection+'" error in query:'+query+' (remember {"_id":1})');
// } else {
// throw new Error('Handlebar helper findOne: "'+collection+'" not found');
// }
// return [];
//});
//
//Handlebars.registerHelper('find', function (collection, query, options) {
// //console.log('find: '+collection + ' '+query+' '+(options instanceof Object));
// var myCollection = eval(collection);
// if (myCollection instanceof Meteor.Collection) {
// var myQuery = JSON.parse(query);
// var myOptions = (options instanceof Object)?undefined: JSON.parse(options);
// //console.log(myCollection.find(myQuery));
// if (myQuery instanceof Object) {
// return myCollection.find(myQuery, myOptions);
// }
// console.log('{{find}} query error: '+query);
// throw new Error('Handlebar helper find: "'+collection+'" error in query:'+query+' (remember {"_id":1})');
// } else {
// throw new Error('Handlebar helper find: "'+collection+'" not found');
// }
// return [];
//});
//
//Handlebars.registerHelper("foreach",function(arr,options) {
// if (options.inverse && !arr.length) {
// return options.inverse(this);
// }
// return arr.map(function(item,index) {
// item.$index = index;
// item.$first = index === 0;
// item.$last = index === arr.length-1;
// return options.fn(item);
// }).join('');
//});
}
}());
<head>
<title>mCotton of Microduino</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
/**
* Created by chenhao on 15/4/7.
*/
Meteor.startup(function () {
//GoogleMaps.load();
//
//Tracker.autorun(function() {
// console.log('There are ' + Posts.find().count() + ' posts');
//});
Template.registerHelper("formatDate", function (timestamp) {
return moment(timestamp).format("YYYY-MM-DD HH:mm:ss");
});
Template.registerHelper("formatDateHour", function (timestamp) {
return moment(timestamp).format("YYYY-MM-DD HH:00");
});
Template.registerHelper("showControlTypeIcon", function (control_type, control_value) {
return controlTypeIcon(control_type, control_value);
});
Template.registerHelper("showDataTypeIcon", function (data_type) {
return dataTypeIcon(data_type);
});
Handlebars.registerHelper('isAdminUser', function() {
return Roles.userIsInRole(Meteor.user(), ['admin']);
});
UI.registerHelper('equals', function (a, b) {
return a == b;
});
UI.registerHelper('obj2String', function (obj) {
if (typeof obj != 'object')
return obj;
return JSON.stringify(obj);
});
UI.registerHelper('isAutherOrGrantedRole', function (role) {
var user = Meteor.user();
var isAuthor = this && this.author_user_id === user._id;
//var isGranted = (user && role) ? _.contains(user.roles, role) : false;
var isGranted = Roles.userIsInRole(Meteor.user(), [role]);
return isAuthor || isGranted;
});
UI.registerHelper('isOwnerOrGrantedRole', function (role) {
var user = Meteor.user();
var isOwner = this && this.owner_user_id === user._id;
//var isGranted = (user && role) ? _.contains(user.roles, role) : false;
var isGranted = Roles.userIsInRole(Meteor.user(), [role]);
return isOwner || isGranted;
});
UI.registerHelper('isGranted', function (role) {
// var user = Meteor.user();
// return (user && role) ? _.contains(user.roles, role) : false;
return Roles.userIsInRole(Meteor.user(), [role]);
});
Template.registerHelper('log', function () {
console.log(this, arguments);
});
//Uploader.uploadUrl = Meteor.absoluteUrl("upload"); // Cordova needs absolute URL
MAPBOX_TOKEN = 'pk.eyJ1IjoicGF1bG9ib3JnZXMiLCJhIjoicFQ1Sll5ZyJ9.alPGD574u3NOBi2iiIh--g';
});
\ No newline at end of file
@card-title-height: 60px;
@card-desc-height: 60px;
h2 {
font-size: 24px;
}
h3 {
font-size: 20px;
}
.container {
width: 100%;
margin: 0px;
padding: 0px;
}
.home-card {
height: 240px;
margin: 0px;
padding: 40px;
}
.entity-card {
border-right: 3px solid #BBB;
border-bottom: 3px solid #BBB;
margin: 5px 0px 5px 0px;
overflow: hidden;
background: #fff;
border-radius: 10px;
padding: 5px;
}
.entity-box {
border-right: 3px solid #BBB;
border-bottom: 3px solid #BBB;
margin: 10px;
overflow: hidden;
background: #fff;
border-radius: 10px;
padding: 5px;
}
.thumbnail {
padding: 0px;
border-radius: 5px;
width: 100%;
}
.thumbnail-simple {
padding: 0px;
border-radius: 5px;
width: 40px;
}
.entity-title {
margin: 0px;
overflow: hidden;
background: #fff;
padding: 10px;
height: @card-title-height;
}
.entity-id {
margin: 0px;
overflow: hidden;
background: #fff;
padding: 0px 10px 0px 10px ;
height: 20px;
}
.entity-sub {
margin: 0px;
overflow: hidden;
background: #fff;
border-radius: 10px;
padding: 5px ;
}
.entity-desc {
margin: 0px;
overflow: hidden;
background: #fff;
padding: 10px;
height: @card-desc-height;
}
.entity-item {
border: 1px solid #BBB;
margin: 10px;
overflow: hidden;
background: #fff;
border-radius: 10px;
padding: 10px;
}
.project-border {
border-right: 1px solid #155dd2;
border-bottom: 5px solid #94b0d4;
}
.module-border {
border-right: 1px solid #BB0;
border-bottom: 5px solid #BB0;
}
.device-border {
border-right: 1px solid #26a396;
border-bottom: 5px solid #49bbaf;
}
.sub-border {
border-right: 0px;
border-bottom: 2px solid #BBB;
}
.module-item {
height: 200px;
}
.data-item {
height: 120px;
}
.control-item {
height: 120px;
}
.module-item {
height: 240px;
}
.data-event-item {
height: 140px;
}
.control-event-item {
height: 140px;
}
.mouse-on:hover {
background-color: lightgoldenrodyellow;
}
.tip {
min-height: 65px;
background-color: #fff;
padding-left: 10px;
padding-right: 10px;
position: absolute;
font-size: 12px;
right: 10px;
bottom: 20px;
border-radius: 3px;
line-height: 18px;
border: 1px solid #ccc;
}
.tip input[type='button'] {
margin-left: 10px;
margin-right: 10px;
margin-top: 10px;
background-color: #0D9BF2;
height: 30px;
text-align: center;
line-height: 30px;
color: #fff;
font-size: 12px;
border-radius: 3px;
outline: none;
border: 0;
float: right;
}
.grid-block, .post, .comments li, .comment-form {
border: 1px solid #AAA;
border-bottom: 3px solid #BBB;
margin: 0px;
overflow: hidden;
background: #fff;
border-radius: 3px;
padding: 10px;
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
-moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.15);
}
.array-editor {
border-left: 3px solid #BBB;
margin: 0px;
overflow: hidden;
background: #fff;
padding: 10px;
border-radius: 1px;
}
.image-box {
border: 2px solid #BBB;
margin: 0px;
overflow: hidden;
background: #fff;
border-radius: 10px;
padding: 5px;
}
.gray {
border-left: 10px solid #BBB;
}
body {
background: #eee;
color: #666666;
margin: 0;
padding: 0;
}
.page {
position: absolute;
top: 0px;
width: 100%;
}
.navbar {
margin-bottom: 10px;
}
/* line 32, ../sass/style.scss */
.navbar .navbar-inner {
border-radius: 0px 0px 3px 3px;
}
#spinner {
height: 300px;
}
.imageArea {
margin-bottom: 70px;
}
.imageArea .media {
margin-top: 0;
margin-right: 10px;
width: 300px;
max-width: 300px;
}
/*.scorll-item {*/
/*height: auto;*/
/*margin: 0 auto;*/
/*width: 100%;*/
/*position: relative;*/
/*box-shadow: 0 0 50px rgba(0, 0, 0, 0.8);*/
/*padding: 80px 0 150px 0;*/
/*background-size: 100% auto;*/
/*}*/
.mtitle {
font-size: 18px;
color: #000;
}
.mdesc {
font-size: 16px;
color: #000;
padding: 10px 20px 0 20px;
text-align: left;
}
.post {
/* For modern browsers */
/* For IE 6/7 (trigger hasLayout) */
zoom: 1;
position: relative;
opacity: 1;
}
.post:before, .post:after {
content: "";
display: table;
}
.post:after {
clear: both;
}
.post.invisible {
opacity: 0;
}
.post.instant {
-webkit-transition: none;
-moz-transition: none;
-o-transition: none;
transition: none;
}
.post.animate {
-webkit-transition: all 300ms 0ms;
-moz-transition: all 300ms 0ms ease-in;
-o-transition: all 300ms 0ms ease-in;
transition: all 300ms 0ms ease-in;
}
.post .upvote {
display: block;
margin: 7px 12px 0 0;
float: left;
}
.post .post-content {
float: left;
}
.post .post-content h3 {
margin: 0;
line-height: 1.4;
font-size: 18px;
}
.post .post-content h3 a {
display: inline-block;
margin-right: 5px;
}
.post .post-content h3 span {
font-weight: normal;
font-size: 14px;
display: inline-block;
color: #aaaaaa;
}
.post .post-content p {
margin: 0;
}
.post .discuss {
display: block;
float: right;
margin-top: 7px;
}
.comments {
list-style-type: none;
margin: 0;
}
.comments li h4 {
font-size: 16px;
margin: 0;
}
.comments li h4 .date {
font-size: 12px;
font-weight: normal;
}
.comments li h4 a {
font-size: 12px;
}
.comments li p:last-child {
margin-bottom: 0;
}
.dropdown-menu span {
display: block;
padding: 3px 20px;
clear: both;
line-height: 20px;
color: #bbb;
white-space: nowrap;
}
.load-more {
display: block;
border-radius: 3px;
background: rgba(0, 0, 0, 0.05);
text-align: center;
height: 60px;
line-height: 60px;
margin-bottom: 10px;
}
.load-more:hover {
text-decoration: none;
background: rgba(0, 0, 0, 0.1);
}
.posts .spinner-container {
position: relative;
height: 100px;
}
.modules .spinner-container {
position: relative;
height: 100px;
}
.jumbotron {
text-align: center;
}
.jumbotron h2 {
font-size: 60px;
font-weight: 100;
}
@-webkit-keyframes fadeOut {
0% {
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
}
}
@keyframes fadeOut {
0% {
opacity: 0;
}
10% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.errors {
position: fixed;
z-index: 10000;
padding: 10px;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
pointer-events: none;
}
.alert {
animation: fadeOut 2700ms ease-in 0s 1 forwards;
-webkit-animation: fadeOut 2700ms ease-in 0s 1 forwards;
-moz-animation: fadeOut 2700ms ease-in 0s 1 forwards;
width: 250px;
float: right;
clear: both;
margin-bottom: 5px;
pointer-events: auto;
}
/*Style for Smart City*/
.node {
stroke: #fff;
stroke-width: 1.5px;
}
.link {
stroke: #999;
stroke-opacity: .6;
}
<template name="layout">
<div class="container">
<div class="col-sm-12">
{{> header}}
</div>
<div class="col-sm-12">
{{> errors}}
</div>
<div id="main" class="row-fluid col-sm-12">
{{> yield}}
</div>
<div class="col-sm-12">
{{> footer}}
</div>
</div>
</template>
\ No newline at end of file
///**
// * Created by chenhao on 15/4/7.
// */
//Template.layout.helpers({
// moduleName: function () {
// return Session.get('moduleName');
// }
//});
\ No newline at end of file
<template name="notFound">
<div class="not-found jumbotron">
<h2>404</h2>
<p>Sorry, we couldn't find a page at this address. </p>
<p>抱歉,我们无法找到该页面。</p>
</div>
</template>
\ No newline at end of file
<template name="commentItem">
<li>
<h4>
<span class="author">{{author}}</span>
<span class="date">on {{submittedText}}</span>
</h4>
<p>{{body}}</p>
</li>
</template>
\ No newline at end of file
Template.commentItem.helpers({
submittedText: function () {
return this.submitted.toString();
}
});
\ No newline at end of file
<template name="commentSubmit">
<form name="comment" class="comment-form form">
<div class="form-group {{errorClass 'body'}}">
<div class="controls">
<label for="body">Comment on this post</label>
<textarea name="body" id="body" class="form-control" rows="3"></textarea>
<span class="help-block">{{errorMessage 'body'}}</span>
</div>
</div>
<button type="submit" class="btn btn-primary">Add Comment</button>
</form>
</template>
\ No newline at end of file
Template.commentSubmit.created = function () {
Session.set('commentSubmitErrors', {});
}
Template.commentSubmit.helpers({
errorMessage: function (field) {
return Session.get('commentSubmitErrors')[field];
},
errorClass: function (field) {
return !!Session.get('commentSubmitErrors')[field] ? 'has-error' : '';
}
});
Template.commentSubmit.events({
'submit form': function (e, template) {
e.preventDefault();
var $body = $(e.target).find('[name=body]');
var comment = {
body: $body.val(),
postId: template.data._id
};
var errors = {};
if (!comment.body) {
errors.body = "Please write some content";
return Session.set('commentSubmitErrors', errors);
}
Meteor.call('commentInsert', comment, function (error, commentId) {
if (error) {
throwError(error.reason);
} else {
$body.val('');
}
});
}
});
\ No newline at end of file
<template name="controlBoard">
<div class="col-sm-4">
<div class="bootcards-cards entity-item control-event-item">
{{> controlBoardTemplate this}}
</div>
</div>
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/4/14.
*/
Template.controlBoard.helpers({
controlBoardTemplate: function() {
switch(this.control_type){
case 'LED':
case 'SWT':
return Template.controlBoardChange;
case 'BTN':
return Template.controlBoardClick;
case 'TXT':
return Template.controlBoardInput;
}
return Template.controlNone;
}
});
\ No newline at end of file
<template name="controlNone">
<div>
<p><img src='{{ showControlTypeIcon control_type control_value}}' alt='{{ control_type }}'/>
<b>{{control_name}}</b>
</p>
<p>On Building</p>
</div>
</template>
<template name="controlBoardChange">
<div>
<p><img src='{{ showControlTypeIcon control_type control_value}}' alt='{{ control_type }}'/>
<b>{{control_name}}</b>
</p>
<p><a href="" class="btn btn-info changeBtn">
<span class="glyphicon glyphicon-off" aria-hidden="true"></span> Switch </a></p>
{{#if control_submit_time}}
<div class="pull-right"><i>{{ formatDate control_submit_time }}</i></div>
{{/if}}
</div>
</template>
<template name="controlBoardClick">
<div>
<p><img src='{{ showControlTypeIcon control_type control_value }}' alt='{{ control_type }}'/>
<b>{{control_name}}</b></p>
<p><a href="" class="btn btn-info clickBtn">
<span class="glyphicon glyphicon-hand-down" aria-hidden="true"></span> Click </a></p>
{{#if control_submit_time}}
<div class="pull-right"><i>{{ formatDate control_submit_time }}</i></div>
{{/if}}
</div>
</template>
<template name="controlBoardInput">
<div>
<p><img src='{{ showControlTypeIcon control_type control_value }}' alt='{{ control_type }}'/>
<b>{{control_name}}</b></p>
<p>
<form className="sendText">
<input type="text" name="text" value="{{control_value}}"/>
<a href="" class="btn btn-info sendBtn">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span> Send
</a>
{{#if control_submit_time}}
<div class="pull-right"><i>{{ formatDate control_submit_time }}</i></div>
{{/if}}
</form>
</p>
</div>
</template>
//Template.controlBoardChange.helpers({
// control_type_icon: function () {
// return controlTypeIcon(this.control_type, this.control_value);
// },
//});
Template.controlBoardChange.events({
'click .changeBtn': function (e) {
e.preventDefault();
// console.log("controlBoardChange", JSON.stringify(this));
var device_id = this.device_id;
var device = Collections.Devices.findOne({_id: device_id});
// console.log("controlBoardChange", device_id, device);
var value = !(this.control_value === "true");
var entity = {
control_name: this.control_name, control_type: this.control_type,
device_id: this.device_id, owner_user_id: device.owner_user_id,
control_value: value.toString(), control_submit_time: new Date(),
};
// console.log("ControlEvents.insert", entity);
Collections.ControlEvents.insert(entity);
}
});
Template.controlBoardClick.events({
'click .clickBtn': function (e) {
e.preventDefault();
var device_id = this.device_id;
var device = Collections.Devices.findOne({_id: device_id});
var value = "1";
var entity = {
control_name: this.control_name, control_type: this.control_type,
device_id: this.device_id, owner_user_id: device.owner_user_id,
control_value: value, control_submit_time: new Date(),
};
console.log("ControlEvents.insert", entity);
Collections.ControlEvents.insert(entity);
}
});
Template.controlBoardInput.events({
'click .sendBtn': function (e) {
e.preventDefault();
var device_id = this.device_id;
var device = Collections.Devices.findOne({_id: device_id});
// Get value from form element
var value = e.target.parentNode.text.value;
var entity = {
control_name: this.control_name, control_type: this.control_type,
device_id: this.device_id, owner_user_id: device.owner_user_id,
control_value: value, control_submit_time: new Date(),
};
// console.log("ControlEvents.insert", entity);
Collections.ControlEvents.insert(entity);
}
});
\ No newline at end of file
<template name="dataEvent">
<!--{{json}}-->
{{> dataEventTemplate this}}
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/4/14.
*/
Template.dataEvent.helpers({
json: function(){
return JSON.stringify(this);
},
dataEventTemplate: function () {
// console.log(this);
if (this.data_show_list) {
switch (this.data_type) {
case 'PIC':
return Template.dataEventPictures;
}
return Template.dataEventList;
}
return Template.dataEventOne;
}
});
\ No newline at end of file
<template name="dataNone">
</template>
<template name="dataEventOne">
<div class="col-sm-4">
<div class="bootcards-cards entity-item data-event-item">
<p><img src='{{ showDataTypeIcon data_type }}' alt='{{ data_type }}'/> <b>{{ data_name }}</b>
, {{ data_value }} {{ data_unit }}</p>
<div class="pull-right"><i>{{ formatDate data_submit_time }}</i></div>
</div>
</div>
</template>
<!-- ============= -->
<!-- dataEventList -->
<template name="dataEventList_Item">
<div>
<p>{{ formatDate data_submit_time }} {{ obj2String data_value }} {{ data_unit }}</p>
</div>
</template>
<template name="dataEventList">
<div class="col-sm-12">
<div class="bootcards-cards entity-item">
<p><img src='{{ showDataTypeIcon data_type }}' alt='{{ data_type }}'/> <b>{{ data_name }}</b></p>
{{#each data_event_items }}
{{> dataEventList_Item }}
{{/each }}
<div class="pull-right"><i>{{ formatDate data_submit_time }}</i></div>
</div>
</div>
</template>
<!-- ============= -->
<!-- dataEventPictures -->
<template name="dataEventList_Picture">
<p><img src='{{ data_value }}' height="200"/> {{ formatDate data_submit_time }}</p>
</template>
<template name="dataEventPictures">
<div>
dataEventPictures
<p>{{ formatDate data_submit_time }}</p>
<div>
<p><img src='{{ showDataTypeIcon data_type }}' alt='{{ data_type }}'/> <b>{{ data_name }}</b></p>
{{#each data_event_items }}
{{> dataEventList_Picture}}
{{/each }}
</div>
<iframe width="560" height="315" src="//www.youtube.com/embed/0njX2e7GwDs" frameborder="0"
allowfullscreen></iframe>
</div>
</template>
\ No newline at end of file
//Template.controlBoardChange.helpers({
// control_type_icon: function () {
// return controlTypeIcon(this.control_type, this.control_value);
// },
//});
Template.dataEventOne.helpers({});
Template.dataEventList.helpers({
data_event_items: function () {
var event = Collections.DataEvents.findOne({device_id: this.device_id, data_name: this.data_name},
{sort: {sid: -1}, fields: {sid: true}});
//console.log("sid", event.sid);
if (event) {
return Collections.DataEvents.find({device_id: this.device_id, data_name: this.data_name, sid: event.sid},
{sort: {data_submit_time: -1}});
}
else {
return Collections.DataEvents.find({device_id: this.device_id, data_name: this.data_name},
{sort: {data_submit_time: -1}, limit: DATA_EVENT_SHOW_LIST_LIMIT});
}
}
});
Template.dataEventPictures.helpers({
data_event_items: function () {
return Collections.DataEvents.find({device_id: this.device_id, data_name: this.data_name},
{sort: {data_submit_time: -1}, limit: DATA_EVENT_SHOW_LIST_LIMIT});
}
});
<template name="dataVisual">
<div class="entity-box sub-border">
<h4> Data
<a href="{{pathFor 'deviceDetail'}}" class="btn btn-info history">
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>Back</a>
</h4>
<div class="col-sm-12 bootcards-cards">
{{> dataVisualTemplate this}}
</div>
</div>
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/4/14.
*/
// Meteor.subscribe("dataevents", Meteor.userId());
Template.dataVisual.helpers({
dataVisualTemplate: function () {
// console.log("dataVisualTemplate", this);
var chart = "LINE";
var project = Collections.Projects.findOne( {_id: this.project_id} );
if (project) {
chart = project.show_chart;
}
// console.log("dataVisualTemplate", project.show_chart);
switch (chart) {
case "PIE" :
return Template.dataVisualPie;
case "BAR" :
return Template.dataVisualBar;
case "MY_CITY" :
return Template.dataVisualMyCity;
case "LINE" :
return Template.dataVisualLine;
case "EGG" :
return Template.dataVisualEgg;
case "MPH" :
return Template.dataVisualMapHeatmap;
case "MPM" :
return Template.dataVisualMapMarkers;
case "MPP" :
return Template.dataVisualMapPoint;
case "MPT" :
return Template.dataVisualMapTrace;
}
return Template.dataVisualLine;
}
});
Template.dataVisual.events({
'click .raw-data':function(e,tmpl) {
//e.preventDefault();
//$('html, body').animate({
// scrollTop: $("#item_id").offset().top
//}, 600);
}
});
\ No newline at end of file
<template name="dataVisualBar">
bar
<p>{{json_data}}</p>
{{> dataVisualRange}}
<div id='visual' class="entity-item">
<svg></svg>
</div>
</template>
\ No newline at end of file
// dataVisual Line
VIAUSL_JSON_DATA = "visual_json_data";
VIAUSL_FROM_TIME = "visual_from_time";
VIAUSL_TO_TIME = "visual_to_time";
VIAUSL_TIME_FORMAT = "visual_time_format";
Template.dataVisualBar.helpers({
json_data: function () {
delete Session.keys[VIAUSL_JSON_DATA];
var hour = moment().hour();
Session.setDefault(VIAUSL_FROM_TIME, moment(hour - 1, "HH").valueOf());
Session.setDefault(VIAUSL_TO_TIME, moment(hour + 1, "HH").valueOf());
Session.setDefault(VIAUSL_TIME_FORMAT, "HH:mm");
var timeStart = getDateFromSession(VIAUSL_FROM_TIME);
var events = Collections.DataEvents.find({
device_id: this._id,
data_submit_time: {$gte: timeStart.toDate()}
},
{sort: {data_name: 1, data_submit_time: 1}}).fetch();
// console.log("dataVisualEvents", events.length);
if (events.length <= 0) return;
// TODO
console.log("retjson", retjson);
Session.set(VIAUSL_JSON_DATA, retjson);
// return JSON.stringify(retjson);
return;
},
//from_time: function () {
// return getDateFromSession(VIAUSL_FROM_TIME);
//},
//to_time: function () {
// return getDateFromSession(VIAUSL_TO_TIME);
//},
});
//Template.dataVisualBar.events({
// "click .showHour": function () {
// console.log("click .showHour");
// var timeinfo = moment().hour();
// addDateToSession(VIAUSL_FROM_TIME, moment(timeinfo - 1, "HH"));
// addDateToSession(VIAUSL_TO_TIME, moment(timeinfo + 1, "HH"));
// Session.set(VIAUSL_TIME_FORMAT, "HH:mm");
// },
// "click .showDay": function () {
// console.log("click .showDay");
// var timeinfo = moment().date();
// addDateToSession(VIAUSL_FROM_TIME, moment(timeinfo - 1, "DD"));
// addDateToSession(VIAUSL_TO_TIME, moment(timeinfo + 1, "DD"));
// Session.set(VIAUSL_TIME_FORMAT, "MM/DD HH:00");
// },
// "click .showWeek": function () {
// console.log("click .showWeek");
// var timeinfo = moment().week();
// addDateToSession(VIAUSL_FROM_TIME, moment(timeinfo - 1, "WW"));
// addDateToSession(VIAUSL_TO_TIME, moment(timeinfo + 1, "WW"));
// Session.set(VIAUSL_TIME_FORMAT, "MM/DD");
// }
//});
Template.dataVisualBar.rendered = function () {
};
\ No newline at end of file
<template name="dataVisualLine">
<p>{{json_data}}</p>
{{> dataVisualRange}}
<div id='visual' class="entity-item">
<svg></svg>
</div>
</template>
<template name="dataVisualRange">
<p>[ {{formatDateHour from_time}} --> {{ formatDateHour to_time}} ]
<a href="" class="btn btn-default showHour">2 Hours</a>
<a href="" class="btn btn-default showDay">2 Days</a>
<a href="" class="btn btn-default showWeek">2 Weeks</a></p>
</template>
// dataVisual Line
VIAUSL_JSON_DATA = "visual_json_data";
VIAUSL_FROM_TIME = "visual_from_time";
VIAUSL_TO_TIME = "visual_to_time";
VIAUSL_TIME_FORMAT = "visual_time_format";
Template.dataVisualLine.helpers({
json_data: function () {
delete Session.keys[VIAUSL_JSON_DATA];
var hour = moment().hour();
Session.setDefault(VIAUSL_FROM_TIME, moment(hour - 1, "HH").valueOf());
Session.setDefault(VIAUSL_TO_TIME, moment(hour + 1, "HH").valueOf());
Session.setDefault(VIAUSL_TIME_FORMAT, "HH:mm");
var timeStart = getDateFromSession(VIAUSL_FROM_TIME);
var events = Collections.DataEvents.find({
device_id: this._id,
data_submit_time: {$gte: timeStart.toDate()}
},
{sort: {data_name: 1, data_submit_time: 1}}).fetch();
// console.log("dataVisualEvents", events.length);
if (events.length <= 0) return;
var currentKey;
var retjson = [], values = [];
var currentSerial;
var nm, vl, dt;
_.forEach(events, function (event) {
nm = event.data_name;
vl = parseFloat(event.data_value);
dt = event.data_submit_time.getTime();
// console.log("getDataEvent", n, e, d);
if (nm != currentKey) {
if (currentKey) {
currentSerial = {
key: currentKey,
values: values
};
retjson.push(currentSerial);
}
currentKey = nm;
values = [];
}
if (vl != 0)
values.push({x: dt, y: vl});
});
//for (var i = 0; i < events.length; i++) {
// nm = events[i].data_name;
// vl = parseFloat(events[i].data_value);
// dt = events[i].data_submit_time.getTime();
//
// // console.log("getDataEvent", n, e, d);
//
// if (nm != currentKey) {
// if (currentKey) {
// currentSerial = {
// key: currentKey,
// values: values
// };
// retjson.push(currentSerial);
// }
//
// currentKey = nm;
// values = [];
// }
//
// if (vl != 0)
// values.push({x: dt, y: vl});
//}
currentSerial = {
key: currentKey,
values: values
};
retjson.push(currentSerial);
console.log("retjson", retjson);
Session.set(VIAUSL_JSON_DATA, retjson);
//return JSON.stringify(retjson);
return;
},
//from_time: function () {
// return getDateFromSession(VIAUSL_FROM_TIME);
//},
//to_time: function () {
// return getDateFromSession(VIAUSL_TO_TIME);
//},
});
Template.dataVisualRange.helpers({
from_time: function () {
return getDateFromSession(VIAUSL_FROM_TIME);
},
to_time: function () {
return getDateFromSession(VIAUSL_TO_TIME);
},
});
Template.dataVisualRange.events({
"click .showHour": function () {
console.log("click .showHour");
var timeinfo = moment().hour();
addDateToSession(VIAUSL_FROM_TIME, moment(timeinfo - 1, "HH"));
addDateToSession(VIAUSL_TO_TIME, moment(timeinfo + 1, "HH"));
Session.set(VIAUSL_TIME_FORMAT, "HH:mm");
},
"click .showDay": function () {
console.log("click .showDay");
var timeinfo = moment().date();
addDateToSession(VIAUSL_FROM_TIME, moment(timeinfo - 1, "DD"));
addDateToSession(VIAUSL_TO_TIME, moment(timeinfo + 1, "DD"));
Session.set(VIAUSL_TIME_FORMAT, "MM/DD HH:00");
},
"click .showWeek": function () {
console.log("click .showWeek");
var timeinfo = moment().week();
addDateToSession(VIAUSL_FROM_TIME, moment(timeinfo - 1, "WW"));
addDateToSession(VIAUSL_TO_TIME, moment(timeinfo + 1, "WW"));
Session.set(VIAUSL_TIME_FORMAT, "MM/DD");
}
});
Template.dataVisualLine.rendered = function () {
var width = window.innerWidth, height = window.innerHeight;
var timeStart = getDateFromSession(VIAUSL_FROM_TIME);
var timeEnd = getDateFromSession(VIAUSL_TO_TIME);
var timeFormat = Session.get(VIAUSL_TIME_FORMAT);
//console.log(timeStart.format("YYYY-MM-DD HH:mm:ss"));
//console.log(timeEnd.format("YYYY-MM-DD HH:mm:ss"));
//console.log(timeFormat);
var svg = d3.select('#visual svg')
.attr('width', width)
.attr('height', height);
var chart = nv.models.lineChart()
//.margin({left: 100}) //Adjust chart margins to give the x-axis some breathing room.
.useInteractiveGuideline(true) //We want nice looking tooltips and a guideline!
.showLegend(true) //Show the legend, allowing users to turn on/off line series.
.showYAxis(true) //Show the y-axis
.showXAxis(true) //Show the x-axis
;
nv.addGraph(function () {
chart.xAxis
.axisLabel('Time');
chart.yAxis
// .forceY([0,120])
.axisLabel('Values')
.tickFormat(d3.format('.02f'));
nv.utils.windowResize(function () {
chart.update();
});
return chart;
});
this.autorun(function () {
var graph = Session.get(VIAUSL_JSON_DATA);
var timeStart = getDateFromSession(VIAUSL_FROM_TIME);
var timeEnd = getDateFromSession(VIAUSL_TO_TIME);
var timeFormat = Session.get(VIAUSL_TIME_FORMAT);
//console.log(timeStart.format("YYYY-MM-DD HH:mm:ss"));
//console.log(timeEnd.format("YYYY-MM-DD HH:mm:ss"));
//console.log(timeFormat);
if (!graph) {
return;
}
// console.log("data", graph);
chart.forceX([timeStart.valueOf(), timeEnd.valueOf()]);
chart.xAxis
.tickFormat(function (d) {
return moment(d).format(timeFormat);
});
// .ticks(d3.time.hours, 5);
svg.datum(graph).call(chart);
chart.update();
});
};
\ No newline at end of file
<template name="dataVisualPie">
Pie
<p>{{json_data}}</p>
{{> dataVisualRange}}
<div id='visual' class="entity-item">
<svg></svg>
</div>
</template>
\ No newline at end of file
// dataVisual Line
VIAUSL_JSON_DATA = "visual_json_data";
VIAUSL_FROM_TIME = "visual_from_time";
VIAUSL_TO_TIME = "visual_to_time";
VIAUSL_TIME_FORMAT = "visual_time_format";
Template.dataVisualPie.helpers({
json_data: function () {
delete Session.keys[VIAUSL_JSON_DATA];
var hour = moment().hour();
Session.setDefault(VIAUSL_FROM_TIME, moment(hour - 1, "HH").valueOf());
Session.setDefault(VIAUSL_TO_TIME, moment(hour + 1, "HH").valueOf());
Session.setDefault(VIAUSL_TIME_FORMAT, "HH:mm");
var timeStart = getDateFromSession(VIAUSL_FROM_TIME);
var events = Collections.DataEvents.find({
device_id: this._id,
data_submit_time: {$gte: timeStart.toDate()}
},
{sort: {data_name: 1, data_submit_time: 1}}).fetch();
// console.log("dataVisualEvents", events.length);
if (events.length <= 0) return;
// TODO
console.log("retjson", retjson);
Session.set(VIAUSL_JSON_DATA, retjson);
// return JSON.stringify(retjson);
return;
},
//from_time: function () {
// return getDateFromSession(VIAUSL_FROM_TIME);
//},
//to_time: function () {
// return getDateFromSession(VIAUSL_TO_TIME);
//},
});
Template.dataVisualPie.rendered = function () {
};
\ No newline at end of file
<template name="dataVisualMyCity">
<p>{{json_data}}</p>
<div id="visual" class="entity-item">
<svg></svg>
</div>
</template>
\ No newline at end of file
// dataVisual My City
Template.dataVisualMyCity.helpers({
json_data: function () {
delete Session.keys[VIAUSL_JSON_DATA];
var cells = [], nodes_map = {}, nodes = [], links = [];
var event = Collections.DataEvents.findOne({device_id: this._id, data_name: 'Cell'},
{sort: {sid: -1}, fields: {sid: true}});
if (!event) {
return;
}
// console.log("sid", event.sid);
var events = Collections.DataEvents.find({device_id: this._id, data_name: 'Cell', sid: event.sid},
{sort: {data_submit_time: -1}}).fetch();
// console.log("getMyCityJson", events);
_.forEach( events, function(event){
cells.push( JSON.parse(event.data_value) );
});
var cell, from_node_id, to_node_id, cell_dir, to_node_name;
// handle nodes
var node_length = 0;
for (var i = 0; i < cells.length; i++) {
cell = cells[i];
if (!nodes_map[cell.ID]) {
nodes_map[cell.ID] = {
_id: node_length
};
nodes.push({
name: cell.ID,
group: cell.T,
});
node_length++;
}
}
//console.log("nodes", nodes);
// handle links
for (var i = 0; i < cells.length; i++) {
cell = cells[i];
from_node_id = nodes_map[cell.ID]._id;
cell_dir = cell.D;
for (var dir in cell_dir) {
if (cell_dir.hasOwnProperty(dir)) {
to_node_name = cell_dir[dir];
if (to_node_name && to_node_name != '0') {
to_node_id = nodes_map[to_node_name]._id;
links.push({
source: from_node_id,
target: to_node_id,
dir: dir,
value: 1,
});
}
}
}
}
// console.log("links", links);
var retjson = {
nodes: nodes,
links: links
};
console.log("retjson", retjson);
Session.set(VIAUSL_JSON_DATA, retjson);
// return JSON.stringify(retjson);
return;
},
});
Template.dataVisualMyCity.rendered = function () {
var graph = Session.get(VIAUSL_JSON_DATA);
if (!graph) {
return;
}
var width = window.innerWidth, height = window.innerHeight;
var rect_width = 100, link_distance = 120;
var force_value = -1800;
var color = d3.scale.category20();
var force = d3.layout.force()
.charge(force_value)
.linkDistance(link_distance)
.size([width, height]);
var svg = d3.select('#visual svg')
.attr('width', width)
.attr('height', height);
force
.nodes(graph.nodes)
.links(graph.links)
.start();
var link = svg.selectAll(".link")
.data(graph.links)
.enter().append("line")
.attr("class", "link")
.style("stroke-width", function (d) {
return Math.sqrt(d.value);
});
var link_text = svg.selectAll("text")
.data(graph.links)
.enter()
.append("g")
.append("text")
.text(function (d) {
return d.dir.toUpperCase();
})
.attr("class", "link-label")
.attr("text-anchor", "start");
var group = svg.selectAll(".node")
.data(graph.nodes)
.enter()
.append("g")
.call(force.drag);
//var node = group.append("rect")
// .attr("width", rect_width)
// .attr("height", rect_width)
// .style("fill", function (d) {
// return color(d.group);
// });
var node = group.append("circle")
.attr("r", 0)
.style("fill", function (d) {
return color(d.group);
});
var icon = group.append("image")
.attr("xlink:href", function (d) {
return imageByCityGroup(d.group);
})
.attr("width", rect_width - 12)
.attr("height", rect_width - 12);
var text = group.append("text")
.text(function (d) {
//return d.name;
return labelByCityGroup(d.group);
})
.style("font-size", "100%")
.style("stroke", "gray")
.style("stroke-width", 0.5)
.style("fill", "black");
group.append("title")
.text(function (d) {
return labelByCityGroup(d.group) + "\n" + d.name;
});
force.on("tick", function () {
link
.attr("x1", function (d) {
return d.source.x;
})
.attr("y1", function (d) {
return d.source.y;
})
.attr("x2", function (d) {
return d.target.x;
})
.attr("y2", function (d) {
return d.target.y;
});
node
.attr("x", function (d) {
return d.x - rect_width / 2;
})
.attr("y", function (d) {
return d.y - rect_width / 2;
});
text
.attr("x", function (d) {
return d.x - rect_width / 2;
})
.attr("y", function (d) {
return d.y - rect_width / 2 + 12;
});
icon
.attr("x", function (d) {
return d.x - rect_width / 2 + 6;
})
.attr("y", function (d) {
return d.y - rect_width / 2 + 12;
});
link_text
.attr("transform", function (d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y;
var dr = Math.sqrt(dx * dx + dy * dy);
var offset = (1 - (42 / dr));
var deg = 180 / Math.PI * Math.atan2(dy, dx);
var x = (d.target.x - dx * offset);
var y = (d.target.y - dy * offset);
return "translate(" + x + ", " + y + ") rotate(" + deg + ")";
});
});
};
\ No newline at end of file
<template name="dataVisualMapHeatmap">
<div class="row">
<p>{{json_data}}</p>
<div id="map" class="mapbox"></div>
<div id="area"></div>
</div>
</template>
\ No newline at end of file
<template name="dataVisualMapMarkers">
<div class="row">
<p>{{json_data}}</p>
<div id="map" class="mapbox"></div>
<div id="area"></div>
</div>
</template>
\ No newline at end of file
<template name="dataVisualMapPoint">
<div class="row">
<p>{{json_data}}</p>
<div id="map" class="mapbox"></div>
<div id="area"></div>
</div>
</template>
\ No newline at end of file
// dataVisual Line
//Meteor.subscribe('mapmarkers');
VISUAL_JSON_DATA = "visual_json_data";
Template.dataVisualMapPoint.created = function () {
delete Session.keys[VISUAL_JSON_DATA];
Session.setDefault(VISUAL_JSON_DATA, {lan: 0, lng: 0});
};
Template.dataVisualMapPoint.helpers({
json_data: function () {
var event = Collections.DataEvents.findOne({
device_id: this._id,
data_type: 'GPS'
},
{sort: {data_submit_time: -1}});
// console.log("dataVisualEvents", event);
if (!event) return;
var retjson = event.data_value;
Session.set(VISUAL_JSON_DATA, JSON.parse(retjson));
return JSON.stringify(retjson);
//return;
}
});
Template.dataVisualMapPoint.rendered = function () {
var width = 600;
$("#area").height(width);
var location = Session.get(VISUAL_JSON_DATA);
var map, marker;
Mapbox.debug = true;
Mapbox.load({
plugins: ['markercluster']
});
this.autorun(function () {
if (Mapbox.loaded()) {
// console.log("json_data", location);
L.mapbox.accessToken = MAPBOX_TOKEN;
map = L.mapbox.map('map')
.addLayer(L.mapbox.tileLayer('mapbox.streets'))
.setView([location.lat, location.lng], 16);
marker = L.marker(location, {
icon: L.mapbox.marker.icon({'marker-color': '#f86767'})
});
marker.addTo(map);
}
});
var animate = function () {
if (map && marker) {
location = Session.get(VISUAL_JSON_DATA);
map.setView([location.lat, location.lng], map.getZoom());
marker.setLatLng(location);
}
requestAnimationFrame(animate);
};
animate();
};
\ No newline at end of file
<template name="dataVisualMapTrace">
<div class="row">
<p>{{json_data}}</p>
<div id="map" class="mapbox"></div>
<div id="area"></div>
</div>
</template>
\ No newline at end of file
// dataVisual Line
//Meteor.subscribe('mapmarkers');
VISUAL_JSON_DATA = "visual_json_data";
Template.dataVisualMapTrace.created = function () {
delete Session.keys[VISUAL_JSON_DATA];
Session.setDefault(VISUAL_JSON_DATA, {lan: 39.87, lng: -243});
};
Template.dataVisualMapTrace.helpers({
json_data: function () {
var event = Collections.DataEvents.findOne({device_id: this._id, data_type: 'GPS'},
{sort: {sid: -1}, fields: {sid: true}});
if (!event) {
return;
}
// console.log("sid", event.sid);
var events = Collections.DataEvents.find({device_id: this._id, data_type: 'GPS', sid: event.sid},
{sort: {data_submit_time: 1}}).fetch();
// console.log("getMyCityJson", events);
var locations = [];
_.forEach( events, function(event){
locations.push( JSON.parse(event.data_value) );
});
var event = Collections.DataEvents.findOne({
device_id: this._id,
data_type: 'GPS'
},
{sort: {data_submit_time: -1}});
// console.log("dataVisualEvents", event);
if (!event) return;
var retjson = {name: event.data_name, value: JSON.parse(event.data_value)};
Session.set(VISUAL_JSON_DATA, retjson);
return JSON.stringify(retjson);
//return;
}
});
Template.dataVisualMapTrace.rendered = function () {
var width = 600;
$("#area").height(width);
var data = Session.get(VISUAL_JSON_DATA);
var location = data.value;
var map, marker;
Mapbox.debug = true;
Mapbox.load({
plugins: ['markercluster']
});
this.autorun(function () {
if (Mapbox.loaded()) {
// console.log("json_data", location);
L.mapbox.accessToken = MAPBOX_TOKEN;
map = L.mapbox.map('map');
map.addLayer(L.mapbox.tileLayer('mapbox.streets'))
.setView([location.lat, location.lng], 16);
marker = L.marker(location, {
icon: L.mapbox.marker.icon({
'marker-color': '#f86767'
})
});
marker.addTo(map);
}
});
var animate = function () {
if (map && marker) {
location = Session.get(VISUAL_JSON_DATA);
map.setView([location.lat, location.lng], map.getZoom());
marker.setLatLng(location);
}
requestAnimationFrame(animate);
};
animate();
};
\ No newline at end of file
<template name="dataVisualEgg">
<div>
<div class="row">
<div class="col-md-4">
<div class="entity-item">
<!--<p>{{json_data}}</p>-->
{{#each data_events}}
{{> eggEventValue }}
{{/each }}
</div>
<div class="entity-item">
<h4>Visualization</h4>
<div class="col-md-12">
<div>
<input type="checkbox" name="isRotate" class="isRotate"/>
Camera Rotate
</div>
<div>
<input type="checkbox" name="isInside" class="isInside" checked/>
Show Inside Temperatures
</div>
</div>
<h4> Color Map </h4>
<div class="col-md-12">
<div>
<div id="slider"></div>
<p>Green Temperature: {{slider.[0]}}, Red Temperature: {{slider.[1]}}</p>
</div>
<div class="list-group">
<a href="#" class="list-group-item colormap">Rainbow</a>
<a href="#" class="list-group-item colormap">CoolToWarm</a>
<a href="#" class="list-group-item colormap">BlackBody</a>
<a href="#" class="list-group-item colormap">Grayscale</a>
</div>
</div>
</div>
</div>
<div class="col-md-8 array-editor">
<div id="visual" class="col-md-12 entity-item "></div>
</div>
</div>
</div>
</template>
<template name="eggEventValue">
<div class="col-md-6">
<div class="bootcards-cards mouse-on" id="{{ data_name }}">
<p>
<!--<img src='{{ showDataTypeIcon data_type }}' alt='{{ data_type }}'/>-->
<b>{{ data_name }}</b>, {{ data_value }} {{ data_unit }}</p>
</div>
</div>
</template>
\ No newline at end of file
<template name="deviceDetail">
<div class="col-sm-9">
<div class="bootcards-cards entity-box device-border">
<div class="entity-sub">
<h3>
{{name}}
{{#if isOwnerOrGrantedRole 'admin'}}
<a href="{{pathFor 'deviceEdit'}}" class="btn btn-info edit">
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span> Edit </a>
{{/if}}
</h3>
</div>
<div class="entity-sub">
<!--{{#if currentUser}}-->
<!--<a href="{{pathFor 'deviceShare'}}" class="btn btn-primary assemble">-->
<!--<span class="glyphicon glyphicon-ice-lolly-tasted" aria-hidden="true"></span> Share It </a>-->
<!--{{/if}}-->
<h4>ID : {{_id}}</h4>
<p>{{desc}}</p>
<p>owner : {{owner_user_id}}</p>
<!--<p>Status : {{status}}</p>-->
<!--<p>Project : {{project_id}}</p>-->
<p>Share : {{share_label}}</p>
<p>Create at : {{ formatDate create_time}} , Update at : {{ formatDate last_update_time}}</p>
{{#unless project_id }}
<a href="" class="btn btn-primary selectProject">Choose a Project</a>
{{/unless}}
</div>
<div class="entity-box sub-border">
<div class="col-sm-12 bootcards-cards">
<h4>Images
{{#if isOwnerOrGrantedRole 'admin'}}
<a href="{{pathFor 'deviceImagesSelect'}}" class="btn btn-info select">
<span class="glyphicon glyphicon-edit" aria-hidden="true"></span> Edit </a>
{{/if}}
</h4>
{{#each img_ids}}
<div class="col-sm-4">
{{> thumbnail id=this}}
</div>
{{/each}}
</div>
</div>
{{#if project_id }}
{{> deviceData device_id=_id}}
{{> deviceControl device_id=_id}}
{{/if}}
{{#if isOwnerOrGrantedRole 'admin'}}
<a href="" class="btn btn-danger align-right remove">
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span> Remove </a>
{{/if}}
</div>
</div>
<div class="col-sm-3 array-editor">
<div class="bootcards-cards entity-box project-border">
<div class="entity-sub">
<h3>Project</h3>
{{#with project}}
{{> projectSummarySimple }}
{{/with}}
</div>
</div>
<div class="bootcards-cards entity-box device-border">
<div class="entity-sub">
<h3>Other's Devices</h3>
{{#each publicDevices}}
{{> deviceSummarySimple}}
{{/each}}
</div>
<div class="col-sm-6 text-left">
<a href="{{pathFor 'devicesListPublic'}}" class="btn btn-info align-right">
<span class="glyphicon glyphicon-ice-lolly-tasted" aria-hidden="true"></span> All </a>
</div>
</div>
</div>
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/4/29.
*/
// Meteor.subscribe('device');
Template.deviceDetail.helpers({
share_label: function () {
if(this.share)
return SHARES[this.share].label;
else
return SHARES[SHARE_PRIVATE].label;
},
project: function () {
return Collections.Projects.findOne({_id: this.project_id});
},
publicDevices: function () {
//console.log("Project ID ", this.data._id);
return Collections.Devices.find({
project_id: this.project_id,
owner_user_id: {$ne: Meteor.userId()},
share: SHARE_PUBLIC,
'status': {$lt: STATUS_DISABLE}
},
{limit: RECOMMENDED_ITEMS, sort: {last_update_time: -1}});
}
});
Template.deviceDetail.events({
'click .remove': function (e) {
e.preventDefault();
var currentId = this._id;
var _de = Collections.DataEvents.find({device_id: currentId}).count();
var _ce = Collections.ControlEvents.find({device_id: currentId}).count();
if (_de > 0 || _ce > 0) {
if (confirm("Remove this Device?")) {
var now = new Date();
Collections.Devices.update(currentId, {$set: {status: STATUS_DELETE, last_update_time: now}},
function (error) {
if (error) {
throwError(error.reason);
} else {
Router.go('devicesList');
}
});
}
}
else {
if (confirm("Delete this Device?")) {
Collections.Devices.remove(currentId);
}
}
Router.go('devicesList');
},
'click .selectProject': function (e) {
e.preventDefault();
// TODO
var currentId = this._id;
var _de = Collections.DataEvents.find({device_id: currentId}).count();
var _ce = Collections.ControlEvents.find({device_id: currentId}).count();
if (_de > 0 || _ce > 0) {
if (confirm("Retire this Device?")) {
var now = new Date();
Collections.Devices.update(currentId, {
$set: {
status: 'retired',
last_update_time: now
}
}, function (error) {
if (error) {
throwError(error.reason);
} else {
Router.go('devicesList');
}
});
}
}
else {
if (confirm("Delete this Device?")) {
Collections.Devices.remove(currentId);
}
}
Router.go('devicesList');
}
});
Template.moduleDetail.events({
'click .have': function (e) {
e.preventDefault();
// var enc = base64UrlEncode(this.name);
Router.go('myModuleSubmit', {}, {query: 'module_id=' + this._id + "&name=" + this.name});
},
'click .delete': function (e) {
e.preventDefault();
if (confirm("Delete this module?")) {
var currentmoduleId = this._id;
Collections.Modules.remove(currentmoduleId);
Router.go('modulesList');
}
},
'click .buy': function (e) {
e.preventDefault();
Router.go('buyModule', {module_id: this._id});
}
});
\ No newline at end of file
<template name="deviceEdit">
{{#autoForm id="deviceEdit" collection=Collections.Devices doc=this class="main form project-detail"}}
<h3>Edit Project</h3>
{{> afQuickField name='_id' type="hidden"}}
{{> afQuickField name='name'}}
{{> afQuickField name='desc' type="textarea"}}
{{> afQuickField name='share'}}
<div class="form-group">
<button type="submit" class="btn btn-primary">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span> Save
</button>
</div>
{{/autoForm}}
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/10/17.
*/
// Meteor.subscribe('devices');
Template.deviceEdit.events({
'submit form': function (e) {
e.preventDefault();
var currentId = $(e.target).find('[name=_id]').val();
var properties = {
name: $(e.target).find('[name=name]').val(),
desc: $(e.target).find('[name=desc]').val(),
share: $(e.target).find('[name=share]').val(),
};
console.log("deviceEdit", properties);
var errors = validateDevice(properties);
if (errors.name || errors.desc)
return Session.set('deviceSubmitErrors', errors);
var now = new Date();
var entity = _.extend(properties, {
last_update_time: now,
});
console.log("deviceEdit", entity);
Collections.Devices.update(currentId, {$set: entity}, function (error) {
if (error) {
throwError(error.reason);
} else {
Router.go('deviceDetail' , { _id: currentId});
}
});
},
});
Template.deviceEdit.created = function () {
Session.set('deviceEditErrors', {});
};
Template.deviceEdit.helpers({
errorMessage: function (field) {
return Session.get('deviceEditErrors')[field];
},
errorClass: function (field) {
return !!Session.get('deviceEditErrors')[field] ? 'has-error' : '';
}
});
\ No newline at end of file
<template name="deviceImagesSelect">
<div class="col-sm-12">
<div class="bootcards-cards entity-item device-border">
<a href="{{pathFor 'deviceDetail'}}" class="btn btn-info">
<h3><span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span> {{name}} </h3>
</a>
<h3>Selected Images</h3>
<div>
<div class="col-md-6">
{{#each img_ids}}
<!--{{this}}-->
<div class="col-md-6">
<div class="entity-item">
{{> deviceImagesSelected id=this}}
</div>
</div>
{{/each}}
</div>
<div class="col-md-6 array-editor">
<h4>Images</h4>
{{> imagesArea multi=true}}
{{#each all_images_ids}}
<!--{{this._id}}-->
<div class="col-md-6">
<div class="entity-item">
{{> deviceImagesAll id=this._id}}
</div>
</div>
{{/each}}
</div>
</div>
</div>
</div>
</template>
<template name="deviceImagesAll">
<div>
<b>{{_.name}}</b>
<span class="pull-right">
<a href="#" name="{{../../_id}}" class="btn btn-success select">
<span class="glyphicon glyphicon-arrow-left" aria-hidden="true"></span> </a>
</span>
</div>
<div style="text-align: center">
{{> thumbnail id=_._id }}
</div>
</template>
<template name="deviceImagesSelected">
<div class="bootcards-cards">
<b>{{_.name}}</b>
<span class="pull-right">
<a href="#" name="{{../../_id}}" class="btn btn-info delete">
<span class="glyphicon glyphicon-arrow-right" aria-hidden="true"></span> </a>
</span>
</div>
<div style="text-align: center">
{{> thumbnail id=_._id }}
</div>
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/10/14.
*/
/////////////
// deviceImagesSelect
/////////////
Template.deviceImagesSelect.helpers({
json: function () {
return JSON.stringify(this);
},
all_images_ids: function () {
if (this.img_ids)
return Collections.Images.find({owner: Meteor.userId(), _id: {$nin: this.img_ids}}, {fields: {_id: true}, sort: {uploadedAt: -1}});
else {
return Collections.Images.find({owner: Meteor.userId() }, {fields: {_id: true}, sort: {uploadedAt: -1}});
}
},
});
/////////////
// deviceImagesAll
/////////////
Template.deviceImagesAll.helpers({
_: function () {
return Collections.Images.findOne({_id: this.id});
}
});
Template.deviceImagesAll.events({
'click .select': function (e) {
e.preventDefault();
var ent = this;
//console.log("ent" , JSON.stringify(ent));
var id = e.currentTarget.name;
//console.log("id" , id);
Collections.Devices.update({_id: id}, {$push: {img_ids: ent.id}});
}
});
/////////////
// deviceImagesSelected
/////////////
Template.deviceImagesSelected.helpers({
_: function () {
return Collections.Images.findOne({_id: this.id});
}
});
Template.deviceImagesSelected.events({
'click .delete': function (e) {
e.preventDefault();
var ent = this;
// console.log("ent" , JSON.stringify(ent));
var id = e.currentTarget.name;
//console.log("id" , id);
Collections.Devices.update({_id: id}, {$pull: {img_ids: ent.id}});
}
});
<template name="deviceControl">
<div class="bootcards-cards entity-box sub-border">
<div class="entity-sub">
<h4>Control</h4>
<!--{{ device_id }}-->
</div>
<div class="entity-sub">
{{#each control_events}}
{{> controlBoard }}
{{/each }}
</div>
</div>
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/4/14.
*/
//Meteor.subscribe('controlevents');
Template.deviceControl.helpers({
gray: function () {
var device_id = this.device_id;
var device = Collections.Devices.findOne({_id: device_id});
if (device.status >= STATUS_DISABLE) {
return "gray";
}
},
control_events: function () {
var device_id = this.device_id;
var device = Collections.Devices.findOne({_id: device_id});
var project = Collections.Projects.findOne({_id: device.project_id});
var ctrl_points = project.control_points;
ctrl_points = _.sortBy(ctrl_points, function (point) {
return point.control_name;
});
var ret = new Mongo.Collection(null);
_.forEach(ctrl_points, function (point) {
var event = Collections.ControlEvents.findOne({device_id: device_id, control_name: point.control_name},
{sort: {control_submit_time: -1}});
if (event) {
// console.log("event.control_name", event.control_name, event.control_value, event.control_submit_time);
ret.insert(event);
} else {
// console.log("point.control_name", ctrl_points[i].control_name);
event = _.extend(point, {
device_id: device_id,
owner_user_id: device.owner_user_id,
});
ret.insert(event);
}
});
return ret.find();
}
});
\ No newline at end of file
<template name="deviceData">
<div class="bootcards-cards entity-box sub-border">
<div class="entity-sub">
<h4>Data
<a href="{{pathFor 'deviceDataVisual'}}" class="btn btn-info history">
<span class="glyphicon glyphicon-stats" aria-hidden="true"></span> Visulization </a>
<a href="" class="btn btn-primary dump" style="float: right">
<span class="glyphicon glyphicon-download-alt" aria-hidden="true"></span> Dump Data </a>
</h4>
{{#if dumping}}
<div class="col-sm-12">
<div class="loading">{{>spinner}}</div>
</div>
{{/if}}
</div>
<div class="entity-sub">
{{#each data_events}}
{{> dataEvent }}
{{/each }}
</div>
</div>
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/4/14.
*/
var rVarDumping;
Template.deviceData.created = function () {
rVarDumping = new ReactiveVar(false);
},
Template.deviceData.helpers({
gray: function () {
var device_id = this.device_id;
var device = Collections.Devices.findOne({ _id: device_id });
if (device.status >= STATUS_DISABLE) {
return "gray";
}
},
data_events: function () {
var device_id = this.device_id;
var device = Collections.Devices.findOne({ _id: device_id });
var project = Collections.Projects.findOne({ _id: device.project_id });
var data_points = project.data_points;
data_points = _.sortBy(data_points, function (point) {
return point.data_name;
});
var ret = new Mongo.Collection(null);
_.forEach(data_points, function (point) {
//console.log("event.datapoint", point;
var event = Collections.DataEvents.findOne({ device_id: device_id, data_name: point.data_name },
{ sort: { data_submit_time: -1 } });
if (event) {
//console.log("event.dataevent", event);
if (point.data_show_list) {
event.sid = point.sid;
}
ret.insert(event);
}
});
return ret.find();
},
dumping: function () {
return rVarDumping.get();
},
});
Template.deviceData.events({
"click .history": function () {
//console.log("history" , this.device_id);
Router.go('deviceDataVisual', { _id: this.device_id });
},
"click .dump": function () {
console.log("dump", this.device_id);
var date_str = moment().format(DATA_TIME_FORMAT);
rVarDumping.set(true);
var link = null;
Meteor.call("dumpDeviceData", this.device_id, date_str, function (error, result) {
rVarDumping.set(false);
console.log("dumpDeviceData", result);
if (result != "Failed") {
link = DATA_DOWNLOAD_PATH + "/" + result;
window.open(link, '_blank');
}
else {
alert("Dump Failed : " + error);
}
});
}
});
<template name="projectItemSimple">
<div>
<div class="entity-title">
<h4>{{project.name}}</h4>
</div>
<div class="entity-sub">
<div class="col-sm-6">
<p>ID : {{project._id}}</p>
<p>{{project.desc}}</p>
</div>
<div class="col-sm-6">
{{#each project.img_ids}}
<div class="col-sm-4">
{{> thumbnail id=this}}
</div>
{{/each}}
</div>
</div>
</div>
</template>
\ No newline at end of file
Template.projectItemSimple.helpers({
project: function(_id) {
return Collections.Projects.findOne({_id: this._id })
}
});
\ No newline at end of file
<template name="deviceSubmit">
{{#autoForm id="deviceEdit" collection=Collections.Devices class="main form project-detail"}}
<h3>Create My New Device</h3>
{{> projectItemSimple _id=project_id}}
{{> afQuickField name='project_id' value=project_id type='hidden'}}
{{> afQuickField name='name'}}
{{> afQuickField name='desc' type="textarea"}}
<button type="submit" class="btn btn-primary">
<span class="glyphicon glyphicon-ok" aria-hidden="true"></span> Save
</button>
{{/autoForm}}
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/4/8.
*/
Template.deviceSubmit.events({
'submit form': function (e) {
e.preventDefault();
var entity = {
project_id: $(e.target).find('[name=project_id]').val(),
name: $(e.target).find('[name=name]').val(),
desc: $(e.target).find('[name=desc]').val(),
};
// console.log(entity);
var errors = validateDevice(entity);
if (errors.name || errors.project_id)
return Session.set('deviceSubmitErrors', errors);
Meteor.call('deviceInsert', entity, function (error, result) {
if (error)
return throwError(error.reason);
if (result.sameNameDevice)
return throwError('This same name Project has already been added');
});
Router.go('devicesList');
}
});
\ No newline at end of file
<template name="deviceSummary">
<div class="bootcards-cards entity-card device-border">
<div class="entity-title">
<h3>{{name}}</h3>
</div>
<div class="entity-id">
ID : {{_id}}
</div>
<div class="entity-sub">
<a href="{{pathFor 'deviceDetail'}}" class="btn btn-info detail">
<span class="glyphicon glyphicon-sunglasses" aria-hidden="true"></span> Details </a>
<!--{{#if status }}-->
<!--<p>Status : {{status}}</p>-->
<!--{{/if}}-->
<!--&lt;!&ndash;<p>owner : {{ owner_user_id}}</p>&ndash;&gt;-->
<!--<p>Create at : {{ formatDate create_time}} , Update at : {{ formatDate last_update_time}}</p>-->
</div>
<div class="entity-desc">
<!--<p>Project ID : {{project_id}}</p>-->
{{desc}}
</div>
<div class="entity-sub">
{{#with firstIamge}}
<img src="{{this.url store='thumbs'}}" class="thumbnail"/>
{{/with}}
{{#unless firstIamge}}
<img src="/device.png" class="thumbnail"/>
{{/unless}}
</div>
</div>
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/4/29.
*/
Template.deviceSummary.helpers({
gray : function(){
if (this.status >= STATUS_DISABLE) {
return "gray";
}
},
firstIamge: function () {
if (this.img_ids && this.img_ids.length > 0)
return Collections.Images.findOne({_id: this.img_ids[0]}); // Where Images is an FS.Collection instance
},
});
Template.deviceSummary.events({
'click .disassemble': function (e) {
e.preventDefault();
var currentId = this._id;
var _de = Collections.DataEvents.find({device_id: currentId}).count();
var _ce = Collections.ControlEvents.find({device_id: currentId}).count();
if (_de > 0 || _ce > 0) {
if (confirm("Retire this Device?")){
var now = new Date();
Collections.Devices.update(currentId, {$set: {status : 'retired', last_update_time: now}}, function (error) {
if (error) {
throwError(error.reason);
} else {
Router.go('devicesList');
}
});
}
}
else {
if (confirm("Delete this Device?")) {
Collections.Devices.remove(currentId);
}
}
Router.go('devicesList');
},
'click .selectAppKit': function (e) {
e.preventDefault();
// TODO
var currentId = this._id;
var _de = Collections.DataEvents.find({device_id: currentId}).count();
var _ce = Collections.ControlEvents.find({device_id: currentId}).count();
if (_de > 0 || _ce > 0) {
if (confirm("Retire this Device?")){
var now = new Date();
Collections.Devices.update(currentId, {$set: {status : 'retired', last_update_time: now}}, function (error) {
if (error) {
throwError(error.reason);
} else {
Router.go('devicesList');
}
});
}
}
else {
if (confirm("Delete this Device?")) {
Collections.Devices.remove(currentId);
}
}
Router.go('devicesList');
}
});
<template name="deviceSummarySimple">
<div class="col-sm-12 entity-sub">
<a href="{{pathFor 'deviceDetail'}}">
<div class="col-sm-4">
{{#with firstIamge}}
<img src="{{this.url store='thumbs'}}" class="thumbnail-simple"/>
{{/with}}
{{#unless firstIamge}}
<img src="/device.png" class="thumbnail-simple"/>
{{/unless}}
</div>
<div class="col-sm-8">
{{name}}
</div>
</a>
</div>
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/4/29.
*/
Template.deviceSummarySimple.helpers({
firstIamge: function () {
if (this.img_ids && this.img_ids.length > 0)
return Collections.Images.findOne({_id: this.img_ids[0]}); // Where Images is an FS.Collection instance
},
});
\ No newline at end of file
<template name="deviceDataVisual">
<div class="entity-box device-border">
<div class="entity-sub">
<h3>{{name}}</h3>
<h4>ID : {{_id}}</h4>
{{> dataVisual this}}
</div>
</div>
</template>
\ No newline at end of file
<template name="devicesList">
<div class="col-sm-9">
<h3><span class="glyphicon glyphicon-ice-lolly-tasted" aria-hidden="true"></span> My Devices </h3>
<div>
{{#each myDevices}}
<div class="col-sm-4">
{{> deviceSummary}}
</div>
{{/each}}
{{#if Template.subscriptionsReady}}
{{#if hasMoreEntities}}
<div class="col-sm-12">
<a class="load-more" href="#">Load More</a>
</div>
{{/if}}
{{else}}
<div class="col-sm-12">
<div class="loading">{{>spinner}}</div>
</div>
{{/if}}
</div>
</div>
<div class="col-sm-3 array-editor">
<h3><span class="glyphicon glyphicon-ice-lolly-tasted" aria-hidden="true"></span> Other's Devices </h3>
<div>
{{#each publicDevices}}
<div class="col-sm-12">
{{> deviceSummarySimple}}
</div>
{{/each}}
<div class="col-sm-12">
<a class="btn btn-info" href="{{pathFor 'devicesListPublic'}}">
<span class="glyphicon glyphicon-ice-lolly-tasted" aria-hidden="true"></span> All </a>
</div>
</div>
</div>
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/4/7.
*/
Template.devicesList.onCreated(function () {
// 1. Initialization
var instance = this;
// console.log("Meteor UserID", Meteor.userId());
// initialize the reactive variables
instance.loaded = new ReactiveVar(0);
instance.limit = new ReactiveVar(DEVICE_PAGINATION);
// 2. Autorun
// will re-run when the "limit" reactive variables changes
instance.autorun(function () {
// get the limit
var limit = instance.limit.get();
//console.log("Asking for "+limit+" devices…")
// subscribe to the publication
var subscription = instance.subscribe('devices', limit);
// if subscription is ready, set limit to newLimit
if (subscription.ready()) {
// console.log("> Received "+limit+" devices. \n\n")
instance.loaded.set(limit);
} else {
// console.log("> Subscription is not ready yet. \n\n");
}
});
// 3. Cursor
instance.myDevices = function () {
return Collections.Devices.find({owner_user_id: Meteor.userId(), 'status': {$lt: STATUS_DISABLE}},
{limit: instance.loaded.get(), sort: {last_update_time: -1}});
};
instance.publicDevices = function () {
return Collections.Devices.find({
owner_user_id: {$ne: Meteor.userId()},
share: SHARE_PUBLIC,
'status': {$lt: STATUS_DISABLE}
},
{limit: DEVICE_PAGINATION, sort: {last_update_time: -1}});
};
});
Template.devicesList.helpers({
myDevices: function () {
// return Collections.Devices.find({'status': {$lt : STATUS_DISABLE}}, {sort: {last_update_time: -1}});
return Template.instance().myDevices();
},
publicDevices: function () {
// return Collections.Devices.find({'status': {$lt : STATUS_DISABLE}}, {sort: {last_update_time: -1}});
return Template.instance().publicDevices();
},
// are there more posts to show?
hasMoreEntities: function () {
return Template.instance().myDevices().count() >= Template.instance().limit.get();
},
owner: function () {
return JSON.stringify(Meteor.user());
}
});
Template.devicesList.events({
'click .load-more': function (event, instance) {
event.preventDefault();
// get current value for limit, i.e. how many posts are currently displayed
var limit = instance.limit.get();
// increase limit by 5 and update it
limit += DEVICE_PAGINATION;
instance.limit.set(limit);
}
});
\ No newline at end of file
<template name="home">
<div class="row col-sm-12">
{{#each ultraVisualData}}
{{> scroll_item}}
{{/each}}
</div>
</template>
<template name="scroll_item">
<!--<div class="anchor" name="{{paragraph}}"></div>-->
{{#if title}}
<div class="col-sm-4 home-card">
<section data-speed="4" data-type="background">
<div class="mtitle text-center">
<div data-0="left:100%;" data-1000="left:100%;" data-top="color:rgb(255,0,0);"
data-bottom="color:rgb(0,0,0);">
<h2>{{safe 'title'}}</h2>
</div>
</div>
<div class="text-center">
{{#each buttons}}
<a class="btn btn-success btn-large" href="{{href}}">{{label}}</a>
{{/each}}
</div>
<div class="mdesc text-center">
<p>{{description}}</p>
</div>
</section>
</div>
{{/if}}
{{#unless title}}
<div class="col-sm-4 home-card"
style="background: url({{background}}) 50% 0 fixed no-repeat;">
</div>
{{/unless}}
</template>
/**
* Created by chenhao on 15/7/20.
*/
var index = 0;
var img_index = 0;
//_imgs = ["/imgs/cover.png", "/imgs/mcookie.jpg", "/imgs/mic_light.png", "/imgs/music_box.png"];
var _imgs = ["/imgs/cover.png"];
var rVarProjectsCount = new ReactiveVar(0), rVarDevicesCount = new ReactiveVar(0),
rVarPublicDevicesCount = new ReactiveVar(0);
Template.home.helpers({
ultraVisualData: function () {
var _data = [
{
"title": "<strong> " + rVarProjectsCount.get() + " Awesome Projects </strong>",
"description": "Smart Vulture Egg, Weather Station, City Block",
"buttons": [
{label: "Projects ...", href: "/projects"},
]
},
{}, {
"title": "<strong> " + rVarDevicesCount.get() + " IoT Devices </strong>",
"description": "Push data or pull control via API of RESTful, WebSocket, MQTT, ...",
"buttons": [
{label: "microduino", href: "https://www.microduino.cc/store"},
{label: "mCookie", href: "https://www.microduino.cc/store"}
]
},
{}, {
"title": "<strong> " + rVarPublicDevicesCount.get() + " Open Data Devices</strong>",
"description": "Share your data to public, and dump it as your wish.",
"buttons": [
{label: "Public Devices ...", href: "/publicdevices"},
//{label: "Map ...", href: "/pdmap"}
]
},
{}, {
"title": "<strong> Data Visualization </strong>",
"description": "Data visualized by SVG, D3.js, WebGL, GIS, ...",
},
{}, {
"title": "<strong> Open Source Software </strong>",
"description": "We built full stack open sources software for open sources hardware",
"buttons": [
{label: "Fork from Github", href: "https://github.com/iascchen/mCotton"}
]
},
{}, {
"title": "<strong> In Javascript </strong>",
"description": "All platform built in Javascript, includes clouds, gateways, even in embedded devices"
},
{}, {
"title": "<strong> Android, iOS, HTML5 </strong>",
"description": "Control devices via mobile apps or browser. provides sample code in iOS Swift, Android, and H5"
},
{}, {
"title": "<strong> Wireless Connection </strong>",
"description": "Via BLE 4, Wifi, Zigbee"
}
];
return _data;
}
});
Template.home.rendered = function () {
//rVarProjectsCount = new ReactiveVar(0);
//rVarDevicesCount = new ReactiveVar(0);
//rVarPublicDevicesCount = new ReactiveVar(0);
Meteor.call('projectsCount', function (error, result) {
if (result) {
rVarProjectsCount.set(result);
}
});
Meteor.call('devicesCount', function (error, result) {
if (result) {
rVarDevicesCount.set(result);
}
});
Meteor.call('publicDevicesCount', function (error, result) {
if (result) {
rVarPublicDevicesCount.set(result);
}
});
};
Template.scroll_item.helpers({
background: function () {
var img_index2 = (img_index + 1) % _imgs.length;
return _imgs[img_index2];
}
});
\ No newline at end of file
<template name="imageStore">
<div class="main form module-detail">
<h3>Images Store</h3>
<p>Multiple Images : {{multiImage}}</p>
{{> imagesArea multi=multiImage}}
<div class="imageArea clearfix">
{{#each uploadedImages}}
{{> uploadedImage}}
{{/each}}
</div>
</div>
</template>
<template name="uploadedImage">
<div class="media well pull-left">
<a href="{{this.url}}" target="_blank" class="pull-left">
<img src="{{this.url store='thumbs'}}" alt="" class="img-rounded"></a>
<div class="media-body">
<h4 class="media-heading">{{this.name}}</h4>
<p><em>Original size: {{this.formattedSize}}<br>Thumbnail size: {{this.formattedSize store="thumbs"}}<br>Type: {{this.type store="images"}}
</em></p>
{{#if this.isUploaded}}
{{#if this.hasStored 'images'}}
{{#with this.url download=true}}
<a href="{{this}}" class="btn btn-info btn-xs" role="button">Download</a>
{{/with}}
{{else}}
Storing...
{{/if}}
{{else}}
Uploading: {{this.uploadProgress}}%
<p>{{> FS.UploadProgressBar}}</p>
{{/if}}
{{> FS.DeleteButton class="btn btn-danger btn-xs"}}
</div>
</div>
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/10/11.
*/
Template.imageStore.helpers({
uploadedImages: function () {
return Collections.Images.find({}, {sort: {uploadedAt: -1}});
},
multiImage: function () {
Session.set('uploadMultiImages', true);
return true;
}
});
\ No newline at end of file
<template name="accessDenied">
<div class="access-denied jumbotron">
<h2>Access Denied</h2>
<p>You can't get here! Please log in.</p>
</div>
</template>
\ No newline at end of file
<template name="errors">
<div class="errors">
{{#each errors}}
{{> error}}
{{/each}}
</div>
</template>
<template name="error">
<div class="alert alert-danger" role="alert">
<button type="button" class="close" data-dismiss="alert">&times;</button>
{{message}}
</div>
</template>
\ No newline at end of file
Errors = new Mongo.Collection(null);
Template.errors.helpers({
errors: function () {
return Errors.find();
}
});
Template.error.rendered = function () {
var error = this.data;
Meteor.setTimeout(function () {
Errors.remove(error._id);
}, 3000);
};
\ No newline at end of file
<template name="footer">
<div class="row">
<div class="col-sm-3 bootcards-cards"></div>
<div class="col-sm-3 bootcards-cards text-left">
<h3>Sites</h3>
<ul>
<li>English Site: <a href="http://microduino.cc">microduino.cc</a></li>
<li>中文站: <a href="http://microduino.cn">microduino.cn</a></li>
<li>mCotton: <a href="http://mcotton.microduino.cn">mCotton.microduino.cn</a></li>
</ul>
</div>
<div class="col-sm-3 bootcards-cards text-left">
<h3>Social</h3>
<ul>
<li><a href="https://www.facebook.com/Microduino" target="_blank">Facebook</a></li>
<li><a href="https://plus.google.com/u/0/communities/117933845827174624649"
target="_blank">Google+</a>
</li>
<li><a href="http://www.twitter.com/Microduino" target="_blank">Twitter</a></li>
<li><a href="http://wiki.Microduino.cc" target="_blank">Wiki</a></li>
</ul>
</div>
</div>
<div class="row text-center">
<div class="copyright"><span>© Microduino Inc. 2012-2015. All rights reserved.</span></div>
<div class="copyright"><span>© 美科科技有限公司. 2012-2015. 版权所有.</span>
<a href="http://www.miibeian.gov.cn" target="_blank">京ICP备15050568号</a></div>
</div>
</template>
\ No newline at end of file
<template name="header">
<nav class="navbar navbar-default" role="navigation">
<div class="container-fluid">
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navigation">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{pathFor 'home'}}">
<span class="glyphicon glyphicon-home" aria-hidden="true"></span> mCotton </a>
<a class="navbar-brand" href="{{pathFor 'projectsList'}}">
<span class="glyphicon glyphicon-ice-lolly" aria-hidden="true"></span> Projects </a>
{{#if currentUser}}
<a class="navbar-brand" href="{{pathFor 'devicesList'}}">
<span class="glyphicon glyphicon-ice-lolly-tasted" aria-hidden="true"></span> Devices </a>
<!--<a class="navbar-brand" href="{{pathFor 'myModulesList'}}">-->
<!--<span class="glyphicon glyphicon-tree-conifer" aria-hidden="true"></span> My Modules </a>-->
{{/if}}
{{#if isInRole 'admin'}}
<a href="#" class="navbar-brand dropdown-toggle" data-toggle="dropdown"
role="button" aria-haspopup="true" aria-expanded="false">
<span class="glyphicon glyphicon-cog" aria-hidden="true"></span>
Admin <b class="caret"></b>
</a>
<ul class="dropdown-menu navbar-nav">
<li><a href="/admin"> Admin </a></li>
<li><a href="{{pathFor 'modulesList'}}"> Modules </a></li>
<li><a href="{{pathFor 'imageStore'}}"> Image Store </a></li>
</ul>
{{/if}}
</div>
<div class="collapse navbar-collapse" id="navigation">
<ul class="nav navbar-nav navbar-right">
{{> loginButtons}}
</ul>
</div>
</div>
</nav>
</template>
\ No newline at end of file
Template.header.helpers({
activeRouteClass: function (/* route names */) {
var args = Array.prototype.slice.call(arguments, 0);
args.pop();
var active = _.any(args, function (name) {
return Router.current() && Router.current().route.getName() === name
});
return active && 'active';
}
});
\ No newline at end of file
<template name="imagesArea">
<div class="well imageDropArea">
<input type="file" class="images">
{{> FS.UploadProgressBar bootstrap=true}}
</div>
<!--{{this.multi}}-->
<!--<div class="imageArea clearfix">-->
<!--{{curImageIds}}-->
<!--</div>-->
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/10/13.
*/
function getHandler(dropped) {
return FS.EventHandlers.insertFiles(Collections.Images, {
metadata: function (fileObj) {
return {
owner: Meteor.userId(),
foo: "bar",
dropped: dropped
};
},
after: function (error, fileObj) {
if (!error) {
console.log("FileInserted", fileObj.name(), " | ", fileObj._id);
// alert("Inserted : " + fileObj._id);
var multiImage = Session.get('uploadMultiImages');
console.log( "FileInserted multi : ", this.multi);
if (multiImage) {
var uploadIds = Session.get('uploadedFileIds');
if (!uploadIds)
uploadIds = [];
uploadIds.push(fileObj._id);
Session.set('uploadedFileIds', uploadIds);
}
else {
Session.set('uploadedFileIds', fileObj._id);
}
}
}
});
}
Template.imagesArea.onCreated(function () {
delete Session.keys['uploadedFileIds'];
console.log("params : " , this.multi);
// console.log(Session.get('uploadedFileIds'))
});
Template.imagesArea.events({
'dropped .imageArea': getHandler(true),
'dropped .imageDropArea': getHandler(true),
'change input.images': getHandler(false)
});
Template.imagesArea.helpers({
curImageIds: function () {
return Session.get('uploadedFileIds');
}
});
\ No newline at end of file
<template name="thumbnail">
<!--{{this.id}}-->
{{#with thumb}}
<img src="{{this.url store='thumbs'}}" class="thumbnail"/>
{{/with}}
</template>
\ No newline at end of file
/**
* Created by chenhao on 15/10/13.
*/
Template.thumbnail.helpers({
thumb: function () {
// console.log("thumbnail", this.id);
if (this.id)
return Collections.Images.findOne({_id: this.id});
}
});
\ No newline at end of file
<template name="loading">
{{>spinner}}
</template>
\ No newline at end of file
<template name="vidoePlayer">
<video id="example_video_1" class="video-js vjs-default-skin vjs-big-play-centered"
controls width="640" height="264"
poster="{{video.thumbs}}">
<source type='video/flv' src="{{uurl}}"/>
</video>
</template>
\ No newline at end of file
Template.vidoePlayer.rendered = function () {
videojs("test.mp4",
{controls: true, autoplay: true, techOrder: ["flash", "html5"], preload: "auto"},
function () {
// Player (this) is initialized and ready.
});
};
Template.header.helpers({
uurl: function () {
// return "http://192.168.190.134:8080/";
return "test.mp4";
},
"video.thumbs": function () {
return "test.png";
}
});
\ No newline at end of file
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment