diff --git a/css/styles.css b/css/styles.css index 095493c..ab0e5f0 100644 --- a/css/styles.css +++ b/css/styles.css @@ -632,3 +632,88 @@ h2.ssb-title { .folder-actions-list li { padding: 5px 0px; } + + + +multiselect { + display:block; +} +multiselect .btn { + width: 100%; + background-color: #FFF; +} +multiselect .btn.error{ + border: 1px solid #da4f49 !important; +} +multiselect .dropdown-menu { + max-height: 300px; + overflow-y: auto; +} +multiselect .dropdown-menu { + width: 100%; + box-sizing: border-box; + padding: 2px; +} +multiselect .dropdown-menu > li > a { + padding: 3px 10px; + cursor:pointer; +} +.icon-ok:before { + content: "\f00c"; + font-family: FontAwesome; + font-style: normal; + font-weight: normal; + text-decoration: inherit; + color: #000; + font-size: 14px; +} +.icon-remove:before { + content: "\f00d"; + font-family: FontAwesome; + font-style: normal; + font-weight: normal; + text-decoration: inherit; + color: #000; + font-size: 14px; +} + +.error-notice .oaerror { + width: 95%; /* Configure it fit in your design */ + margin: 0 auto; /* Centering Stuff */ + background-color: #FFFFFF; /* Default background */ + padding: 20px; + border: 1px solid #eee; + border-left-width: 5px; + border-radius: 3px; + margin: 0 auto; + font-family: 'Open Sans', sans-serif; + font-size: 16px; +} +.error-notice .danger { + border-left-color: #d9534f; /* Left side border color */ + background-color: rgba(217, 83, 79, 0.1); /* Same color as the left border with reduced alpha to 0.1 */ +} +.error-notice .danger strong { + color: #d9534f; +} +.error-notice .warning { + border-left-color: #f0ad4e; + background-color: rgba(240, 173, 78, 0.1); +} +.error-notice .warning strong { + color: #f0ad4e; +} +.error-notice .info { + border-left-color: #5bc0de; + background-color: rgba(91, 192, 222, 0.1); +} +.error-notice .info strong { + color: #5bc0de; +} +.error-notice .success { + border-left-color: #3c763d; + background-color: rgba(43, 84, 44, 0.1); +} +.error-notice .success strong { + color: #3c763d; +} diff --git a/index.html b/index.html index 01143de..31bff41 100644 --- a/index.html +++ b/index.html @@ -34,6 +34,7 @@ + diff --git a/js/app.js b/js/app.js index 01313a5..a83d6ed 100644 --- a/js/app.js +++ b/js/app.js @@ -4,7 +4,7 @@ var angular = window.angular; var OAUTH3 = window.OAUTH3; -var app = window.app = angular.module('launchpad', ['oauth3.org', 'ui.router', 'LocalStorageModule', 'angucomplete-alt', 'ez.fileTree']); +var app = window.app = angular.module('launchpad', ['oauth3.org', 'ui.router', 'LocalStorageModule', 'angucomplete-alt', 'ez.fileTree', 'ui.multiselect']); app.directive('daplieFileChange', function () { return { diff --git a/js/controllers/website-controller.js b/js/controllers/website-controller.js index 16f4aad..1274d37 100644 --- a/js/controllers/website-controller.js +++ b/js/controllers/website-controller.js @@ -58,6 +58,14 @@ app.directive('fileTree', [function () { }; }]); +app.directive('notificationBar', [function () { + return { + restrict: 'EA', + templateUrl: '/templates/widgets/website-notification-bar.html', + controller: 'websiteCtrl as vm' + }; +}]); + app.controller('websiteCtrl', [ '$scope', '$q', 'Auth', 'azp@oauth3.org', '$timeout', '$sce' , function ($scope, $q, Auth, Oauth3, $timeout, $sce) { @@ -65,6 +73,10 @@ app.controller('websiteCtrl', [ var vm = this; var angular = window.angular; vm.domains = []; + vm.displaySpinner = ''; + vm.alertNotification = { + hidden: 'hidden' + }; //vm.unzipPath = '/'; vm.uploadPath = '/'; @@ -191,6 +203,8 @@ app.controller('websiteCtrl', [ }); }; vm._uploadFile = function (pkg, opts) { + debugger; + vm.newFileUploaded = opts.newFile.name; opts.progress = opts.progress || opts; return pkg.add({ hostname: opts.domain @@ -210,8 +224,10 @@ app.controller('websiteCtrl', [ , strip: opts.stripZip , path: opts.uploadPath }).then(function (result) { + var msg = vm.newFileUploaded + " has been uploaded" opts.progress.uploadTotal = 0; - window.alert(JSON.stringify(result)); + vm.buildNotification(result, msg); + // window.alert(JSON.stringify(result)); }); }; @@ -355,7 +371,7 @@ app.controller('websiteCtrl', [ vm.Sites.remove = function (r, opts) { var pkg = Auth.oauth3.pkg('www@daplie.com'); - + vm.pathRemoved = r.path || opts.path; return pkg.remove({ hostname: r.domain , domain: r.domain @@ -365,13 +381,15 @@ app.controller('websiteCtrl', [ , path: opts.path || r.path , confirm: opts.confirm || r.confirm }).then(function (result) { - window.alert(JSON.stringify(result)); + var msg = "'"+ vm.pathRemoved + "'" + ' has been removed'; + vm.buildNotification(result, msg); + // window.alert(JSON.stringify(result)); }); }; vm.deleteFilesFrom = function (r, path, opts) { var confirmMessage; - vm.autoPopulateWebPath; + // vm.autoPopulateWebPath; opts = {}; if (path === undefined) { @@ -424,7 +442,7 @@ app.controller('websiteCtrl', [ vm.autoPopulateWebPath.push('/'); vm.autoPopulateWebPath = vm.autoPopulateWebPath.join(''); } else { - vm.currentFolder = "hidden" + vm.currentFolder = "hidden"; } if (vm.breadcrumbPathClicked) { if (path === 'root') { @@ -455,6 +473,7 @@ app.controller('websiteCtrl', [ //, sub: r.sub, path: vm.breadcrumbsPath.join('/') }).then(function (result) { + vm.displaySpinner = 'hidden'; vm.folderStructure = result; result.data.forEach(function(file) { if (file.directory) { @@ -471,7 +490,7 @@ app.controller('websiteCtrl', [ vm.cleanPath = function () { vm.savedPath = vm.autoPopulateWebPath; vm.autoPopulateWebPath = ''; - } + }; vm.showUploadButton = true; vm.showFolderAction = true; @@ -479,20 +498,20 @@ app.controller('websiteCtrl', [ if (vm.savedPath === undefined) { vm.savedPath = '/'; } - vm.savedPath; + // vm.savedPath; vm.hideFolderInput = 'hidden'; vm.showFileUploadBtn = true; r.uploadPath = vm.savedPath + vm.autoPopulateWebPath; }; vm.autoPopulateFolderName = function () { - console.log('blah', vm.currentFolder); if (vm.currentFolder === 'hidden') { - vm.autoPopulateWebPath = '/' + vm.autoPopulateWebPath = '/'; } - } + }; vm.getDirectories = function (path) { + vm.displaySpinner = ''; vm.siteDirectories = []; vm.siteFiles = []; var site = vm.siteResults; @@ -518,11 +537,17 @@ app.controller('websiteCtrl', [ vm.triggerDeleteFolder = function (folder, r) { console.log('DELETE FOLDER ->', folder); - vm.deleteFilesFrom(r, folder) - } + vm.deleteFilesFrom(r, folder); + }; vm.Shares = {}; vm.Shares.invite = function (r) { + if (vm.copiedShareMode === undefined) { + r.shareMode = ''; + } else { + r.shareMode = vm.copiedShareMode; + } + r.sharePath = vm.autoPopulateWebPath; var pkg = Auth.oauth3.pkg('www@daplie.com'); return pkg.invite({ comment: r.shareEmail @@ -537,7 +562,15 @@ app.controller('websiteCtrl', [ var arr = r.sharedWith || []; arr.push(result.data); r.sharedWith = arr; - window.alert(JSON.stringify(result.data)); + var msg; + var person = result.data.comment; + if (result.data.error) { + msg = result.data.error.message; + } else { + msg = 'shared with ' + person; + } + vm.buildNotification(result, msg); + // window.alert(JSON.stringify(result.data)); }); }; vm.Shares.accept = function () { @@ -559,6 +592,7 @@ app.controller('websiteCtrl', [ //, sub: r.sub , path: r.sharePath }).then(function (result) { + vm.displaySpinner = 'hidden'; console.log('list shares result:'); console.log(result); r.sharedWith = result.data; @@ -577,6 +611,9 @@ app.controller('websiteCtrl', [ , path: s.sharePath , challenge: s.challenge }).then(function (result) { + var person = result.data.comment; + var msg = "revoked access from " + person; + vm.buildNotification(result, msg); console.log('remove share result:'); console.log(result); var index; @@ -652,6 +689,22 @@ app.controller('websiteCtrl', [ } }); + $scope.$watch('selectedAccess', function (selectedAccess) { + if (selectedAccess.length !== 0) { + vm.accessLevelArry = []; + vm.prettyAccessArray = []; + selectedAccess.forEach(function(letter) { + vm.prettyAccessArray.push(letter.name); + vm.accessLevelArry.push(letter.value); + }); + vm.prettyShareMode = vm.prettyAccessArray.join(", "); + vm.copiedShareMode = vm.accessLevelArry.join(","); + } + }); + + $scope.selectedAccess = []; + $scope.accessLevel = [{ name: 'Read', value: 'r' }, { name: 'Write', value: 'w' }, { name: 'Invite', value: 'x' }]; + $scope.localDomainSearch = function(str, domain) { var matches = []; domain.forEach(function(domain) { @@ -687,6 +740,26 @@ app.controller('websiteCtrl', [ } }; + vm.buildNotification = function (result, msg) { + console.log('THE RESULT ->', result); + console.log('THE MSG', msg); + if (result.data.error) { + vm.alertNotification = { + className: 'danger', + title: 'Error', + hidden: '', + message: msg + }; + } else { + vm.alertNotification = { + className: 'success', + title: 'Success', + hidden: '', + message: msg + }; + } + }; + vm.closeAllOpenActions = function () { $timeout(function() { vm.showInviteContainer = false; @@ -695,6 +768,7 @@ app.controller('websiteCtrl', [ vm.displayFileTree = false; vm.websiteTiles = false; vm.showBackBtn = false; + vm.autoPopulateWebPath = ''; }, 150); }; }]); @@ -744,5 +818,5 @@ app.filter('stringify', function() { app.filter('capitalize', function() { return function(input) { return (!!input) ? input.charAt(0).toUpperCase() + input.substr(1).toLowerCase() : ''; - } + }; }); diff --git a/js/lib/angular/multiselect-dropdown.js b/js/lib/angular/multiselect-dropdown.js new file mode 100644 index 0000000..af2faed --- /dev/null +++ b/js/lib/angular/multiselect-dropdown.js @@ -0,0 +1,262 @@ +angular.module('ui.multiselect', []) + +//from bootstrap-ui typeahead parser +.factory('optionParser', ['$parse', function ($parse) { + + var TYPEAHEAD_REGEXP = /^\s*(.*?)(?:\s+as\s+(.*?))?\s+for\s+(?:([\$\w][\$\w\d]*))\s+in\s+(.*)$/; + + return { + parse: function (input) { + + var match = input.match(TYPEAHEAD_REGEXP), modelMapper, viewMapper, source; + if (!match) { + throw new Error( + "Expected typeahead specification in form of '_modelValue_ (as _label_)? for _item_ in _collection_'" + + " but got '" + input + "'."); + } + + return { + itemName: match[3], + source: $parse(match[4]), + viewMapper: $parse(match[2] || match[1]), + modelMapper: $parse(match[1]) + }; + } + }; + }]) + + .directive('multiselect', ['$parse', '$document', '$compile', 'optionParser', + + function ($parse, $document, $compile, optionParser) { + return { + restrict: 'E', + require: 'ngModel', + link: function (originalScope, element, attrs, modelCtrl) { + + var exp = attrs.options, + parsedResult = optionParser.parse(exp), + isMultiple = attrs.multiple ? true : false, + required = false, + scope = originalScope.$new(), + changeHandler = attrs.change || anguler.noop; + + scope.items = []; + scope.header = 'Select'; + scope.multiple = isMultiple; + scope.disabled = false; + + originalScope.$on('$destroy', function () { + scope.$destroy(); + }); + + var popUpEl = angular.element(''); + + //required validator + if (attrs.required || attrs.ngRequired) { + required = true; + } + attrs.$observe('required', function(newVal) { + required = newVal; + }); + + //watch disabled state + scope.$watch(function () { + return $parse(attrs.disabled)(originalScope); + }, function (newVal) { + scope.disabled = newVal; + }); + + //watch single/multiple state for dynamically change single to multiple + scope.$watch(function () { + return $parse(attrs.multiple)(originalScope); + }, function (newVal) { + isMultiple = newVal || false; + }); + + //watch option changes for options that are populated dynamically + scope.$watch(function () { + return parsedResult.source(originalScope); + }, function (newVal) { + if (angular.isDefined(newVal)) + parseModel(); + }); + + //watch model change + scope.$watch(function () { + return modelCtrl.$modelValue; + }, function (newVal, oldVal) { + //when directive initialize, newVal usually undefined. Also, if model value already set in the controller + //for preselected list then we need to mark checked in our scope item. But we don't want to do this every time + //model changes. We need to do this only if it is done outside directive scope, from controller, for example. + if (angular.isDefined(newVal)) { + markChecked(newVal); + scope.$eval(changeHandler); + } + getHeaderText(); + modelCtrl.$setValidity('required', scope.valid()); + }, true); + + function parseModel() { + scope.items.length = 0; + var model = parsedResult.source(originalScope); + for (var i = 0; i < model.length; i++) { + var local = {}; + local[parsedResult.itemName] = model[i]; + scope.items.push({ + label: parsedResult.viewMapper(local), + model: model[i], + checked: false + }); + } + } + + parseModel(); + + element.append($compile(popUpEl)(scope)); + + function getHeaderText() { + var edit = "edit"; + if (!modelCtrl.$modelValue || !modelCtrl.$modelValue.length) return scope.header = 'Can ' + edit; + if (isMultiple) { + edit = ''; + var accessLevelArray = []; + modelCtrl.$modelValue.forEach(function(level) { + accessLevelArray.push(level.name); + }); + scope.header = "Can " + accessLevelArray.join(', '); + } else { + var local = {}; + local[parsedResult.itemName] = modelCtrl.$modelValue; + scope.header = parsedResult.viewMapper(local); + } + } + + scope.valid = function validModel() { + if(!required) return true; + var value = modelCtrl.$modelValue; + return (angular.isArray(value) && value.length > 0) || (!angular.isArray(value) && value != null); + }; + + function selectSingle(item) { + if (item.checked) { + scope.uncheckAll(); + } else { + scope.uncheckAll(); + item.checked = !item.checked; + } + setModelValue(false); + } + + function selectMultiple(item) { + item.checked = !item.checked; + setModelValue(true); + } + + function setModelValue(isMultiple) { + var value; + + if (isMultiple) { + value = []; + angular.forEach(scope.items, function (item) { + if (item.checked) value.push(item.model); + }); + } else { + angular.forEach(scope.items, function (item) { + if (item.checked) { + value = item.model; + return false; + } + }); + } + modelCtrl.$setViewValue(value); + } + + function markChecked(newVal) { + if (!angular.isArray(newVal)) { + angular.forEach(scope.items, function (item) { + if (angular.equals(item.model, newVal)) { + item.checked = true; + return false; + } + }); + } else { + angular.forEach(newVal, function (i) { + angular.forEach(scope.items, function (item) { + if (angular.equals(item.model, i)) { + item.checked = true; + } + }); + }); + } + } + + scope.checkAll = function () { + if (!isMultiple) return; + angular.forEach(scope.items, function (item) { + item.checked = true; + }); + setModelValue(true); + }; + + scope.uncheckAll = function () { + angular.forEach(scope.items, function (item) { + item.checked = false; + }); + setModelValue(true); + }; + + scope.select = function (item) { + if (isMultiple === false) { + selectSingle(item); + scope.toggleSelect(); + } else { + selectMultiple(item); + } + }; + } + }; + }]) + + .directive('multiselectPopup', ['$document', function ($document) { + return { + restrict: 'E', + scope: false, + replace: true, + templateUrl: 'templates/widgets/share-access-multiselect.html', + link: function (scope, element, attrs) { + + scope.isVisible = false; + + scope.toggleSelect = function () { + if (element.hasClass('open')) { + element.removeClass('open'); + $document.unbind('click', clickHandler); + } else { + element.addClass('open'); + scope.focus(); + $document.bind('click', clickHandler); + } + }; + + function clickHandler(event) { + if (elementMatchesAnyInArray(event.target, element.find(event.target.tagName))) + return; + element.removeClass('open'); + $document.unbind('click', clickHandler); + scope.$digest(); + } + + scope.focus = function focus(){ + var searchBox = element.find('input')[0]; + searchBox.focus(); + }; + + var elementMatchesAnyInArray = function (element, elementArray) { + for (var i = 0; i < elementArray.length; i++) + if (element == elementArray[i]) + return true; + return false; + }; + } + }; + }]); diff --git a/templates/website.html b/templates/website.html index 30c83e3..e06f7e8 100644 --- a/templates/website.html +++ b/templates/website.html @@ -135,6 +135,7 @@