Merge pull request #1992 from emanuelschuetze/fixes1

Several template fixes
This commit is contained in:
Norman Jäckel 2016-02-23 22:36:50 +01:00
commit eecfc45dce
20 changed files with 126 additions and 59 deletions

View File

@ -13,7 +13,7 @@ Agenda:
- Changed API of related objects. All assignments, motions and custom slides - Changed API of related objects. All assignments, motions and custom slides
are now agenda items and can be hidden. are now agenda items and can be hidden.
- Removed mptt. - Removed mptt.
- Added attachments to agenda items. - Added attachments to custom sldies.
- Improved csv import. - Improved csv import.
Assignments: Assignments:
- Renamed app from assignment to assignments. - Renamed app from assignment to assignments.

View File

@ -11,6 +11,7 @@
"angular-animate": "~1.4.9", "angular-animate": "~1.4.9",
"angular-sanitize": "~1.4.9", "angular-sanitize": "~1.4.9",
"angular-bootstrap": "~0.14.3", "angular-bootstrap": "~0.14.3",
"angular-bootstrap-colorpicker": "~3.0.24",
"angular-csv-import": "~0.0.27", "angular-csv-import": "~0.0.27",
"angular-formly-templates-bootstrap": "~6.2.0", "angular-formly-templates-bootstrap": "~6.2.0",
"angular-formly": "~7.3.9", "angular-formly": "~7.3.9",

View File

@ -20,45 +20,18 @@ def validate_start_time(value):
def setup_agenda_config(sender, **kwargs): def setup_agenda_config(sender, **kwargs):
""" """
Receiver function to setup all agenda config variables. They are not Receiver function to setup all agenda config variables.
grouped. This function connected to the signal This function connected to the signal openslides.core.signals.config_signal
openslides.core.signals.config_signal during app loading. during app loading.
""" """
# TODO: Use an input type with generic datetime support.
yield ConfigVariable(
name='agenda_start_event_date_time',
default_value='',
label=ugettext_lazy('Begin of event'),
help_text=ugettext_lazy('Input format: DD.MM.YYYY HH:MM'),
weight=210,
group=ugettext_lazy('Agenda'),
validators=(validate_start_time,))
yield ConfigVariable(
name='agenda_show_last_speakers',
default_value=1,
input_type='integer',
label=ugettext_lazy('Number of last speakers to be shown on the projector'),
weight=220,
group=ugettext_lazy('Agenda'),
validators=(MinValueValidator(0),))
yield ConfigVariable(
name='agenda_couple_countdown_and_speakers',
default_value=False,
input_type='boolean',
label=ugettext_lazy('Couple countdown with the list of speakers'),
help_text=ugettext_lazy('[Begin speech] starts the countdown, [End speech] stops the countdown.'),
weight=230,
group=ugettext_lazy('Agenda'))
yield ConfigVariable( yield ConfigVariable(
name='agenda_number_prefix', name='agenda_number_prefix',
default_value='', default_value='',
label=ugettext_lazy('Numbering prefix for agenda items'), label=ugettext_lazy('Numbering prefix for agenda items'),
help_text=ugettext_lazy('This prefix will be set if you run the automatic agenda numbering.'), help_text=ugettext_lazy('This prefix will be set if you run the automatic agenda numbering.'),
weight=240, weight=210,
group=ugettext_lazy('Agenda'), group=ugettext_lazy('Agenda'),
subgroup=ugettext_lazy('General'),
validators=(MaxLengthValidator(20),)) validators=(MaxLengthValidator(20),))
yield ConfigVariable( yield ConfigVariable(
@ -69,8 +42,53 @@ def setup_agenda_config(sender, **kwargs):
choices=( choices=(
{'value': 'arabic', 'display_name': ugettext_lazy('Arabic')}, {'value': 'arabic', 'display_name': ugettext_lazy('Arabic')},
{'value': 'roman', 'display_name': ugettext_lazy('Roman')}), {'value': 'roman', 'display_name': ugettext_lazy('Roman')}),
weight=250, weight=215,
group=ugettext_lazy('Agenda')) group=ugettext_lazy('Agenda'),
subgroup=ugettext_lazy('General'))
# TODO: Use an input type with generic datetime support.
yield ConfigVariable(
name='agenda_start_event_date_time',
default_value='',
label=ugettext_lazy('Begin of event'),
help_text=ugettext_lazy('Input format: DD.MM.YYYY HH:MM'),
weight=220,
group=ugettext_lazy('Agenda'),
subgroup=ugettext_lazy('General'),
validators=(validate_start_time,))
# List of speakers
yield ConfigVariable(
name='agenda_show_last_speakers',
default_value=1,
input_type='integer',
label=ugettext_lazy('Number of last speakers to be shown on the projector'),
weight=230,
group=ugettext_lazy('Agenda'),
subgroup=ugettext_lazy('List of speakers'),
validators=(MinValueValidator(0),))
yield ConfigVariable(
name='agenda_countdown_warning_time',
default_value=0,
input_type='integer',
label=ugettext_lazy('Show orange countdown in the last x seconds of speaking time'),
help_text=ugettext_lazy('Enter duration in seconds. Choose 0 to disable warning color.'),
weight=235,
group=ugettext_lazy('Agenda'),
subgroup=ugettext_lazy('List of speakers'),
validators=(MinValueValidator(0),))
yield ConfigVariable(
name='agenda_couple_countdown_and_speakers',
default_value=False,
input_type='boolean',
label=ugettext_lazy('Couple countdown with the list of speakers'),
help_text=ugettext_lazy('[Begin speech] starts the countdown, [End speech] stops the countdown.'),
weight=240,
group=ugettext_lazy('Agenda'),
subgroup=ugettext_lazy('List of speakers'))
def listen_to_related_object_post_save(sender, instance, created, **kwargs): def listen_to_related_object_post_save(sender, instance, created, **kwargs):

View File

@ -209,8 +209,9 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
'operator', 'operator',
'Agenda', 'Agenda',
'User', 'User',
'LimitUsers',
'item', 'item',
function ($scope, $filter, $http, $state, operator, Agenda, User, item) { function ($scope, $filter, $http, $state, operator, Agenda, User, LimitUsers, item) {
Agenda.bindOne(item.id, $scope, 'item'); Agenda.bindOne(item.id, $scope, 'item');
User.bindAll({}, $scope, 'users'); User.bindAll({}, $scope, 'users');
$scope.speakerSelectBox = {}; $scope.speakerSelectBox = {};
@ -218,7 +219,7 @@ angular.module('OpenSlidesApp.agenda.site', ['OpenSlidesApp.agenda'])
$scope.speakers = $filter('orderBy')(item.speakers, 'weight'); $scope.speakers = $filter('orderBy')(item.speakers, 'weight');
// limit the number of users in ui-select field // limit the number of users in ui-select field
$scope.limitUsers = 50; $scope.LimitUsers = LimitUsers;
$scope.$watch(function () { $scope.$watch(function () {
return Agenda.lastModified(); return Agenda.lastModified();

View File

@ -135,7 +135,7 @@
<ui-select-match placeholder="{{ 'Select or search a participant ...' | translate }}"> <ui-select-match placeholder="{{ 'Select or search a participant ...' | translate }}">
{{ $select.selected.get_full_name() }} {{ $select.selected.get_full_name() }}
</ui-select-match> </ui-select-match>
<ui-select-choices repeat="user in users | filter: $select.search | limitTo: limitUsers"> <ui-select-choices repeat="user in users | filter: $select.search | limitTo: LimitUsers">
<div ng-bind-html="user.get_full_name() | highlight: $select.search"></div> <div ng-bind-html="user.get_full_name() | highlight: $select.search"></div>
</ui-select-choices> </ui-select-choices>
</ui-select> </ui-select>

View File

@ -268,9 +268,10 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
'operator', 'operator',
'Assignment', 'Assignment',
'User', 'User',
'LimitUsers',
'assignment', 'assignment',
'phases', 'phases',
function($scope, $http, filterFilter, gettext, ngDialog, AssignmentForm, operator, Assignment, User, assignment, phases) { function($scope, $http, filterFilter, gettext, ngDialog, AssignmentForm, operator, Assignment, User, LimitUsers, assignment, phases) {
User.bindAll({}, $scope, 'users'); User.bindAll({}, $scope, 'users');
Assignment.bindOne(assignment.id, $scope, 'assignment'); Assignment.bindOne(assignment.id, $scope, 'assignment');
Assignment.loadRelations(assignment, 'agenda_item'); Assignment.loadRelations(assignment, 'agenda_item');
@ -279,7 +280,7 @@ angular.module('OpenSlidesApp.assignments.site', ['OpenSlidesApp.assignments'])
$scope.alert = {}; $scope.alert = {};
// limit the number of users in ui-select field // limit the number of users in ui-select field
$scope.limitUsers = 50; $scope.LimitUsers = LimitUsers;
// open edit dialog // open edit dialog
$scope.openDialog = function (assignment) { $scope.openDialog = function (assignment) {

View File

@ -95,7 +95,7 @@
<ui-select-match placeholder="{{ 'Select or search a participant ...' | translate }}"> <ui-select-match placeholder="{{ 'Select or search a participant ...' | translate }}">
{{ $select.selected.get_full_name() }} {{ $select.selected.get_full_name() }}
</ui-select-match> </ui-select-match>
<ui-select-choices repeat="user in users | filter: $select.search | limitTo: limitUsers"> <ui-select-choices repeat="user in users | filter: $select.search | limitTo: LimitUsers">
<div ng-bind-html="user.get_full_name() | highlight: $select.search"></div> <div ng-bind-html="user.get_full_name() | highlight: $select.search"></div>
</ui-select-choices> </ui-select-choices>
</ui-select> </ui-select>

View File

@ -9,7 +9,8 @@ INPUT_TYPE_MAPPING = {
'text': str, 'text': str,
'integer': int, 'integer': int,
'boolean': bool, 'boolean': bool,
'choice': str} 'choice': str,
'colorpicker': str}
class ConfigHandler: class ConfigHandler:

View File

@ -154,6 +154,7 @@ def setup_general_config(sender, **kwargs):
yield ConfigVariable( yield ConfigVariable(
name='projector_backgroundcolor', name='projector_backgroundcolor',
default_value='#317796', default_value='#317796',
input_type='colorpicker',
label=ugettext_lazy('Background color of projector header'), label=ugettext_lazy('Background color of projector header'),
help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'), help_text=ugettext_lazy('Use web color names like "red" or hex numbers like "#ff0000".'),
weight=160, weight=160,

View File

@ -466,6 +466,10 @@ img {
padding-right: 10px; padding-right: 10px;
} }
.col2 .countdown_timer.warning {
color: #ed940d;
}
.col2 .countdown_timer.negative { .col2 .countdown_timer.negative {
color: #CC0000; color: #CC0000;
} }

View File

@ -207,9 +207,13 @@ hr {
margin-top: 20px; margin-top: 20px;
padding-right: 5px; padding-right: 5px;
} }
.countdown.warning {
color: #ed940d;
}
.countdown.negative { .countdown.negative {
color: #CC0000; color: #CC0000;
} }
.message_background { .message_background {
background-color: #777777; background-color: #777777;
opacity: 0.8; opacity: 0.8;

View File

@ -7,6 +7,7 @@ angular.module('OpenSlidesApp.core.site', [
'OpenSlidesApp.core', 'OpenSlidesApp.core',
'ui.router', 'ui.router',
'angular-loading-bar', 'angular-loading-bar',
'colorpicker.module',
'formly', 'formly',
'formlyBootstrap', 'formlyBootstrap',
'ngBootbox', 'ngBootbox',
@ -402,6 +403,7 @@ angular.module('OpenSlidesApp.core.site', [
integer: 'number', integer: 'number',
boolean: 'checkbox', boolean: 'checkbox',
choice: 'choice', choice: 'choice',
colorpicker: 'colorpicker',
}[type]; }[type];
} }
@ -989,9 +991,10 @@ angular.module('OpenSlidesApp.core.site', [
.controller('ChatMessageCtrl', [ .controller('ChatMessageCtrl', [
'$scope', '$scope',
'$http', '$http',
'$timeout',
'ChatMessage', 'ChatMessage',
'NewChatMessages', 'NewChatMessages',
function ($scope, $http, ChatMessage, NewChatMessages) { function ($scope, $http, $timeout, ChatMessage, NewChatMessages) {
ChatMessage.bindAll({}, $scope, 'chatmessages'); ChatMessage.bindAll({}, $scope, 'chatmessages');
$scope.unreadMessages = NewChatMessages.length; $scope.unreadMessages = NewChatMessages.length;
$scope.chatboxIsCollapsed = true; $scope.chatboxIsCollapsed = true;
@ -999,6 +1002,9 @@ angular.module('OpenSlidesApp.core.site', [
$scope.chatboxIsCollapsed = !$scope.chatboxIsCollapsed; $scope.chatboxIsCollapsed = !$scope.chatboxIsCollapsed;
NewChatMessages = []; NewChatMessages = [];
$scope.unreadMessages = NewChatMessages.length; $scope.unreadMessages = NewChatMessages.length;
$timeout(function () {
angular.element('#messageInput').focus();
}, 0);
}; };
$scope.sendMessage = function () { $scope.sendMessage = function () {
angular.element('#messageSendButton').addClass('disabled'); angular.element('#messageSendButton').addClass('disabled');
@ -1011,6 +1017,9 @@ angular.module('OpenSlidesApp.core.site', [
$scope.newMessage = ''; $scope.newMessage = '';
angular.element('#messageSendButton').removeClass('disabled'); angular.element('#messageSendButton').removeClass('disabled');
angular.element('#messageInput').removeAttr('disabled'); angular.element('#messageInput').removeAttr('disabled');
$timeout(function () {
angular.element('#messageInput').focus();
}, 0);
}) })
.error(function () { .error(function () {
angular.element('#messageSendButton').removeClass('disabled'); angular.element('#messageSendButton').removeClass('disabled');
@ -1031,6 +1040,9 @@ angular.module('OpenSlidesApp.core.site', [
} }
]) ])
// define maximum number of users shown in users select fields (e.g. in motion or speakers forms)
.value('LimitUsers', 50)
.directive('osFocusMe', [ .directive('osFocusMe', [
'$timeout', '$timeout',
function ($timeout) { function ($timeout) {

View File

@ -2,19 +2,30 @@
<label>{{ label }}</label> <label>{{ label }}</label>
<div class="input-group"> <div class="input-group">
<input ng-if="type != 'choice' && type != 'textarea'" <!-- text/number input, checkbox -->
<input ng-if="type == 'text' || type == 'number' || type == 'checkbox'"
ng-model="$parent.value" ng-model="$parent.value"
ng-change="save(configOption.key, $parent.value)" ng-change="save(configOption.key, $parent.value)"
ng-class="{ 'form-control': type != 'checkbox' }" ng-class="{ 'form-control': type != 'checkbox' }"
id="{{ key }}" id="{{ key }}"
type="{{ type }}"> type="{{ type }}">
<!-- colorpicker -->
<input ng-if="type == 'colorpicker'"
colorpicker
class="form-control"
ng-model="$parent.value"
ng-change="save(configOption.key, $parent.value)"
type="text">
<!-- textarea -->
<textarea ng-if="type == 'textarea'" <textarea ng-if="type == 'textarea'"
ng-model="$parent.value" ng-model="$parent.value"
ng-change="save(configOption.key, $parent.value)" ng-change="save(configOption.key, $parent.value)"
id="{{ key }}" class="form-control"> id="{{ key }}" class="form-control">
</textarea> </textarea>
<!-- select -->
<select ng-if="type == 'choice'" <select ng-if="type == 'choice'"
ng-model="$parent.value" ng-model="$parent.value"
ng-change="save(configOption.key, $parent.value)" ng-change="save(configOption.key, $parent.value)"

View File

@ -122,7 +122,9 @@
<i class="fa fa-pause"></i> <i class="fa fa-pause"></i>
</a> </a>
<span ng-if="!editTime" class="countdown_timer vcenter" <span ng-if="!editTime" class="countdown_timer vcenter"
ng-class="{ 'negative': countdown.seconds < 0 }"> ng-class="{
'negative': countdown.seconds <= 0,
'warning': countdown.seconds <= config('agenda_countdown_warning_time') && countdown.seconds > 0 }">
{{ countdown.seconds | osSecondsToTime }} {{ countdown.seconds | osSecondsToTime }}
</span> </span>
<!-- edit countdown form --> <!-- edit countdown form -->

View File

@ -1,6 +1,9 @@
<div ng-controller="SlideCountdownCtrl"> <div ng-controller="SlideCountdownCtrl">
<div ng-if="visible"> <div ng-if="visible">
<div class="countdown well" style="margin-top: calc({{index}}*100px);" ng-class="{ 'negative': seconds < 0 }"> <div class="countdown well" style="margin-top: calc({{index}}*100px);"
ng-class="{
'negative': seconds <= 0,
'warning': seconds <= config('agenda_countdown_warning_time') && seconds > 0 }">
{{ seconds | osSecondsToTime}} {{ seconds | osSecondsToTime}}
<div class="description">{{ description }}</div> <div class="description">{{ description }}</div>
</div> </div>

View File

@ -141,7 +141,7 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
id: mediafile.id, id: mediafile.id,
numPages: mediafile.mediafile.pages, numPages: mediafile.mediafile.pages,
page: 1, page: 1,
scale: 1, scale: 'page-fit',
rotate: 0, rotate: 0,
visible: true visible: true
}]; }];
@ -222,11 +222,13 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
'$scope', '$scope',
'MediafileForm', 'MediafileForm',
'User', 'User',
function($scope, MediafileForm, User) { 'LimitUsers',
function($scope, MediafileForm, User, LimitUsers) {
User.bindAll({}, $scope, 'users'); User.bindAll({}, $scope, 'users');
$scope.mediafile = {}; $scope.mediafile = {};
$scope.alert = {}; $scope.alert = {};
$scope.users = User.getAll(); $scope.users = User.getAll();
$scope.LimitUsers = LimitUsers;
// upload and save mediafile // upload and save mediafile
$scope.save = function (mediafile) { $scope.save = function (mediafile) {
@ -251,11 +253,13 @@ angular.module('OpenSlidesApp.mediafiles.site', ['ngFileUpload', 'OpenSlidesApp.
'operator', 'operator',
'Mediafile', 'Mediafile',
'User', 'User',
'LimitUsers',
'mediafile', 'mediafile',
function($scope, operator, Mediafile, User, mediafile) { function($scope, operator, Mediafile, User, LimitUsers, mediafile) {
User.bindAll({}, $scope, 'users'); User.bindAll({}, $scope, 'users');
$scope.alert = {}; $scope.alert = {};
$scope.users = User.getAll(); $scope.users = User.getAll();
$scope.LimitUsers = LimitUsers;
// set initial values for form model by create deep copy of motion object // set initial values for form model by create deep copy of motion object
// so list/detail view is not updated while editing // so list/detail view is not updated while editing

View File

@ -28,7 +28,7 @@
<ui-select-match placeholder="{{ 'Select or search a participant ...' | translate }}" data-allow-clear="true"> <ui-select-match placeholder="{{ 'Select or search a participant ...' | translate }}" data-allow-clear="true">
{{ $select.selected.get_full_name() }} {{ $select.selected.get_full_name() }}
</ui-select-match> </ui-select-match>
<ui-select-choices repeat="user.id as user in users | filter: $select.search | limitTo: 50"> <ui-select-choices repeat="user.id as user in users | filter: $select.search | limitTo: LimitUsers">
<div ng-bind-html="user.get_full_name() | highlight: $select.search"></div> <div ng-bind-html="user.get_full_name() | highlight: $select.search"></div>
</ui-select-choices> </ui-select-choices>
</ui-select> </ui-select>

View File

@ -29,10 +29,12 @@
<nav ng-class="getNavStyle(scroll)" class="form-inline"> <nav ng-class="getNavStyle(scroll)" class="form-inline">
<div class="btn-group"> <div class="btn-group">
<button class="btn btn-default" ng-click="mediafileGoToPage(presentedMediafile.page - 1)" <button class="btn btn-default" ng-click="mediafileGoToPage(presentedMediafile.page - 1)"
ng-class="{ 'disabled': (presentedMediafile.page - 1) < 1 }"
title="{{ 'Previous page' | translate }}"> title="{{ 'Previous page' | translate }}">
<i class="fa fa-backward"></i> <i class="fa fa-backward"></i>
</button> </button>
<button class="btn btn-default" ng-click="mediafileGoToPage(presentedMediafile.page + 1)" <button class="btn btn-default" ng-click="mediafileGoToPage(presentedMediafile.page + 1)"
ng-class="{ 'disabled': (presentedMediafile.page + 1) > presentedMediafile.numPages }"
title="{{ 'Next page' | translate }}"> title="{{ 'Next page' | translate }}">
<i class="fa fa-forward"></i> <i class="fa fa-forward"></i>
</button> </button>
@ -52,8 +54,9 @@
<button class="btn btn-default" ng-click="mediafileZoomOut()" title="{{ 'Zoom out' | translate }}"> <button class="btn btn-default" ng-click="mediafileZoomOut()" title="{{ 'Zoom out' | translate }}">
<i class="fa fa-search-minus"></i> <i class="fa fa-search-minus"></i>
</button> </button>
<button class="btn btn-default" ng-click="mediafileFit()" title="{{ 'Reset zoom' | translate }}"> <button class="btn" ng-click="mediafileFit()" title="{{ 'Reset zoom' | translate }}"
100% ng-class="presentedMediafile.scale=='page-fit' ? 'btn-primary' : 'btn-default'">
100%
</button> </button>
<button class="btn btn-default" ng-click="mediafileZoomIn()" title="{{ 'Zoom in' | translate }}"> <button class="btn btn-default" ng-click="mediafileZoomIn()" title="{{ 'Zoom in' | translate }}">
<i class="fa fa-search-plus"></i> <i class="fa fa-search-plus"></i>

View File

@ -159,7 +159,8 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
'Tag', 'Tag',
'User', 'User',
'Workflow', 'Workflow',
function (gettextCatalog, operator, Editor, Category, Config, Mediafile, Tag, User, Workflow) { 'LimitUsers',
function (gettextCatalog, operator, Editor, Category, Config, Mediafile, Tag, User, Workflow, LimitUsers) {
return { return {
// ngDialog for motion form // ngDialog for motion form
getDialog: function (motion) { getDialog: function (motion) {
@ -207,7 +208,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
label: gettextCatalog.getString('Submitters'), label: gettextCatalog.getString('Submitters'),
optionsAttr: 'bs-options', optionsAttr: 'bs-options',
options: User.getAll(), options: User.getAll(),
ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search | limitTo: 50', ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search | limitTo: ' + LimitUsers,
valueProp: 'id', valueProp: 'id',
labelProp: 'full_name', labelProp: 'full_name',
placeholder: gettextCatalog.getString('Select or search a submitter ...') placeholder: gettextCatalog.getString('Select or search a submitter ...')
@ -318,7 +319,7 @@ angular.module('OpenSlidesApp.motions.site', ['OpenSlidesApp.motions'])
label: gettextCatalog.getString('Supporters'), label: gettextCatalog.getString('Supporters'),
optionsAttr: 'bs-options', optionsAttr: 'bs-options',
options: User.getAll(), options: User.getAll(),
ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search | limitTo: 50', ngOptions: 'option[to.valueProp] as option in to.options | filter: $select.search | limitTo: ' + LimitUsers,
valueProp: 'id', valueProp: 'id',
labelProp: 'full_name', labelProp: 'full_name',
placeholder: gettextCatalog.getString('Select or search a supporter ...') placeholder: gettextCatalog.getString('Select or search a supporter ...')

View File

@ -56,15 +56,15 @@
placeholder="{{ 'Search' | translate}}"> placeholder="{{ 'Search' | translate}}">
</div> </div>
</div> </div>
<button os-perms="users.can_see_extra_data" class="btn btn-default" ng-click="isFilterOpen = !isFilterOpen" <button os-perms="users.can_see_extra_data" class="btn btn-default" ng-click="$parent.isFilterOpen = !$parent.isFilterOpen"
ng-class="isFilterOpen ? 'btn-primary' : 'btn-default'"> ng-class="$parent.isFilterOpen ? 'btn-primary' : 'btn-default'">
<i class="fa fa-filter"></i> <i class="fa fa-filter"></i>
<translate>Filter ...</translate> <translate>Filter ...</translate>
</button> </button>
</div> </div>
</div> </div>
</div> </div>
<div os-perms="users.can_see_extra_data" uib-collapse="!isFilterOpen" class="row spacer"> <div os-perms="users.can_see_extra_data" uib-collapse="!$parent.isFilterOpen" class="row spacer">
<div class="col-sm-6 text-right"></div> <div class="col-sm-6 text-right"></div>
<div class="col-sm-6 text-right form-inline"> <div class="col-sm-6 text-right form-inline">
<!-- group filter --> <!-- group filter -->