diff --git a/src/app/components/stream/status/action-bar/action-bar.component.ts b/src/app/components/stream/status/action-bar/action-bar.component.ts index 68bc2fe3..5b2f2f95 100644 --- a/src/app/components/stream/status/action-bar/action-bar.component.ts +++ b/src/app/components/stream/status/action-bar/action-bar.component.ts @@ -11,6 +11,7 @@ import { Status, Account, Results } from '../../../../services/models/mastodon.i import { ToolsService, OpenThreadEvent } from '../../../../services/tools.service'; import { NotificationService } from '../../../../services/notification.service'; import { StatusWrapper } from '../../../../models/common.model'; +import { StatusesStateService, StatusState } from '../../../../services/statuses-state.service'; @Component({ selector: 'app-action-bar', @@ -43,7 +44,7 @@ export class ActionBarComponent implements OnInit, OnDestroy { favoriteIsLoading: boolean; boostIsLoading: boolean; - isContentWarningActive: boolean = false; + isContentWarningActive: boolean = false; displayedStatus: Status; @@ -55,10 +56,12 @@ export class ActionBarComponent implements OnInit, OnDestroy { private accounts$: Observable; private accountSub: Subscription; + private statusStateSub: Subscription; - constructor( + constructor( private readonly store: Store, private readonly toolsService: ToolsService, + private readonly statusStateService: StatusesStateService, private readonly mastodonService: MastodonWrapperService, private readonly notificationService: NotificationService) { @@ -79,6 +82,8 @@ export class ActionBarComponent implements OnInit, OnDestroy { this.displayedStatus = status; } + this.analyseMemoryStatus(); + if (this.displayedStatus.visibility === 'direct') { this.isDM = true; } @@ -86,10 +91,31 @@ export class ActionBarComponent implements OnInit, OnDestroy { this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => { this.checkStatus(accounts); }); + + this.statusStateSub = this.statusStateService.stateNotification.subscribe((state: StatusState) => { + if (state && state.statusId === this.displayedStatus.url) { + this.favoriteStatePerAccountId[state.accountId] = state.isFavorited; + this.bootedStatePerAccountId[state.accountId] = state.isRebloged; + + this.checkIfFavorited(); + this.checkIfBoosted(); + } + }); } ngOnDestroy(): void { this.accountSub.unsubscribe(); + this.statusStateSub.unsubscribe(); + } + + private analyseMemoryStatus() { + let memoryStatusState = this.statusStateService.getStateForStatus(this.displayedStatus.url); + if (!memoryStatusState) return; + + memoryStatusState.forEach((state: StatusState) => { + this.favoriteStatePerAccountId[state.accountId] = state.isFavorited; + this.bootedStatePerAccountId[state.accountId] = state.isRebloged; + }); } private checkStatus(accounts: AccountInfo[]): void { @@ -156,7 +182,7 @@ export class ActionBarComponent implements OnInit, OnDestroy { this.bootedStatePerAccountId[account.id] = boostedStatus.reblog !== null; //FIXME: when Pleroma will return the good status } else { let reblogged = boostedStatus.reblogged; //FIXME: when pixelfed will return the good status - if(reblogged === null){ + if (reblogged === null) { reblogged = !this.bootedStatePerAccountId[account.id]; } this.bootedStatePerAccountId[account.id] = reblogged; @@ -168,6 +194,7 @@ export class ActionBarComponent implements OnInit, OnDestroy { this.notificationService.notifyHttpError(err, account); }) .then(() => { + this.statusStateService.statusReblogStatusChanged(this.displayedStatus.url, account.id, this.bootedStatePerAccountId[account.id]); this.boostIsLoading = false; }); @@ -192,7 +219,7 @@ export class ActionBarComponent implements OnInit, OnDestroy { }) .then((favoritedStatus: Status) => { let favourited = favoritedStatus.favourited; //FIXME: when pixelfed will return the good status - if(favourited === null){ + if (favourited === null) { favourited = !this.favoriteStatePerAccountId[account.id]; } this.favoriteStatePerAccountId[account.id] = favourited; @@ -202,6 +229,7 @@ export class ActionBarComponent implements OnInit, OnDestroy { this.notificationService.notifyHttpError(err, account); }) .then(() => { + this.statusStateService.statusFavoriteStatusChanged(this.displayedStatus.url, account.id, this.favoriteStatePerAccountId[account.id]); this.favoriteIsLoading = false; }); @@ -225,9 +253,9 @@ export class ActionBarComponent implements OnInit, OnDestroy { } else { this.isFavorited = false; } - } + } - browseThread(event: OpenThreadEvent){ + browseThread(event: OpenThreadEvent) { this.browseThreadEvent.next(event); } } diff --git a/src/app/components/stream/stream-statuses/stream-statuses.component.scss b/src/app/components/stream/stream-statuses/stream-statuses.component.scss index 1a154be9..d0ff772a 100644 --- a/src/app/components/stream/stream-statuses/stream-statuses.component.scss +++ b/src/app/components/stream/stream-statuses/stream-statuses.component.scss @@ -23,7 +23,7 @@ } &__new-notification { - z-index: 100; + z-index: 1; width: $stream-column-width; height: 25px; position: absolute; diff --git a/src/app/services/statuses-state.service.spec.ts b/src/app/services/statuses-state.service.spec.ts new file mode 100644 index 00000000..b115d873 --- /dev/null +++ b/src/app/services/statuses-state.service.spec.ts @@ -0,0 +1,59 @@ +import { TestBed } from '@angular/core/testing'; + +import { StatusesStateService } from './statuses-state.service'; +import { setRootDomAdapter } from '@angular/platform-browser/src/dom/dom_adapter'; + +describe('StatusesStateService', () => { + beforeEach(() => TestBed.configureTestingModule({})); + + it('should be created', () => { + const service: StatusesStateService = TestBed.get(StatusesStateService); + expect(service).toBeTruthy(); + }); + + it('should set unset favorited status', () => { + const statusId = 'statusId'; + const accountId = 'accountId'; + + const service: StatusesStateService = TestBed.get(StatusesStateService); + service.statusFavoriteStatusChanged(statusId, accountId, true); + let result = service.getStateForStatus(statusId).find(x => x.accountId === accountId); + + expect(result.isFavorited).toBeTruthy(); + }); + + it('should set unset rebloged status', () => { + const statusId = 'statusId'; + const accountId = 'accountId'; + + const service: StatusesStateService = TestBed.get(StatusesStateService); + service.statusReblogStatusChanged(statusId, accountId, true); + let result = service.getStateForStatus(statusId).find(x => x.accountId === accountId); + + expect(result.isRebloged).toBeTruthy(); + }); + + it('should be able to reset favorited status', () => { + const statusId = 'statusId'; + const accountId = 'accountId'; + + const service: StatusesStateService = TestBed.get(StatusesStateService); + service.statusFavoriteStatusChanged(statusId, accountId, true); + service.statusFavoriteStatusChanged(statusId, accountId, false); + let result = service.getStateForStatus(statusId).find(x => x.accountId === accountId); + + expect(result.isFavorited).toBeFalsy(); + }); + + it('should be able to reset rebloged status', () => { + const statusId = 'statusId'; + const accountId = 'accountId'; + + const service: StatusesStateService = TestBed.get(StatusesStateService); + service.statusReblogStatusChanged(statusId, accountId, true); + service.statusReblogStatusChanged(statusId, accountId, false); + let result = service.getStateForStatus(statusId).find(x => x.accountId === accountId); + + expect(result.isRebloged).toBeFalsy(); + }); +}); diff --git a/src/app/services/statuses-state.service.ts b/src/app/services/statuses-state.service.ts new file mode 100644 index 00000000..e8e67d99 --- /dev/null +++ b/src/app/services/statuses-state.service.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@angular/core'; + +import { Subject } from 'rxjs'; + +@Injectable({ + providedIn: 'root' +}) +export class StatusesStateService { + private cachedStatusStates: { [statusId: string]: { [accountId: string]: StatusState } } = {}; + public stateNotification = new Subject(); + + constructor() { } + + getStateForStatus(statusId: string): StatusState[] { + if(!this.cachedStatusStates[statusId]) + return null; + + let results: StatusState[] = []; + Object.entries(this.cachedStatusStates[statusId]).forEach( + ([key, value]) => { + results.push(value); + } + ); + return results; + } + + statusFavoriteStatusChanged(statusId: string, accountId: string, isFavorited: boolean) { + if (!this.cachedStatusStates[statusId]) + this.cachedStatusStates[statusId] = {}; + + if (!this.cachedStatusStates[statusId][accountId]) { + this.cachedStatusStates[statusId][accountId] = new StatusState(statusId, accountId, isFavorited, false); + } else { + this.cachedStatusStates[statusId][accountId].isFavorited = isFavorited; + } + + this.stateNotification.next(this.cachedStatusStates[statusId][accountId]); + } + + statusReblogStatusChanged(statusId: string, accountId: string, isRebloged: boolean) { + if (!this.cachedStatusStates[statusId]) + this.cachedStatusStates[statusId] = {}; + + if (!this.cachedStatusStates[statusId][accountId]) { + this.cachedStatusStates[statusId][accountId] = new StatusState(statusId, accountId, false, isRebloged); + } else { + this.cachedStatusStates[statusId][accountId].isRebloged = isRebloged; + } + + this.stateNotification.next(this.cachedStatusStates[statusId][accountId]); + } +} + +export class StatusState { + constructor( + public statusId: string, + public accountId: string, + public isFavorited: boolean, + public isRebloged: boolean) { + } +}