diff --git a/services/web/app/views/project/list/project-list.pug b/services/web/app/views/project/list/project-list.pug index 0bb93e8336..629c3dc62d 100644 --- a/services/web/app/views/project/list/project-list.pug +++ b/services/web/app/views/project/list/project-list.pug @@ -140,23 +140,35 @@ ng-repeat="project in visibleProjects | orderBy:predicate:reverse", ng-controller="ProjectListItemController" ) - .row + .row(select-row) .col-xs-6 input.select-item( select-individual, type="checkbox", ng-model="project.selected" + stop-propagation="click" ) span - a.projectName(href="/project/{{project.id}}") {{project.name}} + a.projectName( + href="/project/{{project.id}}" + stop-propagation="click" + ) {{project.name}} span( ng-controller="TagListController" ) - a.label.label-default.tag-label( - href, - ng-repeat='tag in project.tags', - ng-click="selectTag(tag)" - ) {{tag.name}} + .tag-label( + ng-repeat='tag in project.tags' + stop-propagation="click" + ) + a.label.label-default.tag-label-name( + href, + ng-click="selectTag(tag)" + ) {{tag.name}} + a.label.label-default.tag-label-remove( + href + ng-click="removeProjectFromTag(project, tag)" + ) × + .col-xs-2 span.owner {{ownerName()}} .col-xs-4 diff --git a/services/web/app/views/project/list/side-bar.pug b/services/web/app/views/project/list/side-bar.pug index 1fb0e2bda9..b3a3f11948 100644 --- a/services/web/app/views/project/list/side-bar.pug +++ b/services/web/app/views/project/list/side-bar.pug @@ -79,6 +79,15 @@ li a(href, ng-click="deleteTag(tag)", stop-propagation="click") | #{translate("delete")} + li.tag.untagged( + ng-if="tags.length", + ng-cloak, + ng-click="selectUntagged()" + ng-class="{active: filter === 'untagged'}", + ) + a.tag-name(href) + | #{translate("uncategorized")} + span.subdued ({{ nUntagged }}) li(ng-cloak) a.tag(href, ng-click="openNewTagModal()") i.fa.fa-fw.fa-plus diff --git a/services/web/public/coffee/directives/selectAll.coffee b/services/web/public/coffee/directives/selectAll.coffee index d5be2639e6..6400d2d0ad 100644 --- a/services/web/public/coffee/directives/selectAll.coffee +++ b/services/web/public/coffee/directives/selectAll.coffee @@ -58,4 +58,18 @@ define [ scope.$apply () -> scope.ngModel = false ignoreChanges = false + + scope.$on "select-all:row-clicked", () -> + ignoreChanges = true + scope.$apply () -> + scope.ngModel = !scope.ngModel + ignoreChanges = false + } + + App.directive "selectRow", () -> + return { + scope: true + link: (scope, element, attrs) -> + element.on "click", (e) -> + scope.$broadcast "select-all:row-clicked" } \ No newline at end of file diff --git a/services/web/public/coffee/main/project-list/project-list.coffee b/services/web/public/coffee/main/project-list/project-list.coffee index 161598d32d..ad9d0f3582 100644 --- a/services/web/public/coffee/main/project-list/project-list.coffee +++ b/services/web/public/coffee/main/project-list/project-list.coffee @@ -2,7 +2,7 @@ define [ "base" ], (App) -> - App.controller "ProjectPageController", ($scope, $modal, $q, $window, queuedHttp, event_tracking, $timeout) -> + App.controller "ProjectPageController", ($scope, $modal, $q, $window, queuedHttp, event_tracking, $timeout, localStorage) -> $scope.projects = window.data.projects $scope.tags = window.data.tags $scope.notifications = window.data.notifications @@ -10,6 +10,7 @@ define [ $scope.selectedProjects = [] $scope.filter = "all" $scope.predicate = "lastUpdated" + $scope.nUntagged = 0 $scope.reverse = true $scope.searchText = value : "" @@ -18,6 +19,12 @@ define [ recalculateProjectListHeight() , 10 + $scope.$watch(( + () -> $scope.projects.filter((project) -> !project.tags? or project.tags.length == 0).length + ), (newVal) -> $scope.nUntagged = newVal) + + storedUIOpts = JSON.parse(localStorage("project_list")) + recalculateProjectListHeight = () -> topOffset = $(".project-list-card")?.offset()?.top bottomOffset = $("footer").outerHeight() + 25 @@ -52,6 +59,13 @@ define [ project.tags ||= [] project.tags.push tag + markTagAsSelected = (id) -> + for tag in $scope.tags + if tag._id == id + tag.selected = true + else + tag.selected = false + $scope.changePredicate = (newPredicate)-> if $scope.predicate == newPredicate $scope.reverse = !$scope.reverse @@ -104,6 +118,10 @@ define [ if $scope.filter == "tag" and selectedTag? and project.id not in selectedTag.project_ids visible = false + # Hide tagged projects if we only want to see the uncategorized ones + if $scope.filter == "untagged" and project.tags?.length > 0 + visible = false + # Hide projects we own if we only want to see shared projects if $scope.filter == "shared" and project.accessLevel == "owner" visible = false @@ -126,6 +144,11 @@ define [ else # We don't want hidden selections project.selected = false + + localStorage("project_list", JSON.stringify({ + filter: $scope.filter, + selectedTagId: selectedTag?._id + })) $scope.updateSelectedProjects() $scope.getSelectedTag = () -> @@ -178,6 +201,23 @@ define [ # the projects from view $scope.updateVisibleProjects() + $scope.removeProjectFromTag = (project, tag) -> + tag.showWhenEmpty = true + + project.tags ||= [] + index = project.tags.indexOf tag + + if index > -1 + $scope._removeProjectIdsFromTagArray(tag, [ project.id ]) + project.tags.splice(index, 1) + queuedHttp({ + method: "DELETE" + url: "/tag/#{tag._id}/project/#{project.id}" + headers: + "X-CSRF-Token": window.csrfToken + }) + $scope.updateVisibleProjects() + $scope.addSelectedProjectsToTag = (tag) -> selected_projects = $scope.getSelectedProjects() event_tracking.send 'project-list-page-interaction', 'project action', 'addSelectedProjectsToTag' @@ -425,7 +465,12 @@ define [ window.location = path - $scope.updateVisibleProjects() + if storedUIOpts?.filter? + if storedUIOpts.filter == "tag" and storedUIOpts.selectedTagId? + markTagAsSelected(storedUIOpts.selectedTagId) + $scope.setFilter(storedUIOpts.filter) + else + $scope.updateVisibleProjects() App.controller "ProjectListItemController", ($scope) -> $scope.ownerName = () -> diff --git a/services/web/public/coffee/main/project-list/tag-controllers.coffee b/services/web/public/coffee/main/project-list/tag-controllers.coffee index cc3415e32e..6a75e7491b 100644 --- a/services/web/public/coffee/main/project-list/tag-controllers.coffee +++ b/services/web/public/coffee/main/project-list/tag-controllers.coffee @@ -15,7 +15,11 @@ define [ $scope._clearTags() tag.selected = true $scope.setFilter("tag") - + + $scope.selectUntagged = () -> + $scope._clearTags() + $scope.setFilter("untagged") + $scope.deleteTag = (tag) -> modalInstance = $modal.open( templateUrl: "deleteTagModalTemplate" diff --git a/services/web/public/stylesheets/app/project-list.less b/services/web/public/stylesheets/app/project-list.less index 17f6b7bb25..13ca60e181 100644 --- a/services/web/public/stylesheets/app/project-list.less +++ b/services/web/public/stylesheets/app/project-list.less @@ -124,6 +124,20 @@ ul.folders-menu { } } } + &.untagged { + font-style: italic; + margin-bottom: @line-height-computed / 4; + a { + line-height: 1.7; + &:hover, + &:focus { + text-decoration: none; + } + } + span.subdued { + font-style: normal; + } + } &:hover { &:not(.active) { background-color: darken(@gray-lightest, 2%); @@ -246,10 +260,24 @@ ul.project-list { margin-left: @line-height-computed / 4; position: relative; top: -2px; - padding-top: 0.25em; display: inline-block; - color: white; } + .tag-label-name, + .tag-label-remove { + display: inline-block; + padding-top: 0.3em; + color: #FFF; + } + .tag-label-name { + padding-right: 0.3em; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + .tag-label-remove { + padding-left: 0.3em; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } } i.tablesort { padding-left: 8px;