diff --git a/css/styles.css b/css/styles.css index 095493c..616cebd 100644 --- a/css/styles.css +++ b/css/styles.css @@ -632,3 +632,47 @@ 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; +} 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..eba9bc6 100644 --- a/js/controllers/website-controller.js +++ b/js/controllers/website-controller.js @@ -523,6 +523,12 @@ app.controller('websiteCtrl', [ 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 @@ -652,6 +658,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) { @@ -695,6 +717,7 @@ app.controller('websiteCtrl', [ vm.displayFileTree = false; vm.websiteTiles = false; vm.showBackBtn = false; + vm.autoPopulateWebPath = ''; }, 150); }; }]); @@ -744,5 +767,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..aaa60f6 --- /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..4108003 100644 --- a/templates/website.html +++ b/templates/website.html @@ -191,18 +191,20 @@

Name

-
+ +
- - -
-
- - -
-
- + +
+ + +
+ +
@@ -217,7 +219,7 @@

friend@email.com

Share Path: /

-

Actions Allowed: rwx

+

Actions Allowed: {{ vm.prettyShareMode }}

Invite: pending

diff --git a/templates/widgets/share-access-multiselect.html b/templates/widgets/share-access-multiselect.html new file mode 100644 index 0000000..d8ddca0 --- /dev/null +++ b/templates/widgets/share-access-multiselect.html @@ -0,0 +1,20 @@ +