More mobile development

This commit is contained in:
Kenneth Jao 2017-02-26 19:27:50 -05:00
parent 77f8772be8
commit 485be935f3
9 changed files with 500 additions and 134 deletions

View File

@ -45,3 +45,5 @@ rochal:slimscroll
meteorhacks:sikka
mrt:jquery-ui
rajit:bootstrap3-datepicker
hammer:hammer@=2.0.4_1
velocityjs:velocityjs

View File

@ -48,6 +48,7 @@ fastclick@1.0.13
fortawesome:fontawesome@4.7.0
geojson-utils@1.0.10
google@1.1.15
hammer:hammer@2.0.4_1
hot-code-push@1.0.4
html-tools@1.0.11
htmljs@1.0.11
@ -127,6 +128,7 @@ tracker@1.1.1
ui@1.0.12
underscore@1.0.10
url@1.0.11
velocityjs:velocityjs@1.2.1
webapp@1.3.12
webapp-hashing@1.0.9
yogiben:admin-edit@1.2.8

View File

@ -1,6 +1,7 @@
<head>
<title>Hourglass</title>
<link rel="icon" href="/favicon.ico?v=2">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0"/>
</head>
<template name="main">
@ -68,7 +69,7 @@
</div>
<div class="overlay">
<div id="editWork" style="width:{{screen '.5'}};max-width:{{screen '.5'}}">
<div id="editWork" style="width:40%;max-width:40%">
<div id="editWorkCont" style="background-color:{{divColor 'mainColor'}};border-top:10px solid {{work 'typeColor'}};">
<div id="workInfoContainer">
{{#if inRole}}

View File

@ -279,22 +279,24 @@ Template.main.helpers({
},
inRole() { // Checks correct permissions.
if(Session.equals("currentWork",null)) return;
var thisWork = work.findOne({
_id: Session.get("currentWork")._id
});
if (Session.get("newWork")) {
return true;
} else {
if (thisWork === undefined) return;
var currClass = classes.findOne({
_id: thisWork["class"]
try {
var thisWork = work.findOne({
_id: Session.get("currentWork")._id
});
if (Meteor.userId() === thisWork.creator ||
Roles.userIsInRole(Meteor.userId(), ['superadmin', 'admin']) ||
currClass.moderators.indexOf(Meteor.userId()) !== -1 ||
currClass.banned.indexOf(Meteor.userId()) !== -1
) return true;
}
if (Session.get("newWork")) {
return true;
} else {
if (thisWork === undefined) return;
var currClass = classes.findOne({
_id: thisWork["class"]
});
if (Meteor.userId() === thisWork.creator ||
Roles.userIsInRole(Meteor.userId(), ['superadmin', 'admin']) ||
currClass.moderators.indexOf(Meteor.userId()) !== -1 ||
currClass.banned.indexOf(Meteor.userId()) !== -1
) return true;
}
} catch(err) {}
},
admin() {
return Roles.userIsInRole(Meteor.userId(), ['admin', 'superadmin']);
@ -615,7 +617,6 @@ Template.main.events({
serverData = Session.get("currentWork");
if(checkMissing()) return;
sendData("createWork");
Session.set("newWork",false);
$(".overlay").fadeOut(150);
},
'click #workDelete' () {
@ -780,88 +781,90 @@ function toDate(date) { // Turns formatted date back to Date constructor.
}
function formReadable(input, val) { // Makes work information readable by users.
switch (val) {
case "typeColor":
return input.typeColor = workColors[input.type];
case "name":
return input.name;
case "dueDate":
return getReadableDate(input.dueDate);
case "description":
return input.description;
case "type":
return input.type[0].toUpperCase() + input.type.slice(1);
case "comments":
var comments = input.comments;
var resort = [];
if (Session.get("newWork")) return []; // Don't display comments if user is creating work.
for (var k = 0; k < comments.length; k++) {
var re = comments.length - k - 1;
resort[re] = {
"comment": comments[k].comment,
"date": null,
"user": null,
"avatar": null,
"email": null
};
var user = Meteor.users.findOne({
_id: comments[k].user
});
resort[re].user = user.profile.name;
resort[re].date = moment(comments[k].date).fromNow();
resort[re].avatar = user.services.google.picture;
resort[re].email = user.services.google.email;
}
return resort;
case "done":
if (Session.get("newWork")) return [];
for (var i = 0; i < input.done.length; i++) { // Display users who marked as done.
var user = Meteor.users.findOne({
_id: input.done[i]
});
try {
switch (val) {
case "typeColor":
return input.typeColor = workColors[input.type];
case "name":
return input.name;
case "dueDate":
return getReadableDate(input.dueDate);
case "description":
return input.description;
case "type":
return input.type[0].toUpperCase() + input.type.slice(1);
case "comments":
var comments = input.comments;
var resort = [];
if (Session.get("newWork")) return []; // Don't display comments if user is creating work.
for (var k = 0; k < comments.length; k++) {
var re = comments.length - k - 1;
resort[re] = {
"comment": comments[k].comment,
"date": null,
"user": null,
"avatar": null,
"email": null
};
var user = Meteor.users.findOne({
_id: comments[k].user
});
resort[re].user = user.profile.name;
resort[re].date = moment(comments[k].date).fromNow();
resort[re].avatar = user.services.google.picture;
resort[re].email = user.services.google.email;
}
return resort;
case "done":
if (Session.get("newWork")) return [];
for (var i = 0; i < input.done.length; i++) { // Display users who marked as done.
var user = Meteor.users.findOne({
_id: input.done[i]
});
input.done[i] = {
"user": user.profile.name,
"avatar": user.services.google.picture,
"email": user.services.google.email
};
input.done[i] = {
"user": user.profile.name,
"avatar": user.services.google.picture,
"email": user.services.google.email
};
}
return input.done;
case "doneCol":
if (Session.get("newWork")) return "";
if (!_.contains(input.done, Meteor.userId())) return "";
return "#27A127";
case "doneText":
if (Session.get("newWork")) return "";
if (!_.contains(input.done, Meteor.userId())) return "Mark done";
return "Done!";
case "doneIcon":
if (Session.get("newWork")) return "";
if (!_.contains(input.done, Meteor.userId())) return "fa-square-o";
return "fa-check-square-o";
case "userConfirm":
if (!_.contains(input.confirmations, Meteor.userId())) return "";
return "#27A127";
case "confirmations":
return input.confirmations.length;
case "userReport":
if (!_.contains(input.reports, Meteor.userId())) return "";
return "#FF1A1A";
case "reports":
return input.reports.length;
case "email":
return Meteor.users.findOne({
_id: input.creator
}).services.google.email;
case "avatar":
return Meteor.users.findOne({
_id: input.creator
}).services.google.picture;
case "creator":
return Meteor.users.findOne({
_id: input.creator
}).profile.name;
}
return input.done;
case "doneCol":
if (Session.get("newWork")) return "";
if (!_.contains(input.done, Meteor.userId())) return "";
return "#27A127";
case "doneText":
if (Session.get("newWork")) return "";
if (!_.contains(input.done, Meteor.userId())) return "Mark done";
return "Done!";
case "doneIcon":
if (Session.get("newWork")) return "";
if (!_.contains(input.done, Meteor.userId())) return "fa-square-o";
return "fa-check-square-o";
case "userConfirm":
if (!_.contains(input.confirmations, Meteor.userId())) return "";
return "#27A127";
case "confirmations":
return input.confirmations.length;
case "userReport":
if (!_.contains(input.reports, Meteor.userId())) return "";
return "#FF1A1A";
case "reports":
return input.reports.length;
case "email":
return Meteor.users.findOne({
_id: input.creator
}).services.google.email;
case "avatar":
return Meteor.users.findOne({
_id: input.creator
}).services.google.picture;
case "creator":
return Meteor.users.findOne({
_id: input.creator
}).profile.name;
}
} catch(err){}
}
checkComplete = function(required, inputs) {

View File

@ -1,37 +1,204 @@
#mobileHeader {
box-shadow: -1px 2px 2px 1px #444;
position: relative;
}
#mobileHeader h2 {
font-size: 3vh;
font-weight: 200;
padding: 0;
height: 6vh;
height: 8vh;
display: inline;
line-height: 6vh;
line-height: 8vh;
text-align: right;
}
#mobileHeader .fa-bars {
height: 6vh;
margin-left: 1vh;
height: 8vh;
border: 0;
line-height: 8vh;
}
#mobileBody {
width: 100%;
height: 94vh;
height: 92vh;
margin-bottom: 10%;
background-color: #111;
overflow-y: auto;
overflow-x: hidden;
}
.mClassContainer {
border-bottom: 1px solid #AAA;
position: relative;
}
.mobileClass {
width: 100%;
height: 10vh;
border-bottom: 1px solid #AAA;
height: 20vw;
z-index: 30;
}
.mClassContainer:last-child {
margin-bottom: 20vw;
border: 0;
}
.mobileType {
width: 2vw;
height: 100%;
display: inline-block;
pointer-events: none;
}
.mobileClassContent {
padding: 2vh;
width: 88vw;
height: 12vw;
padding: 4vw;
display: inline-block;
vertical-align: top;
pointer-events: none;
}
.mobileClassContent h4 {
font-size: 3vh;
font-size: 5vw;
vertical-align: top;
}
.minorHeader {
font-size: 4vw !important;
width: 100%;
padding: 2%;
background-color: rgba(255,255,255,0.2);
}
.mContTop {
width: 100%;
}
.mName {
font-size: 4.3vw !important;
width: 50%;
}
.mDate {
font-size: 3.4vw !important;
width: 48%;
text-align: right;
}
.mClass {
font-size: 3.5vw !important;
width: 100%;
}
.mDesc {
font-size: 3.4vw !important;
color: #BBB;
}
#mAddWork {
width: 13vw;
height: 13vw;
position: absolute;
bottom: 6vw;
right: 6vw;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
background-color: rgb(255, 26, 26);
-webkit-filter: drop-shadow(3px 4px 4px #111);
filter: drop-shadow(3px 4px 4px #111);
}
#mAddWork .fa {
font-size: 5vw;
margin-left: 4.25vw;
margin-top: 4vw;
pointer-events: none
}
.mUndo {
font-size: 4vw;
width: 13vw;
height: 13vw;
background-color: rgb(17,17,17);
display: none;
opacity: 0;
position: absolute;
top: 3.75vw;
right: 4vw;
-moz-border-radius: 50%;
-webkit-border-radius: 50%;
border-radius: 50%;
}
.mUndo .fa {
width: 100%;
height: 50%;
text-align: center;
line-height: 9vw;
pointer-events: none;
}
.mUndo p {
font-size: 3vw;
width: 100%;
height: 50%;
margin: 0;
text-align: center;
pointer-events: none;
}
.mUndoText {
font-size: 4vw !important;
height: 13vw;
display: none;
opacity: 0;
position: absolute;
top: 3.75vw;
left: 8vw;
line-height: 13vw;
pointer-events: none;
}
#mOverlay {
height: 100%;
width: 100%;
background-color: rgba(0,0,0,0.3);
position: absolute;
top: 0;
left: 0;
}
#mSidebar {
height: 100%;
width: 85%;
background-color: #fff;
box-shadow: 2px 0px 1px 1px #222;
position: absolute;
top: 0;
left: 0;
}

View File

@ -1,18 +1,59 @@
<template name="mobile">
<div class="noScroll">
<header id="mobileHeader" style="background-color:{{divColor 'mainColor'}}">
<i class="fa fa-bars" style="color:{{iconStatus}}" aria-hidden="true"></i>
<h2>{{schoolName}}</h2>
</header>
<div id="mobileBody" style="background-color:{{divColor 'secondaryColor'}}">
{{#each myWork}}
<div class="mobileClass">
<div class="mobileType"></div>
<div class="mobileClassContent">
<h4>{{name}}</h4>
</div>
</div>
{{/each}}
</div>
</div>
<template name="mobile">
<div class="noScroll">
<header id="mobileHeader" style="background-color:{{divColor 'mainColor'}}">
<i class="fa fa-bars" style="color:{{iconStatus}}" aria-hidden="true"></i>
<h2>{{schoolName}}</h2>
</header>
<div id="mobileBody">
{{#if showMode 'main'}}
<h4 class="minorHeader">Main</h4>
{{#each myWork}}
{{> mobileClass}}
{{/each}}
{{/if}}
{{#if showMode 'done'}}
<h4 class="minorHeader">Done</h4>
{{#each myWork "done"}}
{{> mobileClass}}
{{/each}}
{{/if}}
{{#if showMode 'addWork'}}
{{/if}}
</div>
<div id="mAddWork">
<i class="fa fa-pencil" aria-hidden="true"></i>
</div>
<div id="mOverlay">
<div id="mSidebar">
<div class="mSectionTitle">
<div class="mStatus" style="background-color:{{modeStatus 'main'}}"></div>
<i class="fa fa-list-ul" aria-hidden="true"></i>
<h4>Create Classes</h4>
</div>
</div>
</div>
</div>
</template>
<template name="mobileClass">
<div class="mClassContainer" workid="{{_id}}">
{{#if isDone}}
<h4 class="mUndoText" workid="{{_id}}">Unmarked as done!</h4>
{{else}}
<h4 class="mUndoText" workid="{{_id}}">Marked as done!</h4>
{{/if}}
<div class="mUndo" workid="{{_id}}">
<i class="fa fa-undo" aria-hidden="true"></i>
<p>Undo</p>
</div>
<div class="mobileClass" style="background-color:{{divColor 'secondaryColor'}}" workid="{{_id}}">
<div class="mobileType" style="background-color:{{typeColor}}"></div>
<div class="mobileClassContent">
<h4 class="mName">{{name}}</h4>
<h4 class="mDate">{{dueDate}}</h4>
<h4 class="mClass">{{class}}</h4>
<h4 class="mDesc">{{shortdesc}}</h4>
</div>
</div>
</div>
</template>

View File

@ -1,26 +1,174 @@
Session.set("mobileWork", []);
Session.set("mobileMode", "main");
Template.mobile.rendered = function() {
document.getElementsByTagName("body")[0].style.color = Session.get("user").preferences.theme.textColor;
document.getElementsByTagName("body")[0].style.color = Session.get("user").preferences.theme.textColor;
addListeners();
addMobileButton($("#mAddWork")[0], 50, function() {
$("#mAddWork").velocity("fadeOut", 200);
$("#mobileBody").velocity("fadeOut", {
duration: 200,
complete: function() {
Session.set("mobileMode", "addWork");
$("#mobileBody").velocity("fadeIn", 200);
}
});
});
}
Template.mobile.helpers({
schoolName() { // Finds the name of the user's school.
if (Session.get("user").school === undefined || Session.get("user").school === null) return;
return Session.get("user").school;
},
if (Session.get("user").school === undefined || Session.get("user").school === null) return;
return Session.get("user").school;
},
iconStatus() {
return (Session.get("sidebarMode") === "mobile") ? Session.get("user").preferences.theme.iconHighlight : "";
},
myWork() {
myWork(done) {
var array = myClasses();
var allWork = [];
var notDoneWork = [];
var doneWork = [];
for(var i = 0; i < array.length; i++) {
for(var j = 0; j < array[i].thisClassWork.length; j++) {
allWork.push(array[i].thisClassWork[j]);
var classid = array[i].thisClassWork[j].classid;
var desc = array[i].thisClassWork[j].description;
if(desc) {
array[i].thisClassWork[j].shortdesc = (desc.length <= 40 ) ? desc : desc.substring(0,40) +"...";
}
array[i].thisClassWork[j]["class"] = (classid === Meteor.userId()) ? "Personal" : classes.findOne({_id:classid}).name;
if(_.contains(array[i].thisClassWork[j].done, Meteor.userId())) {
array[i].thisClassWork[j].isDone = true;
doneWork.push(array[i].thisClassWork[j]);
} else {
notDoneWork.push(array[i].thisClassWork[j]);
}
}
}
Session.set("mobileWork", allWork);
return allWork;
doneWork = doneWork.sort(function(a,b) {
return Date.parse(a.realDate) - Date.parse(b.realDate);
});
notDoneWork = notDoneWork.sort(function(a,b) {
return Date.parse(a.realDate) - Date.parse(b.realDate);
});
Session.set("mobileWork", [notDoneWork, doneWork]);
return (done === "done") ? Session.get("mobileWork")[1] : Session.get("mobileWork")[0];
},
showMode(mode) {
return Session.equals("mobileMode", mode);
},
modeStatus(mode) {
return (Session.equals("mobileMode", mode)) ? Session.get("user").preferences.theme.modeHighlight : "rgba(0,0,0,0)";
}
});
});
function addListeners() {
var deltaX = 0;
var clearTile;
for(var i = 0; i < $(".mClassContainer").length; i++) {
let id = $(".mClassContainer")[i].getAttribute("workid");
new Hammer($(".mobileClass")[i], {
domEvents: true
});
$(".mobileClass[workid="+id+"]").on('panmove', function(e) {
var dX = deltaX + (e.originalEvent.gesture.deltaX);
if(dX < 0) {
$.Velocity.hook(jQuery(e.target), 'translateX', dX/45 + 'px');
} else {
$.Velocity.hook(jQuery(e.target), 'translateX', dX + 'px');
}
});
$(".mobileClass[workid="+id+"]").on('panend', function(e) {
if(e.target === document.getElementById("mobileBody")) return;
deltaX = deltaX + (e.originalEvent.gesture.deltaX);
if(deltaX >= window.innerWidth * 0.5) {
deltaX = 0;
jQuery(e.target).velocity(
{
translateX: window.innerWidth*1.2+"px"
},
{
duration: 150,
complete: function() {
$(".mUndoText[workid="+id+"]").velocity("fadeIn", {duration: 300});
$(".mUndo[workid="+id+"]").velocity("fadeIn", {duration: 300});
var container = $(".mClassContainer[workid="+id+"]");
clearTile = setTimeout(function() {
container.velocity(
{
height: 0
},
{
duration: 200,
complete: function() {
serverData = [container[0].getAttribute("workid"), "done"];
sendData("toggleWork");
container.remove();
}
});
}, 3000);
}
});
} else {
deltaX = 0;
jQuery(e.target).velocity({translateX: "0px"},300);
}
});
addMobileButton($(".mUndo[workid="+id+"]")[0], 30, function() {
clearTimeout(clearTile);
$(".mobileClass[workid="+id+"]").velocity({translateX: "0px"},300);
$(".mUndoText[workid="+id+"]").velocity("fadeOut", {duration: 300});
$(".mUndo[workid="+id+"]").velocity("fadeOut", {duration: 300});
});
}
}
function addMobileButton(element, lighten, thisFunction) {
let button = new Hammer.Manager(element);
let add = lighten;
let ele = jQuery(element);
let execute = thisFunction;
let colors = [
parseFloat($.Velocity.hook(ele, "backgroundColorRed")),
parseFloat($.Velocity.hook(ele, "backgroundColorGreen")),
parseFloat($.Velocity.hook(ele, "backgroundColorBlue"))
];
var press = new Hammer.Press({
event: 'press',
pointers: 1,
time: 0.01,
threshold: 3000
});
button.add(press);
button.on('press', function(e) {
ele.velocity(
{
backgroundColorRed: colors[0] + add,
backgroundColorGreen: colors[1] + add,
backgroundColorBlue: colors[2] + add,
},100);
});
button.on('pressup', function(e) {
ele.velocity("stop");
ele.velocity(
{
backgroundColorRed: colors[0],
backgroundColorGreen: colors[1],
backgroundColorBlue: colors[2],
},
{
duration: 200,
complete: execute()
});
});
}

View File

@ -30,8 +30,8 @@ Router.route('/', {
this.redirect('/profile');
} else {
Session.set("user", Meteor.user().profile);
this.render("main");
//this.render("mobile");
//this.render("main");
this.render("mobile");
}
}
}

View File

@ -501,6 +501,7 @@ Meteor.methods({
// Work Functions
'createWork': function(input) {
input.creator = Meteor.userId();
if(input.description) input.description = input.description.trim();
work.schema.validate(input);
var found = classes.findOne({
_id: input.class
@ -522,6 +523,7 @@ Meteor.methods({
var currentclass = classes.findOne({
_id: currentwork.class
});
if(change.description) change.description = change.description.trim();
var security = securityCheck([[1, 16, 13, 5, false], 11, 12, 10, 20, true],
Object.assign({}, currentclass || {}, currentwork, {description: change.description, name: change.name, dueDate: change.dueDate, type: change.type}));
if (!security) {