Add fullscreen view for WorkPackage.

pull/3563/head
Tim Habermaas 9 years ago committed by Stefan Botzenhart
parent c2439d20ce
commit 3d9b7266b3
  1. 1
      config/locales/js-en.yml
  2. 31
      frontend/app/routing.js
  3. 13
      frontend/app/templates/work_packages.list.html
  4. 166
      frontend/app/templates/work_packages.show.html
  5. 20
      frontend/app/work_packages/controllers/index.js
  6. 206
      frontend/app/work_packages/controllers/work-package-show-controller.js
  7. 4
      frontend/app/work_packages/controllers/work-packages-controller.js
  8. 9
      frontend/app/work_packages/controllers/work-packages-list-controller.js

@ -48,6 +48,7 @@ en:
button_edit: "Edit"
button_filter: "Filter"
button_list_view: "List view"
button_show_view: "Fullscreen view"
button_log_time: "Log time"
button_more: "More"
button_move: "Move"

@ -67,6 +67,35 @@ angular.module('openproject')
}
}
})
.state('work-packages.show', {
url: '/{workPackageId:[0-9]+}?query_props',
templateUrl: '/templates/work_packages.show.html',
controller: 'WorkPackageShowController',
resolve: {
workPackage: function(WorkPackageService, $stateParams) {
return WorkPackageService.getWorkPackage($stateParams.workPackageId);
}
}
})
.state('work-packages.show.activity', {
url: '/activity',
templateUrl: '/templates/work_packages/tabs/activity.html'
})
.state('work-packages.show.activity.details', {
url: '#{activity_no:[0-9]+}',
templateUrl: '/templates/work_packages/tabs/activity.html'
})
.state('work-packages.show.relations', {
url: '/relations',
templateUrl: '/templates/work_packages/tabs/relations.html'
})
.state('work-packages.show.watchers', {
url: '/watchers',
controller: 'DetailsTabWatchersController',
templateUrl: '/templates/work_packages/tabs/watchers.html'
})
.state('work-packages.list', {
url: '',
controller: 'WorkPackagesListController',
@ -79,7 +108,7 @@ angular.module('openproject')
templateUrl: '/templates/work_packages.list.new.html'
})
.state('work-packages.list.details', {
url: '/{workPackageId:[0-9]+}?query_props',
url: '/details/{workPackageId:[0-9]+}?query_props',
templateUrl: '/templates/work_packages.list.details.html',
controller: 'WorkPackageDetailsController',
resolve: {

@ -62,6 +62,19 @@
<i class="icon-table-detail-view button--icon"></i>
</button>
</li>
<li>
<label for="work-packages-show-view-button" class="hidden-for-sighted">
{{ getActivationActionLabel(!isShowViewActive()) + ' ' + I18n.t('js.button_show_view') }}
</label>
<button id="work-packages-list-view-button"
accesskey="{{ isShowViewActive() ? '' : '9' }}"
class="button"
title="{{ getActivationActionLabel(!isShowViewActive()) + ' ' + I18n.t('js.button_show_view') }}"
ng-click="showWorkPackageShowView()"
ng-class="{ '-active': isShowViewActive() }">
<i class="icon-table-view button--icon"></i>
</button>
</li>
</ul>
</li>
<li class="toolbar-item">

@ -0,0 +1,166 @@
<div class="toolbar-container">
<div toolbar id="toolbar">
<h2 title="{{workPackage.props.subject}}"><a href="" ng-bind="type.props.name" /> {{workPackage.props.subject}}</h2>
<ul id="toolbar-items">
<li class="toolbar-item">
<button class="button -alt-highlight"
has-dropdown-menu
target="TasksDropdownMenu"
locals="availableTypes,projectIdentifier"
ng-disabled="cannot('work_package', 'create')">
<i class="button--icon icon-add"></i>
<span class="button--text" ng-bind="::I18n.t('js.toolbar.unselected_title')"></span>
<i class="button--dropdown-indicator"></i>
</button>
</li>
<li class="toolbar-item" feature-flag="detailsView">
<ul id="work-packages-view-mode-selection" class="toolbar-button-group">
<li>
<label for="work-packages-list-view-button" class="hidden-for-sighted">
{{ getActivationActionLabel(isDetailsViewActive()) + ' ' + I18n.t('js.button_list_view') }}
</label>
<button id="work-packages-list-view-button"
accesskey="{{ !isDetailsViewActive() ? '' : '8' }}"
class="button"
title="{{ getActivationActionLabel(isDetailsViewActive()) + ' ' + I18n.t('js.button_list_view') }}"
ng-click="closeDetailsView()"
ng-class="{ '-active': !isDetailsViewActive() }">
<i class="icon-table-view button--icon"></i>
</button>
</li>
<li feature-flag="detailsView">
<label for="work-packages-details-view-button" class="hidden-for-sighted">
{{ getActivationActionLabel(!isDetailsViewActive()) + ' ' + I18n.t('js.button_details_view') }}
</label>
<button class="hide"
ng-click="openOverviewTab()"
accesskey="{{ isDetailsViewActive() ? '' : '8' }}"></button>
<button id="work-packages-details-view-button"
class="button"
title="{{ getActivationActionLabel(!isDetailsViewActive()) + ' ' + I18n.t('js.button_details_view') }}"
ng-class="{ '-active': isDetailsViewActive() }"
ng-click="openLatestTab()">
<i class="icon-table-detail-view button--icon"></i>
</button>
</li>
<li>
<label for="work-packages-show-view-button" class="hidden-for-sighted">
{{ getActivationActionLabel(!isShowViewActive()) + ' ' + I18n.t('js.button_show_view') }}
</label>
<button id="work-packages-list-view-button"
accesskey="{{ isShowViewActive() ? '' : '9' }}"
class="button"
title="{{ getActivationActionLabel(!isShowViewActive()) + ' ' + I18n.t('js.button_show_view') }}"
ng-click="showWorkPackageShowView()"
ng-class="{ '-active': isShowViewActive() }">
<i class="icon-table-view button--icon"></i>
</button>
</li>
</ul>
</li>
<li class="toolbar-item">
<label for="work-packages-settings-button" class="hidden-for-sighted">
{{ I18n.t('js.button_settings') }}
</label>
<button id="work-packages-settings-button"
title="{{ I18n.t('js.button_settings') }}"
class="button last work-packages-settings-button"
has-dropdown-menu
target="SettingsDropdownMenu"
locals="query">
<i class="button--icon icon-settings"></i>
<i class="button--dropdown-indicator"></i>
</button>
</li>
</ul>
</div>
</div>
<div class="work-packages--filters-optional-container" ng-show="showFiltersOptions">
<div query-form id="query_form_content" class="hide-when-print">
<query-filters></query-filters>
</div>
</div>
<back-url></back-url>
<div class="work-packages--split-view">
<div class="work-packages--left">
<div class="attributes-group">
<div class="attributes-group--header">
<div class="attributes-group--header-container">
<h3 class="attributes-group--header-text">
{{ I18n.t('js.label_description') }}
</h3>
</div>
</div>
<div class="single-attribute wiki">
<work-package-field field="'description'"></work-package-field>
</div>
</div>
<div ng-repeat="group in vm.groupedFields" ng-hide="vm.hideEmptyFields && vm.isGroupHideable(vm.groupedFields, group.groupName, vm.workPackage)" class="attributes-group">
<div class="attributes-group--header">
<div class="attributes-group--header-container">
<h3 class="attributes-group--header-text"
ng-bind="I18n.t('js.work_packages.property_groups.' + group.groupName)"></h3>
</div>
<div class="attributes-group--header-toggle">
<panel-expander tabindex="-1" ng-if="vm.showToggleButton() && $first"
collapsed="vm.hideEmptyFields"
expand-text="{{ I18n.t('js.label_show_attributes') }}"
collapse-text="{{ I18n.t('js.label_hide_attributes') }}">
</panel-expander>
</div>
</div>
<dl class="attributes-key-value">
<dt
ng-hide="vm.hideEmptyFields && vm.isFieldHideable(vm.workPackage, field)"
ng-if="vm.isSpecified(vm.workPackage, field)"
ng-repeat-start="field in group.attributes" class="attributes-key-value--key">
{{vm.getLabel(vm.workPackage, field)}}
<span class="required" ng-if="vm.hasNiceStar(vm.workPackage, field)"> *</span>
</dt>
<dd
ng-hide="vm.hideEmptyFields && vm.isFieldHideable(vm.workPackage, field)"
ng-if="vm.isSpecified(vm.workPackage, field)"
ng-repeat-end
class="attributes-key-value--value-container">
<work-package-field field="field"></work-package-field>
</dd>
</dl>
</div>
<work-package-attachments edit work-package="vm.workPackage"></work-package-attachments>
</div>
<div class="work-packages--right">
<div id="tabs">
<ul class="tabrow">
<!-- The hrefs with empty URLs are necessary for IE10 to focus these links
properly. Thus, don't remove the hrefs or the empty URLs! -->
<li ui-sref="work-packages.show.activity({})"
ui-sref-active="selected">
<a href="" ng-bind="I18n.t('js.work_packages.tabs.activity')"/>
</li>
<li ui-sref="work-packages.show.relations({})"
ui-sref-active="selected">
<a href="" ng-bind="I18n.t('js.work_packages.tabs.relations')"/>
</li>
<li ng-if="canViewWorkPackageWatchers()"
ui-sref="work-packages.show.watchers({})"
ui-sref-active="selected">
<a href="" ng-bind="I18n.t('js.work_packages.tabs.watchers')"/>
</li>
</ul>
</div>
<div class="tabcontent" ui-view>
</div>
</div>
</div>

@ -109,6 +109,26 @@ angular.module('openproject.workPackages.controllers')
'WorkPackagesDisplayHelper',
require('./work-package-new-controller')
])
.controller('WorkPackageShowController', [
'$scope',
'$state',
'latestTab',
'workPackage',
'I18n',
'RELATION_TYPES',
'RELATION_IDENTIFIERS',
'$q',
'WorkPackagesHelper',
'PathHelper',
'UsersHelper',
'ConfigurationService',
'WorkPackageService',
'CommonRelationsHandler',
'ChildrenRelationsHandler',
'ParentRelationsHandler',
'EditableFieldsState',
require('./work-package-show-controller')
])
.controller('WorkPackagesController', [
'$scope',
'$state',

@ -0,0 +1,206 @@
//-- copyright
// OpenProject is a project management system.
// Copyright (C) 2012-2015 the OpenProject Foundation (OPF)
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License version 3.
//
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
// Copyright (C) 2006-2013 Jean-Philippe Lang
// Copyright (C) 2010-2013 the ChiliProject Team
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// See doc/COPYRIGHT.rdoc for more details.
//++
module.exports = function($scope,
$state,
latestTab,
workPackage,
I18n,
RELATION_TYPES,
RELATION_IDENTIFIERS,
$q,
WorkPackagesHelper,
PathHelper,
UsersHelper,
ConfigurationService,
WorkPackageService,
CommonRelationsHandler,
ChildrenRelationsHandler,
ParentRelationsHandler
) {
$scope.$on('$stateChangeSuccess', function(event, toState){
latestTab.registerState(toState.name);
});
$scope.$on('workPackageRefreshRequired', function(e, callback) {
refreshWorkPackage(callback);
});
// initialization
setWorkPackageScopeProperties(workPackage);
$scope.I18n = I18n;
$scope.$parent.preselectedWorkPackageId = $scope.workPackage.props.id;
$scope.maxDescriptionLength = 800;
function refreshWorkPackage(callback) {
WorkPackageService.getWorkPackage($scope.workPackage.props.id)
.then(function(workPackage) {
setWorkPackageScopeProperties(workPackage);
$scope.$broadcast('workPackageRefreshed');
if (callback) {
callback(workPackage);
}
});
}
$scope.refreshWorkPackage = refreshWorkPackage; // expose to child controllers
// Inform parent that work package is loaded so back url can be maintained
$scope.$emit('workPackgeLoaded');
function outputMessage(message, isError) {
$scope.$emit('flashMessage', {
isError: !!isError,
text: message
});
}
function outputError(error) {
outputMessage(error.message, true);
}
$scope.outputMessage = outputMessage; // expose to child controllers
$scope.outputError = outputError; // expose to child controllers
function setWorkPackageScopeProperties(workPackage){
$scope.workPackage = workPackage;
$scope.isWatched = !!workPackage.links.unwatch;
if (workPackage.links.watch === undefined) {
$scope.toggleWatchLink = workPackage.links.unwatch;
} else {
$scope.toggleWatchLink = workPackage.links.watch;
}
// autocomplete path
var projectId = workPackage.embedded.project.props.id;
$scope.autocompletePath = PathHelper.staticWorkPackagesAutocompletePath(projectId);
// activities and latest activities
$scope.activitiesSortedInDescendingOrder = ConfigurationService.commentsSortedInDescendingOrder();
$scope.activities = displayedActivities($scope.workPackage);
// watchers
if(workPackage.links.watchers) {
$scope.watchers = workPackage.embedded.watchers.embedded.elements;
}
$scope.showStaticPagePath = PathHelper.staticWorkPackagePath($scope.workPackage.props.id);
// Type
$scope.type = workPackage.embedded.type;
// Author
$scope.author = workPackage.embedded.author;
$scope.authorPath = PathHelper.staticUserPath($scope.author.props.id);
$scope.authorActive = UsersHelper.isActive($scope.author);
// Attachments
$scope.attachments = workPackage.embedded.attachments.embedded.elements;
// relations
$q.all(WorkPackagesHelper.getParent(workPackage)).then(function(parents) {
var relationsHandler = new ParentRelationsHandler(workPackage, parents, 'parent');
$scope.wpParent = relationsHandler;
});
$q.all(WorkPackagesHelper.getChildren(workPackage)).then(function(children) {
var relationsHandler = new ChildrenRelationsHandler(workPackage, children);
$scope.wpChildren = relationsHandler;
});
function relationTypeIterator(key) {
$q.all(WorkPackagesHelper.getRelationsOfType(
workPackage,
RELATION_TYPES[key])
).then(function(relations) {
var relationsHandler = new CommonRelationsHandler(workPackage,
relations,
RELATION_IDENTIFIERS[key]);
$scope[key] = relationsHandler;
});
}
for (var key in RELATION_TYPES) {
if (RELATION_TYPES.hasOwnProperty(key)) {
relationTypeIterator(key);
}
}
}
$scope.toggleWatch = function() {
var fetchOptions = {
method: $scope.toggleWatchLink.props.method
};
if($scope.toggleWatchLink.props.payload !== undefined) {
fetchOptions.contentType = 'application/json; charset=utf-8';
fetchOptions.data = JSON.stringify($scope.toggleWatchLink.props.payload);
}
$scope.toggleWatchLink
.fetch({ajax: fetchOptions})
.then(refreshWorkPackage, outputError);
};
$scope.canViewWorkPackageWatchers = function() {
return !!($scope.workPackage && $scope.workPackage.embedded.watchers !== undefined);
};
function displayedActivities(workPackage) {
var activities = workPackage.embedded.activities;
if ($scope.activitiesSortedInDescendingOrder) {
activities.reverse();
}
return activities;
}
// toggles
$scope.toggleStates = {
hideFullDescription: true,
hideAllAttributes: true
};
function getFocusAnchorLabel(tab, workPackage) {
var tabLabel = I18n.t('js.work_packages.tabs.' + tab),
params = {
tab: tabLabel,
type: workPackage.props.type,
subject: workPackage.props.subject
};
return I18n.t('js.label_work_package_details_you_are_here', params);
}
$scope.focusAnchorLabel = getFocusAnchorLabel(
$state.current.url.replace(/\//, ''),
$scope.workPackage
);
};

@ -49,6 +49,10 @@ module.exports = function($scope, $state, $stateParams, QueryService, PathHelper
return $state.includes('work-packages.list.details');
};
$scope.isShowViewActive = function() {
return $state.includes('work-packages.show');
};
$scope.getToggleActionLabel = function(active) {
return (active) ? I18n.t('js.label_deactivate') : I18n.t('js.label_activate');
};

@ -301,6 +301,15 @@ module.exports = function($scope, $rootScope, $state, $location, latestTab,
}
};
$scope.showWorkPackageShowView = function() {
var id = $state.params.workPackageId;
if (id) {
$state.go('work-packages.show.activity', {workPackageId: id});
} else {
$state.go('work-packages.show.activity', {workPackageId: $scope.preselectedWorkPackageId});
}
};
$scope.getFilterCount = function() {
if ($scope.query) {
var filters = $scope.query.filters;

Loading…
Cancel
Save