From abc043b56b5fc1761243bfd5b68422a1b9b724f2 Mon Sep 17 00:00:00 2001 From: luisa-beerboom <101706784+luisa-beerboom@users.noreply.github.com> Date: Wed, 27 Sep 2023 14:43:47 +0200 Subject: [PATCH 01/11] Fix scrolling table viewport resizing (#2812) --- .../scrolling-table/scrolling-table.component.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts index d146509acc..6d12e156fe 100644 --- a/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts +++ b/client/src/app/ui/modules/scrolling-table/components/scrolling-table/scrolling-table.component.ts @@ -141,6 +141,8 @@ export class ScrollingTableComponent>> private _dataSource = new BehaviorSubject([]); private _dataSourceMap: Mapable> = {}; + private _oldDistTop = 0; + public constructor(private manageService: ScrollingTableManageService, private cd: ChangeDetectorRef) { super(); } @@ -228,6 +230,12 @@ export class ScrollingTableComponent>> if (this.cdkContainer) { const distTop = this.cdkContainer.nativeElement.getBoundingClientRect().top; + if (this._oldDistTop > distTop) { + setTimeout(() => { + this.scrollViewport?.checkViewportSize(); + }, 10); + } + this._oldDistTop = distTop; return `calc(100vh - ${distTop}px)`; } From 04d1fc2c77c66fb7d7ef2e246b25c9208deedc8a Mon Sep 17 00:00:00 2001 From: rrenkert Date: Thu, 28 Sep 2023 16:16:07 +0200 Subject: [PATCH 02/11] Add filter for gender in participant list and fix duplicated group ids in multiselect (#2813) --- client/src/app/domain/models/users/user.ts | 1 + .../participant-list/participant-list.component.ts | 2 +- .../participant-list-filter.service.ts | 12 ++++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/client/src/app/domain/models/users/user.ts b/client/src/app/domain/models/users/user.ts index 4edac37618..95158d3e87 100644 --- a/client/src/app/domain/models/users/user.ts +++ b/client/src/app/domain/models/users/user.ts @@ -12,6 +12,7 @@ export type UserSortProperty = 'first_name' | 'last_name' | 'number'; * Iterable pre selection of genders */ export const GENDERS = [_(`female`), _(`male`), _(`diverse`), _(`non-binary`)]; +export const GENDER_FITLERABLE = [`female`, `male`, `diverse`, `non-binary`]; /** * Representation of a user in contrast to the operator. diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.ts index 7a814862d4..dce231c3c8 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/components/participant-list/participant-list.component.ts @@ -275,7 +275,7 @@ export class ParticipantListComponent extends BaseMeetingListViewComponent this.activeMeeting.default_group_id !== id); return { id: user.id, - group_ids: nextGroupIds.concat(chosenGroupIds) + group_ids: [...new Set(nextGroupIds.concat(chosenGroupIds))] }; }, ...this.selectedRows) .resolve(); diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/services/participant-list-filter.service/participant-list-filter.service.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/services/participant-list-filter.service/participant-list-filter.service.ts index 9b7faa2cd1..7a2fd360e2 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/services/participant-list-filter.service/participant-list-filter.service.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-list/services/participant-list-filter.service/participant-list-filter.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; +import { GENDER_FITLERABLE, GENDERS } from 'src/app/domain/models/users/user'; import { OsFilter, OsHideFilterSetting } from 'src/app/site/base/base-filter.service'; import { BaseMeetingFilterListService } from 'src/app/site/pages/meetings/base/base-meeting-filter-list.service'; import { MeetingActiveFiltersService } from 'src/app/site/pages/meetings/services/meeting-active-filters.service'; @@ -98,6 +99,17 @@ export class ParticipantListFilterService extends BaseMeetingFilterListService Date: Thu, 28 Sep 2023 16:20:38 +0200 Subject: [PATCH 03/11] Minor cleanup #2 (#2835) --- .../account-button.component.ts | 14 +++- .../global-headbar/global-headbar.module.ts | 2 + .../base-game-dialog/base-game-dialog.ts | 18 +++-- .../chess-dialog/chess-dialog.module.ts | 11 ++- .../chess-dialog/chess-dialog.component.html | 11 +-- .../chess-dialog/chess-dialog.component.ts | 74 +++++++++++++++---- .../services/chess-challenge.service.ts | 40 ++++++++++ 7 files changed, 136 insertions(+), 34 deletions(-) create mode 100644 client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/services/chess-challenge.service.ts diff --git a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts index 2e8e7fe6df..29c6c7a13c 100644 --- a/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts +++ b/client/src/app/site/modules/global-headbar/components/account-button/account-button.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit, ViewChild } from '@angular/core'; -import { MatDialog } from '@angular/material/dialog'; +import { MatDialog, MatDialogConfig } from '@angular/material/dialog'; import { MatMenuTrigger } from '@angular/material/menu'; import { Router } from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; @@ -18,6 +18,7 @@ import { ThemeService } from 'src/app/site/services/theme.service'; import { UserControllerService } from 'src/app/site/services/user-controller.service'; import { BaseUiComponent } from 'src/app/ui/base/base-ui-component'; import { ChessDialogComponent } from 'src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/components/chess-dialog/chess-dialog.component'; +import { ChessChallengeService } from 'src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/services/chess-challenge.service'; import { AccountDialogComponent } from '../account-dialog/account-dialog.component'; @@ -81,9 +82,11 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { private theme: ThemeService, private meetingSettingsService: MeetingSettingsService, private activeMeetingIdService: ActiveMeetingIdService, - private controller: UserControllerService + private controller: UserControllerService, + chessChallengeService: ChessChallengeService ) { super(); + chessChallengeService.startListening(); } public ngOnInit(): void { @@ -159,7 +162,12 @@ export class AccountButtonComponent extends BaseUiComponent implements OnInit { if (this.clickCounter === 4) { this.clickCounter = 0; - this.dialog.open(ChessDialogComponent, { ...mediumDialogSettings }); + const config: MatDialogConfig = mediumDialogSettings; + const match = this.router.url.match(/.*\/participants\/(\d+)\/?$/); + if (match) { + config.data = { userId: +match[1] }; + } + this.dialog.open(ChessDialogComponent, config); } else { this.clickTimeout = setTimeout(() => { this.clickCounter = 0; diff --git a/client/src/app/site/modules/global-headbar/global-headbar.module.ts b/client/src/app/site/modules/global-headbar/global-headbar.module.ts index 50d8979428..526aeaec5a 100644 --- a/client/src/app/site/modules/global-headbar/global-headbar.module.ts +++ b/client/src/app/site/modules/global-headbar/global-headbar.module.ts @@ -19,6 +19,7 @@ import { RouterModule } from '@angular/router'; import { DirectivesModule } from 'src/app/ui/directives'; import { CommaSeparatedListingModule } from 'src/app/ui/modules/comma-separated-listing'; import { InputModule } from 'src/app/ui/modules/input'; +import { ChessDialogModule } from 'src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog'; import { OpenSlidesTranslationModule } from '../translations'; import { UserComponentsModule } from '../user-components'; @@ -58,6 +59,7 @@ const DECLARATIONS = [GlobalHeadbarComponent]; ScrollingModule, FormsModule, ReactiveFormsModule, + ChessDialogModule, ...MODULES ] }) diff --git a/client/src/app/ui/modules/sidenav/modules/easter-egg/components/base-game-dialog/base-game-dialog.ts b/client/src/app/ui/modules/sidenav/modules/easter-egg/components/base-game-dialog/base-game-dialog.ts index 932401dbc6..8f8f868cb3 100644 --- a/client/src/app/ui/modules/sidenav/modules/easter-egg/components/base-game-dialog/base-game-dialog.ts +++ b/client/src/app/ui/modules/sidenav/modules/easter-egg/components/base-game-dialog/base-game-dialog.ts @@ -56,7 +56,7 @@ export abstract class BaseGameDialogComponent implements OnInit, OnDestroy { /** * The channel of the opponent. */ - private replyChannel: string | null = null; + protected replyChannel: string | null = null; /** * The opponents name. @@ -227,6 +227,15 @@ export abstract class BaseGameDialogComponent implements OnInit, OnDestroy { return this.op.shortName; } + protected setTimeout(): void { + if (this.waitTimout) { + clearTimeout(this.waitTimout); + } + this.waitTimout = setTimeout(() => { + this.handleEvent(`waitTimeout`); + }, 5000); + } + /** * Main state machine handler. The current state handler will be called with * the given event. If the handler returns a state (and not null), this will be @@ -276,12 +285,7 @@ export abstract class BaseGameDialogComponent implements OnInit, OnDestroy { { name: this.getPlayerName() }, this.replyChannel! ); - if (this.waitTimout) { - clearTimeout(this.waitTimout); - } - this.waitTimout = setTimeout(() => { - this.handleEvent(`waitTimeout`); - }, 5000); + this.setTimeout(); } /** diff --git a/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/chess-dialog.module.ts b/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/chess-dialog.module.ts index 3125c69de8..2925de490d 100644 --- a/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/chess-dialog.module.ts +++ b/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/chess-dialog.module.ts @@ -4,12 +4,21 @@ import { MatButtonModule } from '@angular/material/button'; import { MatDialogModule } from '@angular/material/dialog'; import { MatIconModule } from '@angular/material/icon'; import { OpenSlidesTranslationModule } from 'src/app/site/modules/translations'; +import { PromptDialogModule, PromptService } from 'src/app/ui/modules/prompt-dialog'; import { ChessDialogComponent } from './components/chess-dialog/chess-dialog.component'; @NgModule({ declarations: [ChessDialogComponent], - imports: [CommonModule, MatButtonModule, MatIconModule, MatDialogModule, OpenSlidesTranslationModule.forChild()] + imports: [ + CommonModule, + MatButtonModule, + MatIconModule, + MatDialogModule, + OpenSlidesTranslationModule.forChild(), + PromptDialogModule + ], + providers: [PromptService] }) export class ChessDialogModule { public static readonly label = `Play chess`; diff --git a/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/components/chess-dialog/chess-dialog.component.html b/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/components/chess-dialog/chess-dialog.component.html index 8093e628c5..45eded4204 100644 --- a/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/components/chess-dialog/chess-dialog.component.html +++ b/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/components/chess-dialog/chess-dialog.component.html @@ -5,13 +5,10 @@

{{ caption | translate }}

close - -
{{ 'Playing against' | translate }} {{ opponentName }}
-
-
- -
-
diff --git a/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/components/chess-dialog/chess-dialog.component.ts b/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/components/chess-dialog/chess-dialog.component.ts index f934a0ebc7..34e2d2f6ff 100644 --- a/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/components/chess-dialog/chess-dialog.component.ts +++ b/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/components/chess-dialog/chess-dialog.component.ts @@ -1,17 +1,28 @@ -import { Component, ElementRef, OnDestroy, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { Component, ElementRef, Inject, OnInit, ViewChild, ViewEncapsulation } from '@angular/core'; +import { MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { TranslateService } from '@ngx-translate/core'; import { Chess, EVENT_TYPE } from 'cm-chess/src/Chess'; import { BORDER_TYPE, Chessboard, COLOR, FEN, INPUT_EVENT_TYPE } from 'cm-chessboard/src/Chessboard'; import { PromotionDialog } from 'cm-chessboard/src/extensions/promotion-dialog/PromotionDialog'; +import { Id } from 'src/app/domain/definitions/key-types'; +import { NotifyResponse, NotifyService } from 'src/app/gateways/notify.service'; +import { ActiveMeetingService } from 'src/app/site/pages/meetings/services/active-meeting.service'; +import { OperatorService } from 'src/app/site/services/operator.service'; import { BaseGameDialogComponent, State } from '../../../../components/base-game-dialog/base-game-dialog'; +interface ChessDialogConfig { + userId?: Id; + notify?: NotifyResponse<{ name: string }>; +} + @Component({ selector: `os-chess-dialog`, templateUrl: `./chess-dialog.component.html`, styleUrls: [`./chess-dialog.component.scss`], encapsulation: ViewEncapsulation.None }) -export class ChessDialogComponent extends BaseGameDialogComponent implements OnInit, OnDestroy { +export class ChessDialogComponent extends BaseGameDialogComponent implements OnInit { protected prefix = `chess`; /** @@ -24,6 +35,9 @@ export class ChessDialogComponent extends BaseGameDialogComponent implements OnI */ private board: Chessboard = null; + /** + * The HTML element for the chess board + */ @ViewChild(`chessboard`, { static: true }) public boardContainer: ElementRef; @@ -32,23 +46,51 @@ export class ChessDialogComponent extends BaseGameDialogComponent implements OnI */ private ownColor: COLOR = COLOR.white; + public constructor( + activeMeetingService: ActiveMeetingService, + notifyService: NotifyService, + op: OperatorService, + translate: TranslateService, + @Inject(MAT_DIALOG_DATA) private config: ChessDialogConfig + ) { + super(activeMeetingService, notifyService, op, translate); + } + public override ngOnInit(): void { super.ngOnInit(); - this.board = new Chessboard(this.boardContainer.nativeElement, { - position: FEN.start, - language: this.translate.currentLang == `de` ? `de` : `en`, - assetsUrl: `./chess/`, - style: { - borderType: BORDER_TYPE.frame - }, - extensions: [{ class: PromotionDialog }] - }); - this.chess.addObserver(({ type }) => { - if (type === EVENT_TYPE.initialized || type === EVENT_TYPE.legalMove) { - this.board.setPosition(this.chess.fen(), true); - } - }); this.caption = this.translate.instant(`Chess`); + if (this.inMeeting) { + this.board = new Chessboard(this.boardContainer.nativeElement, { + position: FEN.start, + language: this.translate.currentLang == `de` ? `de` : `en`, + assetsUrl: `./chess/`, + style: { + borderType: BORDER_TYPE.frame + }, + extensions: [{ class: PromotionDialog }] + }); + this.chess.addObserver(({ type }) => { + if (type === EVENT_TYPE.initialized || type === EVENT_TYPE.legalMove) { + this.board.setPosition(this.chess.fen(), true); + } + }); + } + if (this.config?.userId) { + this.state = `waitForResponse`; + this.notifyService.sendToUsers(`chess_challenge`, { name: this.getPlayerName() }, this.config.userId); + this.caption = this.translate.instant(`Waiting for response...`); + const handle = this.SM.waitForResponse.receivedACK.handle; + this.SM.waitForResponse.receivedACK.handle = (notify: NotifyResponse<{ name: string }>) => { + if (notify.sender_user_id === this.config.userId) { + this.replyChannel = notify.sender_channel_id; + return handle(notify); + } + return null; + }; + } else if (this.config?.notify) { + this.state = `search`; + this.handleEvent(`receivedSearchResponse`, this.config.notify); + } } protected override reset(): void { diff --git a/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/services/chess-challenge.service.ts b/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/services/chess-challenge.service.ts new file mode 100644 index 0000000000..96e65a3c4c --- /dev/null +++ b/client/src/app/ui/modules/sidenav/modules/easter-egg/modules/chess-dialog/services/chess-challenge.service.ts @@ -0,0 +1,40 @@ +import { Injectable } from '@angular/core'; +import { MatDialog } from '@angular/material/dialog'; +import { TranslateService } from '@ngx-translate/core'; +import { NotifyResponse, NotifyService } from 'src/app/gateways/notify.service'; +import { mediumDialogSettings } from 'src/app/infrastructure/utils/dialog-settings'; +import { OperatorService } from 'src/app/site/services/operator.service'; +import { PromptService } from 'src/app/ui/modules/prompt-dialog'; + +import { ChessDialogModule } from '../chess-dialog.module'; + +@Injectable({ + providedIn: ChessDialogModule +}) +export class ChessChallengeService { + constructor( + private notifyService: NotifyService, + private op: OperatorService, + private dialog: MatDialog, + private translate: TranslateService, + private prompt: PromptService + ) {} + + public startListening(): void { + this.notifyService + .getMessageObservable(`chess_challenge`) + .subscribe(async (notify: NotifyResponse<{ name: string }>) => { + if (!notify.sendByThisUser) { + const title = + notify.message.name + ` ` + this.translate.instant(`challenged you to a chess match!`); + const content = this.translate.instant(`Do you accept?`); + if (await this.prompt.open(title, content)) { + this.dialog.open(ChessDialogModule.getComponent(), { + ...mediumDialogSettings, + data: { notify } + }); + } + } + }); + } +} From fa79dd0f5e2a40200112950d83c6f447c0a1d5a4 Mon Sep 17 00:00:00 2001 From: reiterl Date: Fri, 29 Sep 2023 13:01:19 +0200 Subject: [PATCH 04/11] Fix problem bulk set Personal note (#2840) --- .../services/motion-multiselect.service.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/client/src/app/site/pages/meetings/pages/motions/components/motion-multiselect/services/motion-multiselect.service.ts b/client/src/app/site/pages/meetings/pages/motions/components/motion-multiselect/services/motion-multiselect.service.ts index 67474644ff..84ea908a00 100644 --- a/client/src/app/site/pages/meetings/pages/motions/components/motion-multiselect/services/motion-multiselect.service.ts +++ b/client/src/app/site/pages/meetings/pages/motions/components/motion-multiselect/services/motion-multiselect.service.ts @@ -389,8 +389,13 @@ export class MotionMultiselectService { // `bulkSetStar` does imply that "true" sets favorites while "false" unsets favorites const isFavorite = selectedChoice.action === options[0]; const message = this.translate.instant(`I have ${motions.length} favorite motions. Please wait ...`); + + const filteredMotions = motions + .map(motion => this.repo.getViewModel(motion.id)) + .filter(motion => isFavorite || motion.getPersonalNote()); this.spinnerService.show(message, { - hideAfterPromiseResolved: () => this.personalNoteRepo.setPersonalNote({ star: isFavorite }, ...motions) + hideAfterPromiseResolved: () => + this.personalNoteRepo.setPersonalNote({ star: isFavorite }, ...filteredMotions) }); } } From 1d5fc55a2d3fd04850d288ed42eddcbd4c2bd65c Mon Sep 17 00:00:00 2001 From: reiterl Date: Fri, 29 Sep 2023 13:07:55 +0200 Subject: [PATCH 05/11] Fix problem with three picture menu (#2839) --- .../file-list/file-list.component.html | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/client/src/app/ui/modules/file-list/components/file-list/file-list.component.html b/client/src/app/ui/modules/file-list/components/file-list/file-list.component.html index c39303065d..65407e0bda 100644 --- a/client/src/app/ui/modules/file-list/components/file-list/file-list.component.html +++ b/client/src/app/ui/modules/file-list/components/file-list/file-list.component.html @@ -105,19 +105,21 @@ -
-
+ - - text_fields - - - insert_photo - -
+ text_fields + + + insert_photo +
From 1c70f884821f51b504c669c8b026b25acaf6f821 Mon Sep 17 00:00:00 2001 From: Bastian Rihm Date: Fri, 29 Sep 2023 13:11:48 +0200 Subject: [PATCH 06/11] Fix search selection option focus (#2842) --- .../base-search-selector.component.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/client/src/app/ui/modules/search-selector/components/base-search-selector/base-search-selector.component.ts b/client/src/app/ui/modules/search-selector/components/base-search-selector/base-search-selector.component.ts index 83b7f2a169..b7802dfff9 100644 --- a/client/src/app/ui/modules/search-selector/components/base-search-selector/base-search-selector.component.ts +++ b/client/src/app/ui/modules/search-selector/components/base-search-selector/base-search-selector.component.ts @@ -210,6 +210,7 @@ export abstract class BaseSearchSelectorComponent extends BaseFormFieldControlCo } this._selectableItemsList = this.sortFn ? allItems.sort(this.sortFn) : allItems; this.filteredItemsSubject.next(this.getFilteredItemsBySearchValue()); + this.updateOptionFocus(); } protected get selectableItems(): Selectable[] { @@ -251,6 +252,15 @@ export abstract class BaseSearchSelectorComponent extends BaseFormFieldControlCo document.body.appendChild(sheet); } + private updateOptionFocus(): void { + setTimeout(() => { + if (!this.matSelect?.options.some(o => o.active)) { + this.matSelect.options.first.focus(); + this.matSelect.options.first.setActiveStyles(); + } + }, 200); + } + public onChipRemove(itemId: Id): void { this.addOrRemoveId(itemId); @@ -332,6 +342,7 @@ export abstract class BaseSearchSelectorComponent extends BaseFormFieldControlCo protected onSearchValueUpdated(nextValue: string): void { this.filteredItemsSubject.next(this.getFilteredItemsBySearchValue(nextValue.toLowerCase())); + this.updateOptionFocus(); } protected initializeForm(): void { From 0fd4488b975b68bb1945fcba2b697622d90895e1 Mon Sep 17 00:00:00 2001 From: luisa-beerboom <101706784+luisa-beerboom@users.noreply.github.com> Date: Fri, 29 Sep 2023 13:13:16 +0200 Subject: [PATCH 07/11] Fix participant-create-wizard (#2815) --- .../participant-create-wizard.component.ts | 2 +- .../src/app/site/services/operator.service.ts | 40 +++++++++++++++---- client/src/app/site/services/user.service.ts | 10 +++-- 3 files changed, 40 insertions(+), 12 deletions(-) diff --git a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/pages/participant-detail-manage/components/participant-create-wizard/participant-create-wizard.component.ts b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/pages/participant-detail-manage/components/participant-create-wizard/participant-create-wizard.component.ts index c4a7cc32ca..7be55d1911 100644 --- a/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/pages/participant-detail-manage/components/participant-create-wizard/participant-create-wizard.component.ts +++ b/client/src/app/site/pages/meetings/pages/participants/pages/participant-detail/pages/participant-detail-manage/components/participant-create-wizard/participant-create-wizard.component.ts @@ -283,7 +283,7 @@ export class ParticipantCreateWizardComponent extends BaseMeetingComponent imple private async checkScope(): Promise { if (this._accountId) { - this._isUserInScope = await this.userService.isUserInSameScope(this._accountId); + this._isUserInScope = await this.userService.hasScopeManagePerms(this._accountId); if (this._isUserInScope && this.account?.id !== this._accountId) { this.account = new User( (await this.modelRequestService.fetch(getParticipantDetailSubscription(this._accountId)))[`user`][ diff --git a/client/src/app/site/services/operator.service.ts b/client/src/app/site/services/operator.service.ts index c7d280fd61..58b1e2fbe7 100644 --- a/client/src/app/site/services/operator.service.ts +++ b/client/src/app/site/services/operator.service.ts @@ -15,6 +15,7 @@ import { Deferred } from '../../infrastructure/utils/promises'; import { GroupControllerService } from '../pages/meetings/pages/participants'; import { ActiveMeetingService } from '../pages/meetings/services/active-meeting.service'; import { NoActiveMeetingError } from '../pages/meetings/services/active-meeting-id.service'; +import { MeetingControllerService } from '../pages/meetings/services/meeting-controller.service'; import { ViewMeeting } from '../pages/meetings/view-models/view-meeting'; import { AuthService } from './auth.service'; import { AutoupdateService, ModelSubscription } from './autoupdate'; @@ -213,7 +214,8 @@ export class OperatorService { private userRepo: UserRepositoryService, private groupRepo: GroupControllerService, private autoupdateService: AutoupdateService, - private modelRequestBuilder: ModelRequestBuilderService + private modelRequestBuilder: ModelRequestBuilderService, + private meetingRepo: MeetingControllerService ) { this.setNotReady(); // General environment in which the operator moves @@ -494,15 +496,39 @@ export class OperatorService { return true; } - let result: boolean; if (!this._groupIds) { - result = false; + return false; } else if (this.isAuthenticated && this._groupIds.find(id => id === this.adminGroupId)) { - result = true; - } else { - result = checkPerms.some(permission => this._permissions?.includes(permission)); + return true; + } + return checkPerms.some(permission => this._permissions?.includes(permission)); + } + + /** + * Checks, if the operator has at least one of the given permissions for the given meetingId. + * @param checkPerms The permissions to check, if at least one matches. + */ + public hasPermsInMeeting(meetingId: number, ...checkPerms: Permission[]): boolean { + if (meetingId === this.activeMeetingId) { + return this.hasPerms(...checkPerms); + } + if (!this._ready) { + // console.warn(`has perms: Operator is not ready!`); + return false; + } + if (this.isSuperAdmin) { + return true; + } + const groups = this.user.groups(meetingId); + if (!groups || !groups.length) { + return false; + } else if ( + this.isAuthenticated && + groups.find(group => group.id === this.meetingRepo.getViewModel(meetingId)?.admin_group_id) + ) { + return true; } - return result; + return checkPerms.some(permission => groups.some(group => group.hasPermission(permission))); } /** diff --git a/client/src/app/site/services/user.service.ts b/client/src/app/site/services/user.service.ts index 61a065464f..3756c06867 100644 --- a/client/src/app/site/services/user.service.ts +++ b/client/src/app/site/services/user.service.ts @@ -88,7 +88,10 @@ export class UserService { .map(userId => parseInt(userId, 10)) .some(userId => { const toCompare = result[userId]; - return this.presenter.compareScope(ownScope, toCompare) === -1; + return ( + this.presenter.compareScope(ownScope, toCompare) === -1 || + (ownScope.collection === toCompare.collection && ownScope.id !== toCompare.id) + ); }); } @@ -106,7 +109,7 @@ export class UserService { .map(userId => parseInt(userId, 10)) .every(userId => { const toCompare = result[userId]; - let hasPerms = this.operator.hasOrganizationPermissions(OML.can_manage_users); + let hasPerms = this.operator.hasOrganizationPermissions(toCompare.user_oml || OML.can_manage_users); if (!hasPerms && toCompare.collection === UserScope.COMMITTEE) { hasPerms = hasPerms || this.operator.hasCommitteePermissions(toCompare.id, CML.can_manage); } @@ -114,8 +117,7 @@ export class UserService { const committee_id = this.meetingRepo.getViewModel(toCompare.id)?.committee_id; hasPerms = hasPerms || - (this.activeMeetingService.meetingId === toCompare.id && - this.operator.hasPerms(Permission.userCanManage)) || + this.operator.hasPermsInMeeting(toCompare.id, Permission.userCanManage) || (committee_id && this.operator.hasCommitteePermissions(committee_id, CML.can_manage)); } return hasPerms; From 74438b43b4594d6e3e867b45000a7aff7c931574 Mon Sep 17 00:00:00 2001 From: luisa-beerboom <101706784+luisa-beerboom@users.noreply.github.com> Date: Fri, 29 Sep 2023 14:05:42 +0200 Subject: [PATCH 08/11] Switch account import to backend (#2674) --- .../csv-export-for-backend.service.ts | 6 +- .../csv-export.service/csv-export-utils.ts | 14 +++++ .../repositories/users/user-action.ts | 2 + .../users/user-repository.service.ts | 9 +++ .../base-backend-import.service.ts | 21 +++++-- .../base-via-backend-import-list.component.ts | 6 +- .../topic-import/topic-import.component.html | 8 ++- .../account-import-list.component.html | 6 +- .../account-import-list.component.ts | 55 +++++-------------- .../pages/account-import/definitions/index.ts | 8 ++- .../account-import.service.ts | 45 +++++++++------ .../account-list/account-list.component.html | 4 ++ .../account-list/account-list.component.ts | 4 +- .../account-export.service.ts | 9 ++- .../common/account-controller.service.ts | 9 +++ .../backend-import-list.component.html | 14 ++--- .../backend-import-list.component.ts | 33 ++++++++--- .../definitions/backend-import-preview.ts | 15 ++--- 18 files changed, 162 insertions(+), 106 deletions(-) diff --git a/client/src/app/gateways/export/csv-export.service/csv-export-for-backend.service.ts b/client/src/app/gateways/export/csv-export.service/csv-export-for-backend.service.ts index 479d2010ae..a85ec745d0 100644 --- a/client/src/app/gateways/export/csv-export.service/csv-export-for-backend.service.ts +++ b/client/src/app/gateways/export/csv-export.service/csv-export-for-backend.service.ts @@ -4,7 +4,7 @@ import { BaseViewModel } from 'src/app/site/base/base-view-model'; import { ExportServiceModule } from '../export-service.module'; import { FileExportService } from '../file-export.service'; import { - CsvColumnsDefinition, + BackendCsvColumnsDefinition, DEFAULT_COLUMN_SEPARATOR, DEFAULT_ENCODING, DEFAULT_LINE_SEPARATOR, @@ -30,7 +30,7 @@ export class CsvExportForBackendService { */ public export( models: T[], - columns: CsvColumnsDefinition, + columns: BackendCsvColumnsDefinition, filename: string, { lineSeparator = DEFAULT_LINE_SEPARATOR, @@ -54,7 +54,7 @@ export class CsvExportForBackendService { const header = columns.map(column => { let label = ``; if (isPropertyDefinition(column)) { - label = column.label ? column.label : (column.property as string); + label = column.property as string; } else if (isMapDefinition(column)) { label = column.label; } diff --git a/client/src/app/gateways/export/csv-export.service/csv-export-utils.ts b/client/src/app/gateways/export/csv-export.service/csv-export-utils.ts index 8048796dca..fee0e09cae 100644 --- a/client/src/app/gateways/export/csv-export.service/csv-export-utils.ts +++ b/client/src/app/gateways/export/csv-export.service/csv-export-utils.ts @@ -1,3 +1,11 @@ +/** + * Defines a csv column with a property of the model and an optional label. If this is not given, the + * translated and capitalized property name is used. + */ +export interface BackendCsvColumnDefinitionProperty { + property: keyof T; +} + /** * Defines a csv column with a property of the model and an optional label. If this is not given, the * translated and capitalized property name is used. @@ -44,6 +52,12 @@ export function isMapDefinition(obj: any): obj is CsvColumnDefinitionMap { */ export type CsvColumnsDefinition = (CsvColumnDefinitionProperty | CsvColumnDefinitionMap)[]; +/** + * The definition of columns in the export. Either use a property for every model or do a custom mapping to + * a string to be put into the csv. + */ +export type BackendCsvColumnsDefinition = (BackendCsvColumnDefinitionProperty | CsvColumnDefinitionMap)[]; + export const ISO_8859_15_ENCODING = `iso-8859-15`; export const DEFAULT_LINE_SEPARATOR = `\r\n`; export const DEFAULT_COLUMN_SEPARATOR = `,`; diff --git a/client/src/app/gateways/repositories/users/user-action.ts b/client/src/app/gateways/repositories/users/user-action.ts index 556516417e..750f45c89a 100644 --- a/client/src/app/gateways/repositories/users/user-action.ts +++ b/client/src/app/gateways/repositories/users/user-action.ts @@ -14,4 +14,6 @@ export class UserAction { public static readonly FORGET_PASSWORD_CONFIRM = `user.forget_password_confirm`; public static readonly ASSIGN_MEETINGS = `user.assign_meetings`; public static readonly MERGE_TOGETHER = `user.merge_together`; + public static readonly ACCOUNT_JSON_UPLOAD = `account.json_upload`; + public static readonly ACCOUNT_IMPORT = `account.import`; } diff --git a/client/src/app/gateways/repositories/users/user-repository.service.ts b/client/src/app/gateways/repositories/users/user-repository.service.ts index fa5014b82c..748a7e1132 100644 --- a/client/src/app/gateways/repositories/users/user-repository.service.ts +++ b/client/src/app/gateways/repositories/users/user-repository.service.ts @@ -5,6 +5,7 @@ import { BaseRepository } from 'src/app/gateways/repositories/base-repository'; import { UserAction } from 'src/app/gateways/repositories/users/user-action'; import { ActiveMeetingIdService } from 'src/app/site/pages/meetings/services/active-meeting-id.service'; import { ViewMeetingUser } from 'src/app/site/pages/meetings/view-models/view-meeting-user'; +import { BackendImportRawPreview } from 'src/app/ui/modules/import-list/definitions/backend-import-preview'; import { Id } from '../../../domain/definitions/key-types'; import { Displayable } from '../../../domain/interfaces/displayable'; @@ -497,6 +498,14 @@ export class UserRepositoryService extends BaseRepository { return this.createAction(UserAction.SET_PRESENT, payload); } + public accountJsonUpload(payload: { [key: string]: any }): Action { + return this.createAction(UserAction.ACCOUNT_JSON_UPLOAD, payload); + } + + public accountImport(payload: { id: number; import: boolean }[]): Action { + return this.createAction(UserAction.ACCOUNT_IMPORT, payload); + } + private sanitizePayload(payload: any): any { const temp = { ...payload }; for (const key of Object.keys(temp).filter(field => !this.isFieldAllowedToBeEmpty(field))) { diff --git a/client/src/app/site/base/base-import.service/base-backend-import.service.ts b/client/src/app/site/base/base-import.service/base-backend-import.service.ts index ca892b8a00..3f78c7ee20 100644 --- a/client/src/app/site/base/base-import.service/base-backend-import.service.ts +++ b/client/src/app/site/base/base-import.service/base-backend-import.service.ts @@ -282,15 +282,24 @@ export abstract class BaseBackendImportService implements BackendImportService { isBackendImportRawPreview(result) ) as BackendImportRawPreview[]; this.processRawPreviews(updatedPreviews); - if (this.previewHasRowErrors) { + const statesSet = new Set(updatedPreviews.map(preview => preview.state)); + if (statesSet.has(BackendImportState.Error)) { this._currentImportPhaseSubject.next(BackendImportPhase.ERROR); + } else if (statesSet.has(BackendImportState.Warning)) { + this.processRawPreviews( + updatedPreviews + .filter(preview => preview.state === BackendImportState.Warning) + .map(preview => ({ + ...preview, + rows: preview.rows.filter(row => row.messages && row.messages.length) + })) + ); + this._currentImportPhaseSubject.next(BackendImportPhase.FINISHED_WITH_WARNING); } else { - this._currentImportPhaseSubject.next(BackendImportPhase.TRY_AGAIN); + this._currentImportPhaseSubject.next(BackendImportPhase.FINISHED); + this._csvLines = []; + return true; } - } else { - this._currentImportPhaseSubject.next(BackendImportPhase.FINISHED); - this._csvLines = []; - return true; } return false; } diff --git a/client/src/app/site/base/base-via-backend-import-list.component.ts b/client/src/app/site/base/base-via-backend-import-list.component.ts index b9ba3eb7f0..b11d1c39f4 100644 --- a/client/src/app/site/base/base-via-backend-import-list.component.ts +++ b/client/src/app/site/base/base-via-backend-import-list.component.ts @@ -29,7 +29,7 @@ export abstract class BaseViaBackendImportListComponent extends BaseComponent im * True if the import is in a state in which an import can be conducted */ public get canImport(): boolean { - return this._state === BackendImportPhase.AWAITING_CONFIRM || this.tryAgain; + return this._state === BackendImportPhase.AWAITING_CONFIRM; } /** @@ -42,8 +42,8 @@ export abstract class BaseViaBackendImportListComponent extends BaseComponent im /** * True if, after an attempted import failed, the view is waiting for the user to confirm the import on the new preview. */ - public get tryAgain(): boolean { - return this._state === BackendImportPhase.TRY_AGAIN; + public get finishedWithWarnings(): boolean { + return this._state === BackendImportPhase.FINISHED_WITH_WARNING; } /** diff --git a/client/src/app/site/pages/meetings/pages/agenda/modules/topics/pages/topic-import/components/topic-import/topic-import.component.html b/client/src/app/site/pages/meetings/pages/agenda/modules/topics/pages/topic-import/components/topic-import/topic-import.component.html index 5e9b79a31b..a2093e0426 100644 --- a/client/src/app/site/pages/meetings/pages/agenda/modules/topics/pages/topic-import/components/topic-import/topic-import.component.html +++ b/client/src/app/site/pages/meetings/pages/agenda/modules/topics/pages/topic-import/components/topic-import/topic-import.component.html @@ -6,10 +6,16 @@

{{ 'Import topics' | translate }}