diff --git a/.travis.yml b/.travis.yml
index a59245d..0df39ec 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,6 +3,13 @@ language: node_js
node_js:
- "0.10"
+before_install: npm install -g grunt-cli
+
+install: npm install
+
+script: grunt test
+
branches:
only:
- - develop
\ No newline at end of file
+ - develop
+ - master
diff --git a/api/controllers/PageController.js b/api/controllers/PageController.js
new file mode 100644
index 0000000..aeec543
--- /dev/null
+++ b/api/controllers/PageController.js
@@ -0,0 +1,84 @@
+/**
+ * PageController
+ *
+ * @description :: Server-side logic for managing pages
+ * @help :: See http://links.sailsjs.org/docs/controllers
+ * https://github.com/irlnathan/activityoverlord20/blob/master/api/controllers/PageController.js
+ */
+
+module.exports = {
+
+ showHomePage: function (req, res) {
+
+ // If not logged in, show the public view.
+ if (!req.session.me) {
+ //return res.view('homepage');
+ return res.view('homepage', {
+ me: [],
+ video: []
+ });
+ }
+
+ // Otherwise, look up the logged-in user and show the logged-in view,
+ // bootstrapping basic user data in the HTML sent from the server
+ User.findOne(req.session.me, function (err, user){
+ if (err) {
+ return res.negotiate(err);
+ }
+
+ if (!user) {
+ sails.log.verbose('Session refers to a user who no longer exists- did you delete a user, then try to refresh the page with an open tab logged-in as that user?');
+ //return res.view('homepage');
+ return res.view('homepage', {
+ me: [],
+ video: []
+ });
+ }
+
+ return res.view('dashboard', {
+ me: user,
+ video: []
+ });
+
+ });
+ },
+
+ showEditPage: function (req, res) {
+
+ // If not logged in, show the public view.
+ if (!req.session.me) {
+ return res.view('homepage');
+ }
+
+ // Otherwise, look up the logged-in user and show the logged-in view,
+ // bootstrapping basic user data in the HTML sent from the server
+ User.findOne(req.session.me, function (err, user){
+ if (err) {
+ return res.negotiate(err);
+ }
+
+ if (!user) {
+ sails.log.verbose('Session refers to a user who no longer exists- did you delete a user, then try to refresh the page with an open tab logged-in as that user?');
+ return res.view('homepage', {
+ me: [],
+ video: []
+ });
+ }
+
+ // retreive the video object using the id
+ Video.findOne(req.param('id'), function(err, video){
+ if (err) {
+ // return error
+ }
+
+ // if successful, return user and video object to frontend
+ return res.view('edit', {
+ me: user,
+ video: video
+ });
+
+ });
+ });
+ }
+
+};
diff --git a/api/controllers/UserController.js b/api/controllers/UserController.js
index 1a958c4..c2c4e7a 100644
--- a/api/controllers/UserController.js
+++ b/api/controllers/UserController.js
@@ -12,6 +12,8 @@ module.exports = {
/**
* `UserController.login()`
+ * Usage: POST /api/user/login
+ * Content: {username: ':username', password: ':password'}
*/
login: function (req, res) {
// Look for user using given username
@@ -49,19 +51,73 @@ module.exports = {
/**
* `UserController.signup()`
+ * Usage: POST /api/user/signup
+ * Content: {username: ':username', password: ':password', email: ':emailaddress'}
*/
signup: function (req, res) {
- return res.json({
- todo: 'signup() is not implemented yet!'
+ Passwords.encryptPassword({
+ // Encrypt with BCrypt algo
+ password: req.param('password'),
+ difficulty: 10,
+ }).exec({
+ error: function(err) {
+ return res.negotiate(err);
+ },
+
+ success: function(encryptedPassword) {
+ User.create({
+ username: req.param('username'),
+ encryptedPassword: encryptedPassword,
+ email: req.param('email')
+ }).exec(function(err, newUser) {
+ if (err) {
+ console.log("err: ", err);
+ console.log("err.invalidAttributes: ", err.invalidAttributes);
+
+ // If this is a uniqueness error about the email attribute,
+ // send back an easily parseable status code.
+ if (err.invalidAttributes && err.invalidAttributes.email && err.invalidAttributes.email[0]
+ && err.invalidAttributes.email[0].rule === 'unique') {
+ return res.emailAddressInUse();
+ }
+
+ return res.negotitate(err);
+ }
+
+ // Log user in
+ req.session.me = newUser.id;
+
+ // Send back the id of the new user
+ return res.json({
+ id: newUser.id
+ });
+ });
+ },
});
},
/**
* `UserController.logout()`
+ * Usage: GET /api/user/logout
*/
logout: function (req, res) {
- return res.json({
- todo: 'logout() is not implemented yet!'
+ // Look up the user record from the database which is
+ // referenced by the id in the user session (req.session.me)
+ User.findOne(req.session.me, function foundUser(err, user) {
+ if (err) return res.negotiate(err);
+
+ // If session refers to a user who no longer exists, still allow logout.
+ if (!user) {
+ sails.log.verbose('Session refers to a user who no longer exists.');
+ return res.backToHomePage();
+ }
+
+ // Wipe out the session (log out)
+ req.session.me = null;
+
+ // Either send a 200 OK or redirect to the home page
+ return res.backToHomePage();
+
});
},
diff --git a/api/controllers/VideoController.js b/api/controllers/VideoController.js
index 61ab520..e67e2b5 100644
--- a/api/controllers/VideoController.js
+++ b/api/controllers/VideoController.js
@@ -50,7 +50,6 @@ module.exports = {
Video.destroy({
id: req.param('id')
}).exec(function (err, video) {
- console.log(req.body.videoId);
if (err) throw err;
res.json(video);
});
diff --git a/api/models/User.js b/api/models/User.js
index 5cff32e..281e76e 100644
--- a/api/models/User.js
+++ b/api/models/User.js
@@ -20,11 +20,22 @@ module.exports = {
columnName: 'encrypted_password'
},
+ lastLoggedIn: {
+ type: 'datetime',
+ defaultsTo: function() {return new Date(); }
+ },
+
+ email: {
+ type: 'string',
+ email: true,
+ required: true,
+ unique: true
+ },
+
// 0 for admin, 1 for normal user
permission: {
type: 'integer',
- defaultTo: 1,
- required: true
+ defaultsTo: 1
}
},
}
diff --git a/api/models/Video.js b/api/models/Video.js
index 913e1f2..347d1eb 100644
--- a/api/models/Video.js
+++ b/api/models/Video.js
@@ -32,7 +32,6 @@ module.exports = {
// 0 for self only, 1 for public
privacy: {
type: 'integer',
- required: true,
defaultsTo: 1
},
diff --git a/api/policies/sessionAuth.js b/api/policies/sessionAuth.js
index d305168..fe3605b 100644
--- a/api/policies/sessionAuth.js
+++ b/api/policies/sessionAuth.js
@@ -11,7 +11,7 @@ module.exports = function(req, res, next) {
// User is allowed, proceed to the next policy,
// or if this is the last policy, the controller
- if (req.session.authenticated) {
+ if (req.session.me) {
return next();
}
diff --git a/api/responses/backToHomePage.js b/api/responses/backToHomePage.js
new file mode 100644
index 0000000..4187de5
--- /dev/null
+++ b/api/responses/backToHomePage.js
@@ -0,0 +1,22 @@
+/**
+ * Usage:
+ * res.backToHomePage(); // (default to 200 "OK" status code)
+ * res.backToHomePage(400);
+ *
+ */
+
+module.exports = function backToHomePage (statusCode){
+
+ // Get access to `req` and `res`
+ // (since the arguments are up to us)
+ var req = this.req;
+ var res = this.res;
+
+ // All done- either send back an empty response w/ just the status code
+ // (e.g. for AJAX requests)
+ if (req.wantsJSON) {
+ return res.send(statusCode||200);
+ }
+ // or redirect to the home page
+ return res.redirect('/');
+};
\ No newline at end of file
diff --git a/api/responses/emailAddressInUse.js b/api/responses/emailAddressInUse.js
new file mode 100644
index 0000000..d669817
--- /dev/null
+++ b/api/responses/emailAddressInUse.js
@@ -0,0 +1,17 @@
+/**
+ * 409 (Conflict) Handler
+ *
+ * Usage:
+ * res.emailAddressInUse();
+ *
+ * @reference: https://github.com/irlnathan/activityoverlord20/blob/master/api/responses/emailAddressInUse.js
+ */
+
+module.exports = function emailAddressInUse (){
+
+ // Get access to `res`
+ // (since the arguments are up to us)
+ var res = this.res;
+
+ return res.send(409, 'Email address is already taken by another user.');
+};
\ No newline at end of file
diff --git a/assets/images/zoomable_logo_black.svg b/assets/images/zoomable_logo_black.svg
new file mode 100644
index 0000000..84b2fe8
--- /dev/null
+++ b/assets/images/zoomable_logo_black.svg
@@ -0,0 +1,79 @@
+
+
+
+
diff --git a/assets/index.html b/assets/index.html
deleted file mode 100644
index 9d713b2..0000000
--- a/assets/index.html
+++ /dev/null
@@ -1,29 +0,0 @@
-
-
-
-
-
-
-
-
-
-
- Zoomable
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/assets/js/public/appRoutes.js b/assets/js/public/appRoutes.js
deleted file mode 100644
index 27d98bc..0000000
--- a/assets/js/public/appRoutes.js
+++ /dev/null
@@ -1,23 +0,0 @@
-angular.module('appRoutes', []).config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
-
- // Redirect unmatched URL to login state
- $urlRouterProvider.otherwise("/");
-
- $stateProvider
- .state('login', {
- url: "/",
- templateUrl: "views/login.html",
- controller: 'loginController'
- })
- .state('dashboard', {
- url: "/dashboard",
- templateUrl: "views/dashboard.html",
- controller: 'dashboardController'
- })
- .state('edit', {
- url: "/edit/:videoId",
- templateUrl: "views/edit.html",
- controller: 'editController'
- });
-
-}]);
diff --git a/assets/js/public/controllers/dashboardController.js b/assets/js/public/controllers/dashboardController.js
index f4ab573..09015ed 100644
--- a/assets/js/public/controllers/dashboardController.js
+++ b/assets/js/public/controllers/dashboardController.js
@@ -1,10 +1,4 @@
-angular.module('zoomableApp').controller('dashboardController', function($scope, servicesAPI){
-
- // MESSAGES
- $scope.MESSAGE_MY_VIDEOS = 'My Videos';
- $scope.MESSAGE_VIEWS = 'Views';
- $scope.MESSAGE_COUNT_ZERO = '0';
- $scope.MESSAGE_ERROR_NO_VIDEO = 'No Video Yet';
+angular.module('zoomableApp').controller('dashboardController', function($scope, servicesAPI, $mdDialog, $mdMedia){
// VARIABLES
$scope.defaultImagePath = 'images/bunny.png';
@@ -12,27 +6,20 @@ angular.module('zoomableApp').controller('dashboardController', function($scope,
$scope.iconTagPath = 'images/ic_tag_black_24px.svg';
$scope.iconViewPath = 'images/ic_remove_red_eye_black_24px.svg';
$scope.filterStates = ['Newest','Popular','Public','Private'];
- $scope.buttonStates = ['Private','Public','Delete'];
+ $scope.buttonStates = ['Public','Private','Delete'];
$scope.userFilterState = '';
$scope.userButtonState = '';
+ var PUBLIC = 0;
+ var PRIVATE = 1;
+ var DELETE = 2;
+ var videoData = {};
+ var uploadUrl = '/upload';
$scope.model = {
selectedVideoList : []
}
- /* Get video object */
- servicesAPI.get()
- .success(function(data) {
- $scope.videoList = data;
- })
- .error(function(data) {
- console.log('Error: ' + data);
- });
-
- /* Button Handler */
- $scope.toggleButton = function(buttonId) {
- $scope.userButtonState = buttonId;
- };
+ getVideoList();
/* Checkbox Handler */
$scope.isSelectAll = function() {
@@ -60,4 +47,135 @@ angular.module('zoomableApp').controller('dashboardController', function($scope,
}
}
+ /* Dialog Handler */
+ $scope.showConfirm = function(ev, buttonState) {
+ // Check if at least 1 video is checked
+ if($scope.model.selectedVideoList.length > 0) {
+
+ $scope.userButtonState = buttonState;
+ var MESSAGE_VIDEO = 'videos';
+
+ // Check plural for confirm dialog text
+ if($scope.model.selectedVideoList.length === 1) {
+ MESSAGE_VIDEO = MESSAGE_VIDEO.substring(0, MESSAGE_VIDEO.length - 1);
+ }
+
+ // DIALOGUE MESSAGES
+ var MESSAGE_TITLE_PRIVATE = 'Make Video Private?';
+ var MESSAGE_TITLE_PUBLIC = 'Make Video Public?';
+ var MESSAGE_TITLE_DELETE = 'Delete Video?';
+ var MESSAGE_TEXT_CONTENT_PRIVATE = 'Are you sure you want to set ' + $scope.model.selectedVideoList.length + ' ' + MESSAGE_VIDEO + ' to Private?';
+ var MESSAGE_TEXT_CONTENT_PUBLIC = 'Are you sure you want to set ' + $scope.model.selectedVideoList.length + ' ' + MESSAGE_VIDEO + ' to Public?';
+ var MESSAGE_TEXT_CONTENT_DELETE = 'Are you sure you want to delete ' + $scope.model.selectedVideoList.length + ' ' + MESSAGE_VIDEO + '?';
+
+ var MESSAGE_TITLE = '';
+ var MESSAGE_TEXT_CONTENT = '';
+
+ if(buttonState === $scope.buttonStates[PRIVATE]) {
+ MESSAGE_TITLE = MESSAGE_TITLE_PRIVATE;
+ MESSAGE_TEXT_CONTENT = MESSAGE_TEXT_CONTENT_PRIVATE;
+ } else if (buttonState === $scope.buttonStates[PUBLIC]) {
+ MESSAGE_TITLE = MESSAGE_TITLE_PUBLIC;
+ MESSAGE_TEXT_CONTENT = MESSAGE_TEXT_CONTENT_PUBLIC;
+ } else if (buttonState === $scope.buttonStates[DELETE]) {
+ MESSAGE_TITLE = MESSAGE_TITLE_DELETE;
+ MESSAGE_TEXT_CONTENT = MESSAGE_TEXT_CONTENT_DELETE;
+ }
+
+ // Appending dialog to document.body to cover sidenav in docs app
+ var confirm = $mdDialog.confirm()
+ .title(MESSAGE_TITLE)
+ .textContent(MESSAGE_TEXT_CONTENT)
+ .ariaLabel('Confirm Dialog')
+ .targetEvent(ev)
+ .ok('Confirm')
+ .cancel('Cancel');
+ $mdDialog.show(confirm).then(function() {
+ if(buttonState === $scope.buttonStates[PRIVATE]) {
+ for(var i=0;i<$scope.model.selectedVideoList.length;i++) {
+ servicesAPI.update($scope.model.selectedVideoList[i], {privacy: PRIVATE}).then(function() {
+ getVideoList();
+ });
+ }
+ } else if (buttonState === $scope.buttonStates[PUBLIC]) {
+ for(var i=0;i<$scope.model.selectedVideoList.length;i++) {
+ servicesAPI.update($scope.model.selectedVideoList[i], {privacy: PUBLIC}).then(function() {
+ getVideoList();
+ });
+ }
+ } else if (buttonState === $scope.buttonStates[DELETE]) {
+ for(var i=0;i<$scope.model.selectedVideoList.length;i++) {
+ servicesAPI.delete($scope.model.selectedVideoList[i]).then(function() {
+ getVideoList();
+ });
+ }
+ }
+ // Empty video list
+ $scope.model.selectedVideoList = [];
+ });
+
+ } else {
+ return;
+ }
+ };
+
+ /* Sort video list according to filter states */
+ $scope.updateFilterState = function (state) {
+ $scope.userFilterState = state;
+ if ($scope.userFilterState === 'Newest') {
+ $scope.sortType = '-createdAt';
+ } else if ($scope.userFilterState === 'Popular') {
+ $scope.sortType = '-views';
+ } else if ($scope.userFilterState === 'Public') {
+ $scope.sortType = 'privacy';
+ } else if ($scope.userFilterState === 'Private') {
+ $scope.sortType = '-privacy';
+ }
+ };
+
+ /* GET Video Object */
+ function getVideoList() {
+ servicesAPI.get()
+ .success(function(data) {
+ $scope.videoList = data;
+ })
+ .error(function(data) {
+ console.log('Error: ' + data);
+ });
+ }
+
+ /* To display confirmation dialog */
+ function DialogController($scope, $mdDialog) {
+ $scope.hide = function() {
+ $mdDialog.hide();
+ };
+ $scope.cancel = function() {
+ $mdDialog.cancel();
+ };
+ $scope.answer = function(answer) {
+ $mdDialog.hide(answer);
+ };
+ }
+
+ $scope.uploadVideoFile = function (filelist) {
+ for (var i = 0; i < filelist.length; ++i) {
+ var file = filelist.item(i);
+
+ videoData = {
+ title : file.name,
+ videoDir : uploadUrl,
+ thumbnailDir : uploadUrl
+ };
+
+ servicesAPI.create(videoData)
+ .success(function(data) {
+ videoData = {};
+ getVideoList();
+ })
+ .error(function(data) {
+ console.log('Error: ' + data);
+ });
+ }
+ };
+
});
diff --git a/assets/js/public/controllers/editController.js b/assets/js/public/controllers/editController.js
index dc52a72..8a1c532 100644
--- a/assets/js/public/controllers/editController.js
+++ b/assets/js/public/controllers/editController.js
@@ -1,27 +1,81 @@
-angular.module('zoomableApp').controller('editController', function($scope, $stateParams, servicesAPI){
-
+angular.module('zoomableApp').controller('editController', function($scope, $mdToast, $mdDialog, $state, servicesAPI){
// VARIABLES
$scope.defaultImagePath = 'images/bunny.png';
- $scope.video_id = $stateParams.videoId;
-
- /* Get video object by video id */
- servicesAPI.getOne($scope.video_id)
- .success(function(data) {
- $scope.video = data;
- })
- .error(function(data) {
- console.log('Error: ' + data);
- });
-
- /* Copy embed link to system clipboard */
- $scope.copyEmbedLink = function(link) {
- console.log(link);
- // TO BE IMPLEMENTED
+ $scope.originalVideoTitle = '';
+ $scope.video = {};
+ $scope.tags = [];
+
+ $scope.init = function() {
+ $scope.video = window.SAILS_LOCALS.video;
+ // prevent page header from changing when title is being edited
+ $scope.originalVideoTitle = $scope.video.title;
}
+ /* Save changes made to video fields */
+ /* Frontend checks ensure this ftn only called when there are changes & form is valid */
+ $scope.saveChanges = function() {
+ // create object for editable fields
+ var updatedData = {
+ title: $scope.video.title,
+ description: $scope.video.description,
+ //tags: $scope.video.tags,
+ privacy: $scope.video.privacy
+ };
+
+ // update changes into database
+ servicesAPI.update($scope.video.id, updatedData)
+ .success(function(data) {
+ // update page header title with new title
+ $scope.originalVideoTitle = $scope.video.title;
+
+ // show toast if changes saved successfully
+ var toast = $mdToast.simple()
+ .content('Changes Saved!')
+ .action('OK').highlightAction(true)
+ .hideDelay(1500)
+ .position('top right')
+ .parent(document.getElementById('toast-area'));
+
+ $mdToast.show(toast);
+
+ // set form back to clean state to disable save button
+ $scope.videoForm.$setPristine();
+ })
+ .error(function(data) {
+ console.log('Error: ' + data);
+ });
+ };
+
+ /* Show dialog when user click cancel when changes made */
+ $scope.showConfirm = function(ev) {
+ if ($scope.videoForm.$dirty) {
+ // Appending dialog to document.bod
+ var confirm = $mdDialog.confirm()
+ .title('Are you sure you want to leave this page?')
+ .textContent('You have unsaved changes. Your changes will not be saved if you leave this page.')
+ .ariaLabel('Confirm Navigation')
+ .targetEvent(ev)
+ .ok('Stay on this page')
+ .cancel('Leave this page');
+ $mdDialog.show(confirm).then(function() {
+ // if user stay on page, do nothing
+ }, function() {
+ // if user leave page, redirect to dashboard page
+ window.location = '/';
+ });
+ }
+ else {
+ // just redirect to dashboard page if no changes made
+ window.location = '/';
+ }
+ };
+
/* Update video privacy field */
$scope.updatePrivacy = function(privacy) {
$scope.video.privacy = privacy;
+
+ // set form to dirty to enable save button
+ $scope.videoForm.$setDirty();
}
});
diff --git a/assets/js/public/controllers/loginController.js b/assets/js/public/controllers/loginController.js
index dde8d35..25ed33d 100644
--- a/assets/js/public/controllers/loginController.js
+++ b/assets/js/public/controllers/loginController.js
@@ -1,10 +1,66 @@
-angular.module('zoomableApp').controller('loginController', function($scope){
- $scope.user = true; // set to true for now
+angular.module('zoomableApp').controller('loginController', function($scope, $state, servicesAPI){
+ // VARIABLES
+ $scope.username = '';
+ $scope.password = '';
+ $scope.emailAddress = '';
+ $scope.isCreate = false;
+ $scope.errorMsg = '';
- /* FOR NAVBAR */
- $scope.username = "USERNAME"; // TO BE EDITED WHEN LINK TO DB
- $scope.profileItems = ['Settings', 'Log Out'];
+ // FUNCTIONS FOR LOGIN FORM
+ $scope.submitForm = function() {
+ if ($scope.isCreate) {
+ var accountData = {
+ username: $scope.username,
+ password: $scope.password,
+ email: $scope.emailAddress
+ }
+
+ // create a new account for new user
+ servicesAPI.createAccount(accountData)
+ .success(function(data) {
+ // redirect to dashboard page
+ window.location = '/';
+ })
+ .error(function(data) {
+ console.log('Error: ' + data);
+ // add prompt if email is not unique
+ });
+ }
+ else {
+ var accountData = {
+ username: $scope.username,
+ password: $scope.password
+ }
+
+ // check if user entered correct username and password
+ servicesAPI.login(accountData)
+ .success(function(data) {
+ // redirect to dashboard page
+ window.location = '/';
+ })
+ .error(function(data) {
+ console.log('Error: ' + data);
+ // unsuccessful login, update error message and initialise password field
+ $scope.errorMsg = "Incorrect username/password. Please try again.";
+ $scope.password = '';
+ });
+ }
+ };
+
+ $scope.hasEmptyFields = function() {
+ if ($scope.isCreate) {
+ // enable submit only if all fields are entered
+ if ($scope.emailAddress && $scope.username && $scope.password) {
+ return false;
+ }
+ }
+ else {
+ // enable submit only if all fields are entered
+ if ($scope.username && $scope.password) {
+ return false;
+ }
+ }
+ return true;
+ }
- /* FOR LOGIN */
- $scope.greeting = "Welcome to Zoomable";
});
diff --git a/assets/js/public/directives.js b/assets/js/public/directives.js
index 85b1669..37bfb95 100644
--- a/assets/js/public/directives.js
+++ b/assets/js/public/directives.js
@@ -6,4 +6,25 @@ angular.module('zoomableApp')
replace: true,
templateUrl: "../../../views/navbar.html"
}
- });
+ })
+ .directive('dbinfOnFilesSelected', [function() {
+ return {
+ restrict: 'A',
+ scope: {
+ //attribute data-dbinf-on-files-selected (normalized to dbinfOnFilesSelected) identifies the action
+ //to take when file(s) are selected. The '&' says to execute the expression for attribute
+ //data-dbinf-on-files-selected in the context of the parent scope. Note though that this '&'
+ //concerns visibility of the properties and functions of the parent scope, it does not
+ //fire the parent scope's $digest (dirty checking): use $scope.$apply() to update views
+ //(calling scope.$apply() here in the directive had no visible effect for me).
+ dbinfOnFilesSelected: '&'
+ },
+ link: function(scope, element, attr, ctrl) {
+ element.bind("change", function()
+ { //match the selected files to the name 'selectedFileList', and
+ //execute the code in the data-dbinf-on-files-selected attribute
+ scope.dbinfOnFilesSelected({selectedFileList : element[0].files});
+ });
+ }
+ }
+ }]);
\ No newline at end of file
diff --git a/assets/js/public/servicesAPI.js b/assets/js/public/servicesAPI.js
index 07824f8..d4448f1 100644
--- a/assets/js/public/servicesAPI.js
+++ b/assets/js/public/servicesAPI.js
@@ -11,6 +11,15 @@ angular.module('zoomableApp').factory('servicesAPI', function($http) {
},
delete : function(id) {
return $http.delete('/api/video/' + id);
+ },
+ update : function(id, videoData) {
+ return $http.put('/api/video/' + id, videoData);
+ },
+ createAccount : function(accountData) {
+ return $http.post('/api/user/signup', accountData);
+ },
+ login : function(accountData) {
+ return $http.post('/api/user/login', accountData);
}
}
});
diff --git a/assets/js/public/zoomableApp.js b/assets/js/public/zoomableApp.js
index 310496c..44dc222 100644
--- a/assets/js/public/zoomableApp.js
+++ b/assets/js/public/zoomableApp.js
@@ -1,4 +1,4 @@
-angular.module('zoomableApp', ['ui.router', 'appRoutes', 'ngMaterial'])
+angular.module('zoomableApp', ['ui.router', 'ngMaterial', 'ngMessages', 'ngclipboard'])
// Define standard theme for dashboard UI
.config(function($mdThemingProvider) {
$mdThemingProvider.theme('default')
diff --git a/assets/styles/custom/dashboard.less b/assets/styles/custom/dashboard.less
index 8c80e2b..86af0ff 100644
--- a/assets/styles/custom/dashboard.less
+++ b/assets/styles/custom/dashboard.less
@@ -3,6 +3,14 @@
*/
.dashboard {
+ .action-btns-left {
+ margin-top: 8px;
+ }
+
+ .action-btns-right {
+ margin-right: 15px;
+ }
+
img {
height: @dashboard-video-height;
width: @dashboard-video-width;
diff --git a/assets/styles/custom/edit.less b/assets/styles/custom/edit.less
index 387be6c..2735d4d 100644
--- a/assets/styles/custom/edit.less
+++ b/assets/styles/custom/edit.less
@@ -3,7 +3,40 @@
*/
#edit {
- a.md-button.cancel-btn,
+ .input-chips {
+ margin-top: 10px;
+
+ .md-chips {
+ padding-bottom: 3px;
+
+ /* do not group these rules */
+ *::-webkit-input-placeholder {
+ color: fade(@color-black, 26%);
+ }
+ *:-moz-placeholder {
+ /* FF 4-18 */
+ color: fade(@color-black, 26%);
+ }
+ *::-moz-placeholder {
+ /* FF 19+ */
+ color: fade(@color-black, 26%);
+ }
+ *:-ms-input-placeholder {
+ /* IE 10+ */
+ color: fade(@color-black, 26%);
+ }
+
+ &.md-focused {
+ box-shadow: 0 1px @color-primary;
+ }
+ }
+
+ .md-chip.md-focused {
+ background-color: @color-primary;
+ }
+ }
+
+ button.md-button.cancel-btn,
button.md-button.save-btn {
margin: 0px 0px 0px 10px;
}
@@ -61,6 +94,10 @@
padding: 15px 5px 0px 5px;
}
+ .md-chips {
+ padding-bottom: 5px;
+ }
+
md-icon.tab-icon {
width: 20px;
opacity: 0.54;
@@ -83,8 +120,18 @@
vertical-align: middle;
}
+ #toast-area {
+ display: block;
+ height: 70px;
+ width: 100%;
+ z-index: 90;
+
+ button {
+ color: @color-white;
+ }
+ }
+
.video-info {
- margin-top: 60px;
font-size: 13px;
button.md-button.md-icon-button {
diff --git a/assets/styles/custom/elements.less b/assets/styles/custom/elements.less
index 325d3ca..cee55b0 100644
--- a/assets/styles/custom/elements.less
+++ b/assets/styles/custom/elements.less
@@ -2,8 +2,13 @@
* elements.less
*/
+body {
+ min-width: 800px;
+}
+
md-card {
display: block; // override default card styling to inherit height of children
+ padding: 0px !important;
}
md-card-header { // override default card header styling
@@ -13,6 +18,14 @@ md-card-header { // override default card header styling
md-content.layout-padding {
top: @navbar-height;
+ padding: 16px;
+}
+
+md-menu-content {
+ a {
+ color: @color-black !important;
+ text-decoration: none !important;
+ }
}
.page-header {
diff --git a/assets/styles/custom/login.less b/assets/styles/custom/login.less
new file mode 100644
index 0000000..4794eb2
--- /dev/null
+++ b/assets/styles/custom/login.less
@@ -0,0 +1,86 @@
+/**
+ * login.less
+ */
+
+#login {
+ md-card {
+ background-color: @color-primary;
+ margin: 10px auto;
+ width: 550px;
+
+ .card-contents {
+ padding: 50px 0px 30px 0px;
+ }
+
+ .description {
+ color: fade(@color-black, 87%);
+ font-size: 20px;
+ text-align: center;
+ padding: 20px 60px 40px 60px;
+ }
+
+ .field-area {
+ margin: 10px 60px;
+ text-align: center;
+
+ .create-account-link,
+ .forgot-password-link {
+ display: block;
+ font-size: 18px;
+ }
+
+ .create-account-link {
+ text-align: center;
+ padding: 15px 0px 5px 0px;
+ }
+
+ .forgot-password-link {
+ text-align: right;
+ }
+
+ .error-message {
+ color: #BF360C;
+ font-size: 16px;
+ text-align: center;
+ }
+
+ input,
+ button {
+ border: none;
+ border-radius: 3px;
+ height: 55px;
+ margin-bottom: 15px;
+ outline: none;
+ padding: 0px 20px;
+ width: 100%;
+ }
+
+ input {
+ background-color: fade(@color-white, 95%);
+ font-size: 19px;
+ }
+
+ button {
+ background-color: fade(@color-button-primary, 95%);
+ color: @color-white;
+ font-size: 22px;
+ font-weight: bold;
+ margin: 50px 0px 0px 0px;
+ text-transform: uppercase;
+
+ &:disabled {
+ background-color: fade(@color-button-primary, 50%);
+ color: fade(@color-white, 50%);
+ }
+ }
+ }
+
+ .logo {
+ background: url(../../images/zoomable_logo_black.svg) no-repeat;
+ background-position: center;
+ height: 60px;
+ margin: auto;
+ width: 300px;
+ }
+ }
+}
diff --git a/assets/styles/importer.less b/assets/styles/importer.less
index d7bfa72..0a30f78 100644
--- a/assets/styles/importer.less
+++ b/assets/styles/importer.less
@@ -33,3 +33,4 @@
@import 'custom/elements.less';
@import 'custom/dashboard.less';
@import 'custom/edit.less';
+@import 'custom/login.less';
diff --git a/assets/views/dashboard.html b/assets/views/dashboard.html
deleted file mode 100644
index e1d0fe9..0000000
--- a/assets/views/dashboard.html
+++ /dev/null
@@ -1,84 +0,0 @@
-