Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,23 @@ export abstract class BaseSortListService<V extends BaseViewModel>
return this.sortDefinition?.sortAscending;
}

/**
* Set the additional infos regarding sorting order
*
* @param additional info, can be any JSON serializable value
*/
public set additionalInfo(additional: unknown) {
this.sortDefinition!.additionalInfo = additional;
this.updateSortDefinitions();
}

/**
* @returns Additional info for sorting order
*/
public get additionalInfo(): unknown {
return this.sortDefinition?.additionalInfo;
}

public get hasSortOptionSelected(): boolean {
const defaultDef = this._defaultDefinitionSubject.value;
const current = this.sortDefinition;
Expand Down Expand Up @@ -192,7 +209,7 @@ export abstract class BaseSortListService<V extends BaseViewModel>
.pipe(distinctUntilChanged((prev, curr) => prev?.sortProperty === curr?.sortProperty))
.subscribe(defaultDef => {
if (this._isDefaultSorting && defaultDef) {
this.setSorting(defaultDef.sortProperty, defaultDef.sortAscending);
this.setSorting(defaultDef.sortProperty, defaultDef.sortAscending, defaultDef.additionalInfo);
} else if (defaultDef && this.sortDefinition?.sortProperty === defaultDef?.sortProperty) {
this.updateSortDefinitions();
}
Expand Down Expand Up @@ -254,12 +271,13 @@ export abstract class BaseSortListService<V extends BaseViewModel>
* @param property a sorting property of a view model
* @param ascending ascending or descending
*/
public setSorting(property: OsSortProperty<V>, ascending: boolean): void {
public setSorting(property: OsSortProperty<V>, ascending: boolean, additionalInfo?: unknown): void {
if (!this.sortDefinition) {
this.sortDefinition = { sortProperty: property, sortAscending: ascending };
this.sortDefinition = { sortProperty: property, sortAscending: ascending, additionalInfo };
} else {
this.sortDefinition!.sortProperty = property;
this.sortDefinition!.sortAscending = ascending;
this.sortDefinition!.additionalInfo = additionalInfo;
this.updateSortDefinitions();
}
this.hasLoaded.resolve(true);
Expand Down Expand Up @@ -369,31 +387,36 @@ export abstract class BaseSortListService<V extends BaseViewModel>
}

private async loadDefinition(): Promise<void> {
let [sortProperty, sortAscending]: [OsSortProperty<V>, boolean] = await Promise.all([
let [sortProperty, sortAscending, additionalInfo]: [OsSortProperty<V>, boolean, any] = await Promise.all([
this.store.get<OsSortProperty<V>>(this.calcStorageKey(`sorting_property`, this.storageKey)),
this.store.get<boolean>(this.calcStorageKey(`sorting_ascending`, this.storageKey))
this.store.get<boolean>(this.calcStorageKey(`sorting_ascending`, this.storageKey)),
this.store.get<any>(this.calcStorageKey(`sorting_additional_info`, this.storageKey))
]);

const defaultDef = await this.getDefaultDefinition();
sortAscending = sortAscending ?? defaultDef.sortAscending;
sortProperty = sortProperty ?? defaultDef.sortProperty;
additionalInfo = additionalInfo ?? defaultDef.additionalInfo;
this.sortDefinition = {
sortAscending,
sortProperty
sortProperty,
additionalInfo
};
this.updateSortDefinitions();
this.hasLoaded.resolve(true);
}

private async setSortingAfterMeetingChange(meetingId: Id): Promise<void> {
let [sortProperty, sortAscending]: [OsSortProperty<V>, boolean] = await Promise.all([
let [sortProperty, sortAscending, additionalInfo]: [OsSortProperty<V>, boolean, any] = await Promise.all([
this.store.get<OsSortProperty<V>>(`sorting_property_${this.storageKey}_${meetingId}`),
this.store.get<boolean>(`sorting_ascending_${this.storageKey}_${meetingId}`)
this.store.get<boolean>(`sorting_ascending_${this.storageKey}_${meetingId}`),
this.store.get<any>(`sorting_additional_info_${this.storageKey}_${meetingId}`)
]);
const defaultDef = await this.getDefaultDefinition();
sortProperty = sortProperty ?? defaultDef.sortProperty;
sortAscending = sortAscending ?? defaultDef.sortAscending;
this.setSorting(sortProperty, sortAscending);
additionalInfo = additionalInfo ?? defaultDef.additionalInfo;
this.setSorting(sortProperty, sortAscending, additionalInfo);
}

/**
Expand All @@ -404,7 +427,7 @@ export abstract class BaseSortListService<V extends BaseViewModel>
return Array.isArray(a) && Array.isArray(b) ? a.equals(b) : a === b;
}

private compareHelperFunction(itemA: V, itemB: V, alternativeProperty: OsSortProperty<V>): number {
protected compareHelperFunction(itemA: V, itemB: V, alternativeProperty: OsSortProperty<V>): number {
return (
this.sortItems(
itemA,
Expand All @@ -427,6 +450,10 @@ export abstract class BaseSortListService<V extends BaseViewModel>
this.store.set(this.calcStorageKey(`sorting_property`, this.storageKey), this.sortDefinition?.sortProperty);
}
this.store.set(this.calcStorageKey(`sorting_ascending`, this.storageKey), this.sortDefinition?.sortAscending);
this.store.set(
this.calcStorageKey(`sorting_additional_info`, this.storageKey),
this.sortDefinition?.additionalInfo
);
}

private calculateDefaultStatus(): void {
Expand Down
1 change: 1 addition & 0 deletions client/src/app/site/base/base-sort.service/os-sort.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type SortDefinition<T> = keyof T | OsSortingDefinition<T>;
export interface OsSortingDefinition<T> {
sortProperty: OsSortProperty<T>;
sortAscending: boolean;
additionalInfo?: unknown;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { MatButtonModule } from '@angular/material/button';
import { MatDividerModule } from '@angular/material/divider';
import { MatIconModule } from '@angular/material/icon';
import { MatMenuModule } from '@angular/material/menu';
import { MatTooltipModule } from '@angular/material/tooltip';
import { OpenSlidesTranslationModule } from 'src/app/site/modules/translations';
import { DirectivesModule } from 'src/app/ui/directives';
import { ChipComponent } from 'src/app/ui/modules/chip';
Expand Down Expand Up @@ -32,6 +33,7 @@ import { CommitteeListServiceModule } from './services/committee-list-service.mo
OpenSlidesTranslationModule.forChild(),
MatDividerModule,
MatMenuModule,
MatTooltipModule,
MatIconModule,
MatButtonModule,
DirectivesModule
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,25 @@ <h1 class="mock-h2" translate>Committees</h1>
<button mat-icon-button (click)="toggleMultiSelect()"><mat-icon>arrow_back</mat-icon></button>
<span>{{ selectedRows.length }}&nbsp;{{ 'selected' | translate }}</span>
</ng-container>

<div class="extra-controls-slot">
<div>
@if (selectedView === 'hierarchy') {
<button mat-button matTooltip="{{ 'List view' | translate }}" (click)="onChangeView('list')">
<mat-icon>format_align_justify</mat-icon>
</button>
} @else {
<button mat-button matTooltip="{{ 'Hierarchy view' | translate }}" (click)="onChangeView('hierarchy')">
<mat-icon>format_align_right</mat-icon>
</button>
}
</div>
</div>
</os-head-bar>

<os-list
[alwaysShowMenu]="true"
[class.committee-hierarchy]="selectedView === 'hierarchy'"
[filterProps]="['name']"
[filterService]="filterService"
[hiddenInMobile]="['forwarding', 'managers', 'meta']"
Expand All @@ -34,10 +49,32 @@ <h1 class="mock-h2" translate>Committees</h1>
[sortService]="sortService"
[(selectedRows)]="selectedRows"
>
<div *osScrollingTableCell="'name'; row as committee" class="cell-slot fill">
<div
*osScrollingTableCell="'name'; row as committee"
class="cell-slot fill cell-title"
[class.is-child]="!!committee.all_parent_ids?.length"
[class.no-children]="!committee.child_ids?.length"
[style.--committee-level]="committee.all_parent_ids?.length || 0"
>
@if (!isMultiSelect && committee.canAccess()) {
<a class="stretch-to-fill-parent" [attr.aria-label]="ariaLabel(committee)" [routerLink]="committee.id"></a>
}
@if (selectedView === 'hierarchy') {
@if (committee.child_ids?.length) {
<button
class="toggle-button"
mat-icon-button
[attr.aria-label]="'Toggle sub committee expand' | translate"
(click)="filterService.toggleExpanded(committee.id)"
>
<mat-icon>
{{ filterService.isExpanded(committee.id) ? 'keyboard_arrow_down' : 'chevron_right' }}
</mat-icon>
</button>
}

<span class="hl"></span>
}
<div class="overflow-hidden">
<div class="title-line ellipsis-overflow">
{{ committee.name }}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,77 @@
flex-flow: column;
width: 75px;
}

.committee-hierarchy {
--committee-level-padding: 38px;
.cell-title:not(.is-child) {
&:after,
&:before {
content: '';
display: inline-block;
height: 30%;
border: solid #ddd;
border-width: 0 0 2px 2px;
margin-right: 8px;
}
&:after {
position: absolute;
left: 0;
bottom: 0;
border-width: 0 0 0 2px;
}
}

.cell-title.is-child {
padding-left: calc(var(--committee-level) * var(--committee-level-padding));

&:before,
&:after {
content: '';
display: inline-block;
height: 30%;
border: solid #ddd;
border-width: 0 0 2px 2px;
margin-right: 8px;
}
&:after {
position: absolute;
left: calc(var(--committee-level) * var(--committee-level-padding));
bottom: 0;
border-width: 0 0 0 2px;
}
}

.cell-title .stretch-to-fill-parent {
left: calc(calc(var(--committee-level) * var(--committee-level-padding)) + 29px);
}

.cell-title .toggle-button {
position: absolute;
top: 0;
bottom: 0;
left: calc(calc(var(--committee-level) * var(--committee-level-padding)) - 23px);
}

.cell-title .hl {
display: inline-block;
width: 24px;
border: solid #ddd;
border-width: 0 0 2px 0;
margin-right: 10px;
margin-left: 10px;
}

.cell-title.no-children {
&:after,
&:before {
height: 50%;
margin-right: 0px;
}

.hl {
margin-left: 0px;
width: 42px;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ export class CommitteeListComponent extends BaseListViewComponent<ViewCommittee>
return this.translate.instant(`Agenda items are in process. Please wait ...`);
}

private _selectedView = `list`;

public get selectedView(): string {
return this._selectedView;
}

public set selectedView(value) {
this._selectedView = value;
this.filterService.hierarchyFilterActive = value === 'hierarchy';
}

public constructor(
protected override translate: TranslateService,
public committeeController: CommitteeControllerService,
Expand All @@ -50,6 +61,15 @@ export class CommitteeListComponent extends BaseListViewComponent<ViewCommittee>
super.setTitle(`Committees`);
this.canMultiSelect = true;
this.listStorageIndex = COMMITTEE_LIST_STORAGE_INDEX;
this.subscriptions.push(
this.sortService.hierarchySort.subscribe(active => {
this.selectedView = active ? `hierarchy` : `list`;
})
);
}

public onChangeView(type: string): void {
this.sortService.hierarchySort = type === `hierarchy`;
}

public editSingle(committee: ViewCommittee): void {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Injectable } from '@angular/core';
import { inject, Injectable } from '@angular/core';
import { _ } from '@ngx-translate/core';
import { map, Observable } from 'rxjs';
import { Id } from 'src/app/domain/definitions/key-types';
import { StorageService } from 'src/app/gateways/storage.service';
import { BaseFilterListService, OsFilter } from 'src/app/site/base/base-filter.service';
import { OrganizationTagControllerService } from 'src/app/site/pages/organization/pages/organization-tags/services/organization-tag-controller.service';
import { ActiveFiltersService } from 'src/app/site/services/active-filters.service';
Expand All @@ -13,13 +16,27 @@ import { CommitteeListServiceModule } from '../committee-list-service.module';
export class CommitteeFilterService extends BaseFilterListService<ViewCommittee> {
protected storageKey = `CommitteeList`;

private _hierarchyFilterActive = false;

public get hierarchyFilterActive(): boolean {
return this._hierarchyFilterActive;
}

public set hierarchyFilterActive(value) {
this._hierarchyFilterActive = value;
}

private expandedCommittees: Set<number> = new Set<number>();

private orgaTagFilterOptions: OsFilter<ViewCommittee> = {
property: `organization_tag_ids`,
label: _(`Tags`),
isAndConnected: true,
options: []
};

private store = inject(StorageService);

public constructor(organizationTagRepo: OrganizationTagControllerService, store: ActiveFiltersService) {
super(store);
this.updateFilterForRepo({
Expand Down Expand Up @@ -50,4 +67,48 @@ export class CommitteeFilterService extends BaseFilterListService<ViewCommittee>
this.orgaTagFilterOptions
];
}

public override get outputObservable(): Observable<ViewCommittee[]> {
return super.outputObservable.pipe(
map(output => {
if (this.hierarchyFilterActive) {
return output.filter(
el =>
!el.all_parent_ids?.length ||
el.all_parent_ids?.every(id => this.expandedCommittees.has(id))
);
}

return output;
})
);
}

public override storeActiveFilters(): void {
super.storeActiveFilters();
this.store.set(`${this.storageKey}_expanded`, Array.from(this.expandedCommittees));
}

public override async initFilters(inputData: Observable<ViewCommittee[]>): Promise<void> {
const expanded = await this.store.get<number[]>(`${this.storageKey}_expanded`);
if (expanded) {
this.expandedCommittees = new Set(expanded);
}

await super.initFilters(inputData);
}

public isExpanded(id: Id): boolean {
return this.expandedCommittees.has(id);
}

public toggleExpanded(id: Id): void {
if (this.expandedCommittees.has(id)) {
this.expandedCommittees.delete(id);
} else {
this.expandedCommittees.add(id);
}

this.storeActiveFilters();
}
}
Loading
Loading