diff --git a/hourglass/client/main/main.css b/hourglass/client/main/main.css index 91004c8..0dab4af 100644 --- a/hourglass/client/main/main.css +++ b/hourglass/client/main/main.css @@ -304,7 +304,7 @@ body { .mode h4 { width: 65%; - padding-left: 9%; + padding-left: 5%; line-height: 6vh; display: table-cell; vertical-align: middle; @@ -363,6 +363,7 @@ body { font-size: 1.7vh; padding: 5%; display: table-cell; + pointer-events: none; } .sideClassName { @@ -684,9 +685,9 @@ textarea.clickModify { } .optionText { - font-size: 120%; + font-size: 120% !important; min-width: 10%; - padding: 4% 7% 4% 7%; + padding: 4% 7% 4% 7% !important; margin: 0; box-shadow: inset 0 0 0 99999px rgba(0,0,0,0.05); @@ -798,23 +799,16 @@ textarea.clickModify { background-color: rgba(0,0,0,0.15); } -.overlay { +.overlay, #confirmOverlay { width: 100%; height: 100%; - background-color: rgba(0,0,0,0.5); + background-color: rgba(0,0,0,0.7); display: none; position: absolute; top: 0; left: 0; z-index: 51; - - opacity: 0; - - -webkit-transition: opacity 0.4s ease; - -moz-transition: opacity 0.4s ease; - -ms-transition: opacity 0.4s ease; - transition: opacity 0.4s ease; } .overlayCont { @@ -824,11 +818,12 @@ textarea.clickModify { margin: auto; margin-top: 25vh; padding: 1%; - box-shadow: 2px 2px 5px 3px #666; + + box-shadow: 2px 2px 5px 1px #666; - -moz-border-radius: 30px; - -webkit-border-radius: 30px; - border-radius: 30px; + -moz-border-radius: 3px; + -webkit-border-radius: 3px; + border-radius: 3px; text-align: center; } @@ -1230,13 +1225,6 @@ textarea.clickModify { top: 0.5vh; right: 1.5vw; z-index: 20; - - opacity: 0; - - -webkit-transition: opacity 0.4s ease; - -moz-transition: opacity 0.4s ease; - -ms-transition: opacity 0.4s ease; - transition: opacity 0.4s ease; } #userDropdownAvatar { diff --git a/hourglass/client/main/main.html b/hourglass/client/main/main.html index 20961ee..89ae751 100644 --- a/hourglass/client/main/main.html +++ b/hourglass/client/main/main.html @@ -60,6 +60,10 @@ {{#if currSettingMode 'addClass'}} {{> joinClass}} {{/if}} + + {{#if currSettingMode 'createClass'}} + {{> createClass}} + {{/if}} @@ -134,6 +138,16 @@ +
+
+

{{confirmText}}

+
+ + +
+
+
+
@@ -245,7 +259,7 @@ + + @@ -286,11 +355,25 @@
Private + {{#unless classInfo 'personal'}} + {{#if classInfo 'mine'}} +
+ +
+ {{/if}} + {{/unless}} {{else}}
Public + {{#unless classInfo 'personal'}} + {{#if classInfo 'mine'}} +
+ +
+ {{/if}} + {{/unless}} {{/if}}
@@ -312,8 +395,19 @@

Owner

{{> classInfoUserDisp classInfo 'admin'}} + {{#unless classInfo 'personal'}} + {{#if classInfo 'mine'}} + + {{/if}} + {{/unless}}
{{> classInfoCode classInfo 'code'}} + +
+ Change Admin: + +
Change
+
+ + + diff --git a/hourglass/client/menus/menus.js b/hourglass/client/menus/menus.js index b415580..fc7bfa2 100644 --- a/hourglass/client/menus/menus.js +++ b/hourglass/client/menus/menus.js @@ -1,13 +1,17 @@ +/* jshint esversion: 6 */ Session.set("settingMode", "manageClass"); Session.set("classInfoMode", "general"); +Session.set("notsearching", true); // If user isn't searching +Session.set("noclass", null); // If user doesn't have classes. +Session.set("notfound", null); // If no results for autocomplete. var filterOpen = [false, true, true, true, true]; var sidebarMode = [null,null]; -Template.sidebarMenuPlate.rendered = function(){$(".menuWrapper").slideDown(300);} -Template.sidebarOptionPlate.rendered = function(){$(".menuWrapper").slideDown(300);} -Template.sidebarRequestPlate.rendered = function(){$(".menuWrapper").slideDown(300);} -Template.sidebarCreatePlate.rendered = function(){$(".menuWrapper").slideDown(300);} +Template.sidebarMenuPlate.rendered = function(){$(".menuWrapper").slideDown(300);}; +Template.sidebarOptionPlate.rendered = function(){$(".menuWrapper").slideDown(300);}; +Template.sidebarRequestPlate.rendered = function(){$(".menuWrapper").slideDown(300);}; +Template.sidebarCreatePlate.rendered = function(){$(".menuWrapper").slideDown(300);}; Template.sidebarMenuPlate.helpers({ modeStatus(status) { // Color status of display modes. @@ -27,13 +31,13 @@ Template.sidebarMenuPlate.helpers({ }, filterOn() { return Session.get("classDisp").length !== 0 || Session.get("typeFilter").length !== 0; - }, + } }); Template.sidebarMenuPlate.events({ 'click .classes' () { // Click classes mode button. if (Session.equals("mode", "classes")) return; - toggleToMode("classes") + toggleToMode("classes"); setTimeout(startDragula, 500); toggleToSidebar(false); }, @@ -69,24 +73,14 @@ Template.sidebarMenuPlate.events({ }, // CLASS FILTERS 'click .sideClass' (event) { // Click class list in sidebar. - var div = event.target; - while (div.getAttribute("classid") === null) div = div.parentNode; - var classid = div.getAttribute("classid"); - - if (Session.equals("sidebarMode","create")) { // If creating work from calendar. - var newSetting = Session.get("currentWork"); - newSetting.class = classid; - Session.set("currentWork", newSetting); - openDivFade(document.getElementsByClassName("overlay")[0]); - } else { // Normal clicking turns on filter. - var array = Session.get("classDisp"); - if (array.indexOf(classid) !== -1) { - array.splice(array.indexOf(classid), 1); - } else { - array.push(classid); - } - Session.set("classDisp", array); + var classid = event.target.getAttribute("classid"); + var array = Session.get("classDisp"); + if (array.indexOf(classid) !== -1) { + array.splice(array.indexOf(classid), 1); + } else { + array.push(classid); } + Session.set("classDisp", array); }, 'click .sideFilter' (event) { var div = event.target; @@ -180,6 +174,16 @@ Template.sidebarOptionPlate.events({ } }); +Template.sidebarCreatePlate.events({ + 'click .sideClass' (event) { // Click class list in sidebar. + var classid = event.target.getAttribute("classid"); + var newSetting = Session.get("currentWork"); + newSetting.class = classid; + Session.set("currentWork", newSetting); + $(".overlay").fadeIn(250); + } +}); + Template.registerHelper("classInfo", (info) => { var thisClass = classes.findOne({_id:Session.get("classInfo")}); var isYou = Session.equals("classInfo", Meteor.userId()); @@ -197,7 +201,8 @@ Template.registerHelper("classInfo", (info) => { case "admin": return Meteor.users.findOne({_id: (isYou) ? Meteor.userId() : thisClass.admin}); case "code": - return (isYou || !thisClass.code) ? {exists: false} : {exists: true, code: thisClass.code}; + if(isYou) return {exists: false}; + return (isYou || Meteor.userId() !== this.admin) ? {exists: false} : {exists: true, code: Meteor.call('getCode', thisClass._id)}; case "mine": return (isYou) ? true : Meteor.userId() === thisClass.admin; case "moderators": @@ -233,13 +238,17 @@ Template.registerHelper("classInfo", (info) => { Template.registerHelper("classInfoMode", (mode, check) => { if(typeof check === "string") return Session.equals("classInfoMode",mode); return (Session.equals("classInfoMode", mode)) ? Session.get("user").preferences.theme.modeHighlight + ";background-color:rgba(0,0,0,0.1);" : "rgba(0,0,0,0)"; -}) +}); + +Template.registerHelper("classSelected", () => { + return !Session.equals("classInfo", null); +}); Template.manageClass.events({ 'click .classBox' (event) { var classId = event.target.getAttribute("classid"); if(Session.equals("classInfo",classId)) return; - toggleToClassInfo(classId); + toggleToClassInfo(classId); }, 'click #classInfoModeWrapper span:first-child' () { if(Session.equals("classInfoMode","general")) return; @@ -248,6 +257,44 @@ Template.manageClass.events({ 'click #classInfoModeWrapper span:last-child' () { if(Session.equals("classInfoMode","users")) return; toggleToClassInfoMode("users"); + }, + 'click .infoCard .fa-pencil-square-o' () { + $("#changeAdminWrapper").fadeIn(250); + }, + 'click #adminSubmit' () { + var input = document.getElementById("changeAdmin"); + var value = input.value; + var classid = Session.get("classInfo"); + var user = Meteor.users.findOne({ + "services.google.email": value + }); + if(!user) { + sAlert.error("Invalid email!", { + effect: 'stackslide', + position: 'top', + timeout: 3000 + }); + return; + } + serverData = [ + user._id, + classid + ]; + Session.set("confirmText", "Change ownership?"); + confirm = "changeAdmin"; + $("#confirmOverlay").fadeIn(250); + }, + 'click #deleteClass' () { + serverData = Session.get("classInfo"); + confirm = "deleteClass"; + Session.set("confirmText", "Delete this class?"); + $("#confirmOverlay").fadeIn(250); + }, + 'click .classBox .fa-times' (event) { + serverData = event.target.parentNode.getAttribute("classid"); + confirm = "leaveClass"; + Session.set("confirmText", "Leave this class?"); + $("#confirmOverlay").fadeIn(250); } }); @@ -264,7 +311,9 @@ Template.joinClass.helpers({ ).fetch(); for (var i = 0; i < array.length; i++) { + array[i].join = true; array[i].subscribers = array[i].subscribers.length; + array[i].teachershort = array[i].teacher.split(" ").slice(1).reduce(function(a,b) { return a+ " " + b;}); } if (array.length === 0) { Session.set("noclass", true); @@ -308,14 +357,14 @@ Template.joinClass.helpers({ }, notfound() { // Returns if autocomplete has no results. return Session.get("notfound"); - }, + } }); Template.joinClass.events({ 'click .classBox' (event) { var classId = event.target.getAttribute("classid"); if(Session.equals("classInfo",classId)) return; - toggleToClassInfo(classId); + toggleToClassInfo(classId); }, 'click #classInfoModeWrapper span:first-child' () { if(Session.equals("classInfoMode","general")) return; @@ -352,9 +401,110 @@ Template.joinClass.events({ }); } Session.set("autocompleteDivs", divs.sort(function(a, b) { - return b.subscribers - a.subscribers + return b.subscribers - a.subscribers; })); } catch (err) {} + }, + 'click .classBox .fa-plus' (event) { + serverData = [event.target.parentNode.getAttribute("classid"), ""]; + confirm = "joinClass"; + Session.set("confirmText", "Join this class?"); + $("#confirmOverlay").fadeIn(250); + }, + 'click #private' () { + $("#privateCode").css('display','inline-block'); + var input = document.getElementById("privateCode"); + input.focus(); + if(input.value === "") return; + Meteor.call("joinPrivateClass", input.value, function(error, result) { + if(result) { + sAlert.success("Joined!", { + effect: 'genie', + position: 'bottom-right', + timeout: 1500 + }); + } else { + sAlert.error("Invalid code!", { + effect: 'stackslide', + position: 'top', + timeout: 1500 + }); + } + }); + } +}); + +Template.createClass.helpers({ + schoolComplete() { // Returns autocomplete array for schools. + return { + position: "bottom", + limit: 6, + rules: [{ + token: '', + collection: schools, + field: 'name', + matchAll: true, + template: Template.schoolList + }] + }; + }, + teacherComplete() { // Returns autocomplete array for teachers. + return { + position: "bottom", + limit: 1, + rules: [{ + token: '', + collection: teachers, + field: 'name', + template: Template.teacherList + }] + }; + } +}); + +Template.createClass.events({ + 'click #creSubmit' () { + var inputs = document.getElementsByClassName("creInput"); + var values = {}; + var required = ["school","name","privacy","category"]; + var no = []; + for(var i = 0; i < inputs.length; i++) { + var val = inputs[i].value; + var where = inputs[i].getAttribute("form"); + if(val === "" && _.contains(required, where)) { + no.push(where); + } + values[where] = val; + } + console.log(values); + console.log(no); + if(no.length > 0) { // Check missing fields. + sAlert.error("Missing " + no.reduce(function(a,b) { return (b === no[no.length-1]) ? a + ", and " + b : a + ", " + b;}), { + effect: 'stackslide', + position: 'top', + timeout: 3000 + }); + return; + } + values.privacy = (values.privacy === "Public") ? false : true; + values.status = false; + values.category.toLowerCase(); + values.code = ""; + serverData = values; + if(!teachers.findOne({name: values.teacher})) { + Meteor.call("createTeacher", values.teacher, values.school, function(error,result) { + if (error !== undefined) { + sAlert.error(error.message, { + effect: 'stackslide', + position: 'top' + }); + } else { + sendData("createClass"); + } + }); + } else { + sendData("createClass"); + } } }); @@ -385,7 +535,7 @@ Template.classInfoUsers.events({ input.value = ""; }, 'click .userDisp .fa' (event) { - var outerInput = event.target.parentNode.parentNode.parentNode.parentNode.childNodes[1] + var outerInput = event.target.parentNode.parentNode.parentNode.parentNode.childNodes[1]; var type = outerInput.childNodes[6].getAttribute("user"); var userid = event.target.parentNode.parentNode.getAttribute("userid"); if(!Meteor.users.findOne({_id: userid})) { @@ -421,10 +571,11 @@ Template.classInfoCode.events({ toggleToMode = function(mode) { $("#mainBody").fadeOut(250, function() { - (Session.equals("sidebarMode", "option")) ? Session.set("settingMode", mode) : Session.set("mode",mode); + (Session.equals("sidebarMode", "option")) ? Session.set("settingMode", mode) : Session.set("mode", mode); + Session.set("classInfo", null); $("#mainBody").fadeIn(250); }); -} +}; toggleToSidebar = function(sidebar) { try { @@ -442,19 +593,20 @@ toggleToSidebar = function(sidebar) { Session.set("sidebarMode", sidebar); }); } -} +}; toggleToClassInfo = function(classId) { + $("#changeAdminWrapper").fadeOut(250); $("#infoClassCont").fadeOut(250, function() { Session.set("classInfo", classId); Session.set("classInfoMode", "general"); $(this).fadeIn(250); }); -} +}; toggleToClassInfoMode = function(mode) { $("#infoClassCont").fadeOut(250, function() { Session.set("classInfoMode", mode); $(this).fadeIn(250); - }); -} + }); +}; diff --git a/hourglass/collections/main.js b/hourglass/collections/main.js index 697e2c6..c52e70a 100644 --- a/hourglass/collections/main.js +++ b/hourglass/collections/main.js @@ -1,14 +1,21 @@ schools = new Mongo.Collection("Schools"); +teachers = new Mongo.Collection("Teachers"); classes = new Mongo.Collection("Classes"); work = new Mongo.Collection("Work"); requests = new Mongo.Collection("Requests"); admins = Meteor.users; -teachers = new Mongo.Collection("Teachers"); schools.schema = new SimpleSchema({ name: {type: String} }); +teachers.schema = new SimpleSchema({ + name: {type: String}, + school: {type: String}, + creator: {type: String, optional: true} + +}); + classes.schema = new SimpleSchema({ school: {type: String}, //icon: {type: String}, @@ -63,6 +70,7 @@ teachers.schema = new SimpleSchema({ }); schools.attachSchema(schools.schema); +teachers.attachSchema(teachers.schema); classes.attachSchema(classes.schema); work.attachSchema(work.schema); requests.attachSchema(requests.schema); diff --git a/hourglass/lib/adminConfig.js b/hourglass/lib/adminConfig.js index bef4086..82fb376 100644 --- a/hourglass/lib/adminConfig.js +++ b/hourglass/lib/adminConfig.js @@ -10,6 +10,16 @@ AdminConfig = { color: 'red', label: 'Schools' }, + teachers: { + icon: 'book', + tableColumns: [ + { label: 'ID', name: '_id' }, + { label: 'Name', name: 'name' }, + { label: 'School', name: 'school'} + ], + color: 'orange', + label: 'Teachers' + }, classes: { icon: 'graduation-cap', tableColumns: [ diff --git a/hourglass/lib/constants.js b/hourglass/lib/constants.js index c21d2ac..03bc54a 100644 --- a/hourglass/lib/constants.js +++ b/hourglass/lib/constants.js @@ -1,9 +1,9 @@ themeColors = { "lux": { "background": "White.jpg", - "mainColor": "#EEE", - "secondaryColor": "#FEFEFE", - "sidebarColor": "#799cb8", + "mainColor": "#DBDBDB", + "secondaryColor": "#E8E8E8", + "sidebarColor": "#799CB8", "userDropdownColor": "#E6E6E6", "iconHighlight": "#FFF", "modeHighlight": "#D34949", @@ -28,8 +28,8 @@ themeColors = { "sidebarColor": "#327C5A", "userDropdownColor": "#CC3333", "iconHighlight": "#327C5A", - "modeHighlight": "#C9fE62", - "classCardColor":"#302c36", + "modeHighlight": "#C9FE62", + "classCardColor":"#302C36", "textColor": "#FCF0F0" }, "aequor": { @@ -50,7 +50,7 @@ themeColors = { "sidebarColor": "#6D9957", "userDropdownColor": "#89BB52", "iconHighlight": "#91EE61", - "modeHighlight": "#B9f643", + "modeHighlight": "#B9F643", "classCardColor":"#C18311", "textColor": "#FCF0F0" }, @@ -121,4 +121,5 @@ options = { ] } -serverData = null; \ No newline at end of file +serverData = null; +confirm = null; \ No newline at end of file diff --git a/hourglass/lib/router.js b/hourglass/lib/router.js index 6f7167e..50de376 100644 --- a/hourglass/lib/router.js +++ b/hourglass/lib/router.js @@ -6,6 +6,7 @@ Router.route('/', { return [ Meteor.subscribe('classes', this.params._id), Meteor.subscribe('schools', this.params._id), + Meteor.subscribe('teachers', this.params._id), Meteor.subscribe('work', this.params._id), Meteor.subscribe('requests', this.params._id), Meteor.subscribe('users', this.params._id) @@ -45,6 +46,7 @@ Router.route('/profile', { return [ Meteor.subscribe('classes', this.params._id), Meteor.subscribe('schools', this.params._id), + Meteor.subscribe('teachers', this.params._id), Meteor.subscribe('work', this.params._id), Meteor.subscribe('requests', this.params._id), Meteor.subscribe('users', this.params._id) @@ -57,30 +59,6 @@ Router.route('/profile', { } }); -Router.route('/user/:email', { - data: function() { - return Meteor.users.findOne({'services.google.email': this.params.email}); - }, - waitOn: function() { - return [ - Meteor.subscribe('users', this.params._id) - ]; - }, - action: function() { - if(Meteor.users.findOne({'services.google.email': this.params.email}) !== undefined) { - if(Meteor.user() && this.params.email === Meteor.user().services.google.email) { - Session.set("user", Meteor.user().profile); - this.redirect('/profile'); - } else { - Session.set("user", Meteor.user().profile); - this.render('user'); - } - } else { - this.render("NotFound"); - } - } -}); - Router.configure({ notFoundTemplate: "NotFound" }); diff --git a/hourglass/server/main.js b/hourglass/server/main.js index efa059c..9ad9433 100644 --- a/hourglass/server/main.js +++ b/hourglass/server/main.js @@ -25,6 +25,10 @@ Meteor.publish('schools', function() { return schools.find(); }); +Meteor.publish('teachers', function() { + return teachers.find({}, {fields: {name: 1, school: 1}}); +}); + // Returns the code for classes (for debug) Meteor.publish('classes', function() { @@ -125,15 +129,15 @@ Meteor.publish('users', function() { } }); + // Allows only superadmins to edit collections from client Security.permit(['insert', 'update', 'remove']).collections([schools, classes, work]).ifHasRole('superadmin'); -Accounts.validateLoginAttempt(function(info) { - var user = info.user; +// Accounts.validateLoginAttempt(function(info) { +// var user = info.user; +// if(user.banned) throw new Meteor.Error(403, 'You are banned'); - if(user.isBanned) throw new Meteor.Error(403, 'You are banned'); - -}); +// }); var errors = [ @@ -156,7 +160,8 @@ var errors = [ ["trivial", "This request is too long."], ["trivial", "Not a valid work type"], ["unauthorized", "This class has not been approved yet"], - ["matching", "This teacher already exists"], + ["matching", "This teacher already exists!"], + ["trivial", "Please put the full name!"], // 20 ["unauthorized", "Sorry, you are not authorized to complete this action."], // n - 2 ["other", "Error could not be processed"] // n - 1 @@ -192,7 +197,7 @@ function securityCheck(checklist, input) { break; // Duplicate classes case 4: - if (classes.findOne({school: input.school, status: true, privacy: false, teacher: input.teacher, hour: input.hour}) || (input.teacher === "" && input.hour === "")) error = 3; + if (classes.findOne({school: input.school, teacher: input.teacher, status: true, privacy: false, hour: input.hour}) || (input.teacher === "" && input.hour === "")) error = 3; break; // Class admin case 5: @@ -275,12 +280,16 @@ function securityCheck(checklist, input) { break; // New Teacher doesn't already exist case 26: - if (teachers.find({name: input.teacherName, school: input.school}).fetch().length > 0) error = 19; + if (teachers.find({name: input.teachername, school: input.school}).fetch().length > 0) error = 19; break; - // Not banning admin + // Not banning admin case 27: if (Roles.userIsInRole(input.userId, ['superadmin', 'admin'])) error = errors.length - 2; break; + // Incorrect teacher format + case 28: + if (input.teachername.split(" ").length < 2) error = 20; + break; } results.push(error); } @@ -332,7 +341,6 @@ Meteor.methods({ throw new Meteor.Error(errors[security]); } }, - // Class Functions 'createClass': function(input) { classes.schema.validate(input); @@ -350,7 +358,6 @@ Meteor.methods({ input.subscribers = []; input.moderators = []; input.banned = []; - classes.insert(input, function(err, result) { Meteor.call('joinClass', [result, input.code]); }); @@ -726,9 +733,11 @@ Meteor.methods({ var current = Meteor.user().profile; var index = current.classes.indexOf(change); if (index >= 0) { + console.log("hi"); if (classes.findOne({ _id: change }).admin != Meteor.userId()) { + console.log("f"); current.classes.splice(index, 1); Meteor.users.update({ _id: Meteor.userId() @@ -799,11 +808,13 @@ Meteor.methods({ } }, 'createTeacher': function(teacherName, schoolName) { - var security = securityCheck([26, 3, true], {teachername: teacherName, school: schoolName}); + teachers.schema.validate({name: teacherName, school: schoolName}); + var security = securityCheck([26, 28, 3, true], {teachername: teacherName, school: schoolName}); if (!security) { teachers.insert({ name: teacherName, - school: schoolName + school: schoolName, + creator: Meteor.userId() }); } else { throw new Meteor.Error(errors[security]);