diff --git a/package.json b/package.json index 5405b6f7..1756e0d7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sengi", - "version": "0.27.1", + "version": "0.28.0", "license": "AGPL-3.0-or-later", "main": "main-electron.js", "description": "A multi-account desktop client for Mastodon and Pleroma", diff --git a/src/app/app.component.html b/src/app/app.component.html index 9a1e319a..fd202f74 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -10,9 +10,9 @@ -
+
-
A new version is available!
reload +
{{restartNotificationLabel}}
reload
diff --git a/src/app/app.component.scss b/src/app/app.component.scss index c13b810d..aef18c8b 100644 --- a/src/app/app.component.scss +++ b/src/app/app.component.scss @@ -104,17 +104,20 @@ app-streams-selection-footer { transition-timing-function: ease-in; position: absolute; - height: 70px; + height: 50px; left: 0; right: 0; - bottom: -80px; + bottom: 0; + //bottom: -80px; + opacity: 0; z-index: 999999999; &__activated { // opacity: 1; transition: all .25s; transition-timing-function: ease-out; - bottom: 0px; + opacity: 1; + height: 70px; } &__display { diff --git a/src/app/app.component.ts b/src/app/app.component.ts index e0a03b45..dd3e8bad 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -32,7 +32,10 @@ export class AppComponent implements OnInit, OnDestroy { floatingColumnActive: boolean; tutorialActive: boolean; openedMediaEvent: OpenMediaEvent - updateAvailable: boolean; + + restartNotificationLabel: string; + restartNotificationAvailable: boolean; + showRestartNotification: boolean; private authStorageKey: string = 'tempAuth'; @@ -40,9 +43,9 @@ export class AppComponent implements OnInit, OnDestroy { private openMediaSub: Subscription; private streamSub: Subscription; private dragoverSub: Subscription; - private updateAvailableSub: Subscription; private paramsSub: Subscription; - + private restartNotificationSub: Subscription; + @Select(state => state.streamsstatemodel.streams) streamElements$: Observable; constructor( @@ -52,14 +55,17 @@ export class AppComponent implements OnInit, OnDestroy { private readonly mastodonService: MastodonWrapperService, private readonly authService: AuthService, private readonly activatedRoute: ActivatedRoute, - private readonly serviceWorkerService: ServiceWorkerService, + private readonly serviceWorkerService: ServiceWorkerService, // Ensure update checks private readonly toolsService: ToolsService, private readonly mediaService: MediaService, private readonly navigationService: NavigationService) { } ngOnInit(): void { - this.paramsSub = this.activatedRoute.queryParams.subscribe(params => { + // disable tutorial for future update + localStorage.setItem('tutorial', JSON.stringify(true)); + + this.paramsSub = this.activatedRoute.queryParams.subscribe(params => { const code = params['code']; if (!code) { return; @@ -76,10 +82,10 @@ export class AppComponent implements OnInit, OnDestroy { let usedTokenData: TokenData; this.authService.getToken(appDataWrapper.instance, appInfo.app.client_id, appInfo.app.client_secret, code, appInfo.app.redirect_uri) .then((tokenData: TokenData) => { - - if(tokenData.refresh_token && !tokenData.created_at){ + + if (tokenData.refresh_token && !tokenData.created_at) { const nowEpoch = Date.now() / 1000 | 0; - tokenData.created_at = nowEpoch; + tokenData.created_at = nowEpoch; } usedTokenData = tokenData; @@ -87,17 +93,17 @@ export class AppComponent implements OnInit, OnDestroy { return this.mastodonService.retrieveAccountDetails({ 'instance': appDataWrapper.instance, 'id': '', 'username': '', 'order': 0, 'isSelected': true, 'token': tokenData }); }) .then((account: Account) => { - var username = account.username.toLowerCase(); + var username = account.username.toLowerCase(); var instance = appDataWrapper.instance.toLowerCase(); - if(this.isAccountAlreadyPresent(username, instance)){ + if (this.isAccountAlreadyPresent(username, instance)) { this.notificationService.notify(null, null, `Account @${username}@${instance} is already registered`, true); this.router.navigate(['/']); return; } const accountInfo = new AccountInfo(); - accountInfo.username = username; + accountInfo.username = username; accountInfo.instance = instance; accountInfo.token = usedTokenData; @@ -113,10 +119,6 @@ export class AppComponent implements OnInit, OnDestroy { }); }); - this.updateAvailableSub = this.serviceWorkerService.newAppVersionIsAvailable.subscribe((updateAvailable) => { - this.updateAvailable = updateAvailable; - }); - this.streamSub = this.streamElements$.subscribe((streams: StreamElement[]) => { if (streams && streams.length === 0) { this.tutorialActive = true; @@ -147,7 +149,13 @@ export class AppComponent implements OnInit, OnDestroy { ) .subscribe(() => { this.drag = false; - }) + }); + + this.restartNotificationSub = this.notificationService.restartNotificationStream.subscribe((label: string) => { + if (label) { + this.displayRestartNotification(label); + } + }); } ngOnDestroy(): void { @@ -155,8 +163,8 @@ export class AppComponent implements OnInit, OnDestroy { this.columnEditorSub.unsubscribe(); this.openMediaSub.unsubscribe(); this.dragoverSub.unsubscribe(); - this.updateAvailableSub.unsubscribe(); this.paramsSub.unsubscribe(); + this.restartNotificationSub.unsubscribe(); } closeMedia() { @@ -195,19 +203,34 @@ export class AppComponent implements OnInit, OnDestroy { } loadNewVersion(): boolean { - this.serviceWorkerService.loadNewAppVersion(); + document.location.reload(); + // this.serviceWorkerService.loadNewAppVersion(); return false; } - closeAutoUpdate(): boolean { - this.updateAvailable = false; + displayRestartNotification(label: string): boolean { + this.restartNotificationLabel = label; + this.showRestartNotification = true; + setTimeout(() => { + this.restartNotificationAvailable = true; + }, 200); + return false; } - private isAccountAlreadyPresent(username: string, instance: string): boolean{ + closeRestartNotification(): boolean { + this.restartNotificationAvailable = false; + setTimeout(() => { + this.showRestartNotification = false; + }, 250); + + return false; + } + + private isAccountAlreadyPresent(username: string, instance: string): boolean { const accounts = this.store.snapshot().registeredaccounts.accounts; for (let acc of accounts) { - if(acc.instance === instance && acc.username == username){ + if (acc.instance === instance && acc.username == username) { return true; } } diff --git a/src/app/components/create-status/create-status.component.html b/src/app/components/create-status/create-status.component.html index df0480d4..d7f3538e 100644 --- a/src/app/components/create-status/create-status.component.html +++ b/src/app/components/create-status/create-status.component.html @@ -8,7 +8,7 @@ diff --git a/src/app/components/create-status/create-status.component.ts b/src/app/components/create-status/create-status.component.ts index a926199b..ec26f747 100644 --- a/src/app/components/create-status/create-status.component.ts +++ b/src/app/components/create-status/create-status.component.ts @@ -735,7 +735,11 @@ export class CreateStatusComponent implements OnInit, OnDestroy { if (isVisible) { setTimeout(() => { - this.footerElement.nativeElement.scrollIntoViewIfNeeded({ behavior: 'instant', block: 'end', inline: 'start' }); + try{ + this.footerElement.nativeElement.scrollIntoViewIfNeeded({ behavior: 'instant', block: 'end', inline: 'start' }); + }catch(err) { + this.footerElement.nativeElement.scrollIntoView({ behavior: 'instant', block: 'end', inline: 'start' }); + } }, 0); } } diff --git a/src/app/components/floating-column/manage-account/bookmarks/bookmarks.component.ts b/src/app/components/floating-column/manage-account/bookmarks/bookmarks.component.ts index 42e3010d..0b148cb7 100644 --- a/src/app/components/floating-column/manage-account/bookmarks/bookmarks.component.ts +++ b/src/app/components/floating-column/manage-account/bookmarks/bookmarks.component.ts @@ -127,4 +127,15 @@ export class BookmarksComponent implements OnInit { browseThread(openThreadEvent: OpenThreadEvent): void { this.browseThreadEvent.next(openThreadEvent); } + + applyGoToTop(): boolean { + const stream = this.statustream.nativeElement as HTMLElement; + setTimeout(() => { + stream.scrollTo({ + top: 0, + behavior: 'smooth' + }); + }, 0); + return false; + } } diff --git a/src/app/components/floating-column/manage-account/direct-messages/direct-messages.component.ts b/src/app/components/floating-column/manage-account/direct-messages/direct-messages.component.ts index da6d00f2..c03de70f 100644 --- a/src/app/components/floating-column/manage-account/direct-messages/direct-messages.component.ts +++ b/src/app/components/floating-column/manage-account/direct-messages/direct-messages.component.ts @@ -121,6 +121,17 @@ export class DirectMessagesComponent implements OnInit { browseThread(openThreadEvent: OpenThreadEvent): void { this.browseThreadEvent.next(openThreadEvent); } + + applyGoToTop(): boolean { + const stream = this.statustream.nativeElement as HTMLElement; + setTimeout(() => { + stream.scrollTo({ + top: 0, + behavior: 'smooth' + }); + }, 0); + return false; + } } class ConversationWrapper { diff --git a/src/app/components/floating-column/manage-account/favorites/favorites.component.ts b/src/app/components/floating-column/manage-account/favorites/favorites.component.ts index 6f5dfd83..89e8e4af 100644 --- a/src/app/components/floating-column/manage-account/favorites/favorites.component.ts +++ b/src/app/components/floating-column/manage-account/favorites/favorites.component.ts @@ -128,4 +128,15 @@ export class FavoritesComponent implements OnInit { browseThread(openThreadEvent: OpenThreadEvent): void { this.browseThreadEvent.next(openThreadEvent); } + + applyGoToTop(): boolean { + const stream = this.statustream.nativeElement as HTMLElement; + setTimeout(() => { + stream.scrollTo({ + top: 0, + behavior: 'smooth' + }); + }, 0); + return false; + } } diff --git a/src/app/components/floating-column/manage-account/manage-account.component.html b/src/app/components/floating-column/manage-account/manage-account.component.html index 4ad23889..f1916c76 100644 --- a/src/app/components/floating-column/manage-account/manage-account.component.html +++ b/src/app/components/floating-column/manage-account/manage-account.component.html @@ -35,20 +35,20 @@
- - - - - \ No newline at end of file diff --git a/src/app/components/floating-column/manage-account/manage-account.component.ts b/src/app/components/floating-column/manage-account/manage-account.component.ts index c6fdfe69..1f314a68 100644 --- a/src/app/components/floating-column/manage-account/manage-account.component.ts +++ b/src/app/components/floating-column/manage-account/manage-account.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core'; +import { Component, OnInit, OnDestroy, Input, Output, EventEmitter, ViewChild } from '@angular/core'; import { faAt, faUserPlus } from "@fortawesome/free-solid-svg-icons"; import { faBell, faEnvelope, faUser, faStar, faBookmark } from "@fortawesome/free-regular-svg-icons"; import { Subscription } from 'rxjs'; @@ -10,6 +10,11 @@ import { MastodonWrapperService } from '../../../services/mastodon-wrapper.servi import { Account } from "../../../services/models/mastodon.interfaces"; import { NotificationService } from '../../../services/notification.service'; import { AccountInfo } from '../../../states/accounts.state'; +import { BookmarksComponent } from './bookmarks/bookmarks.component'; +import { NotificationsComponent } from './notifications/notifications.component'; +import { MentionsComponent } from './mentions/mentions.component'; +import { DirectMessagesComponent } from './direct-messages/direct-messages.component'; +import { FavoritesComponent } from './favorites/favorites.component'; @Component({ @@ -122,8 +127,35 @@ export class ManageAccountComponent implements OnInit, OnDestroy { } } - loadSubPanel(subpanel: 'account' | 'notifications' | 'mentions' | 'dm' | 'favorites'): boolean { + @ViewChild('bookmarks') bookmarksComp:BookmarksComponent; + @ViewChild('notifications') notificationsComp:NotificationsComponent; + @ViewChild('mentions') mentionsComp:MentionsComponent; + @ViewChild('dm') dmComp:DirectMessagesComponent; + @ViewChild('favorites') favoritesComp:FavoritesComponent; + + loadSubPanel(subpanel: 'account' | 'notifications' | 'mentions' | 'dm' | 'favorites' | 'bookmarks'): boolean { + if(this.subPanel === subpanel){ + switch(subpanel){ + case 'bookmarks': + this.bookmarksComp.applyGoToTop(); + break; + case 'notifications': + this.notificationsComp.applyGoToTop(); + break; + case 'mentions': + this.mentionsComp.applyGoToTop(); + break; + case 'dm': + this.dmComp.applyGoToTop(); + break; + case 'favorites': + this.favoritesComp.applyGoToTop(); + break; + } + } + this.subPanel = subpanel; + return false; } diff --git a/src/app/components/floating-column/manage-account/mentions/mentions.component.ts b/src/app/components/floating-column/manage-account/mentions/mentions.component.ts index b90a56a2..cee550d4 100644 --- a/src/app/components/floating-column/manage-account/mentions/mentions.component.ts +++ b/src/app/components/floating-column/manage-account/mentions/mentions.component.ts @@ -146,4 +146,15 @@ export class MentionsComponent implements OnInit, OnDestroy { browseThread(openThreadEvent: OpenThreadEvent): void { this.browseThreadEvent.next(openThreadEvent); } + + applyGoToTop(): boolean { + const stream = this.statustream.nativeElement as HTMLElement; + setTimeout(() => { + stream.scrollTo({ + top: 0, + behavior: 'smooth' + }); + }, 0); + return false; + } } diff --git a/src/app/components/floating-column/manage-account/my-account/my-account.component.html b/src/app/components/floating-column/manage-account/my-account/my-account.component.html index b18a8fea..adcb39fc 100644 --- a/src/app/components/floating-column/manage-account/my-account/my-account.component.html +++ b/src/app/components/floating-column/manage-account/my-account/my-account.component.html @@ -1,11 +1,16 @@

add timeline:

- - {{ stream.name }} - + +

manage list:

- -
this settings needs a reload to be effective.

Timelines

@@ -123,9 +117,6 @@
- this settings needs a reload to be effective. - loading behavior:

- - this settings needs a reload to be effective.

Other

diff --git a/src/app/components/floating-column/settings/settings.component.ts b/src/app/components/floating-column/settings/settings.component.ts index b8aacd77..190a0c56 100644 --- a/src/app/components/floating-column/settings/settings.component.ts +++ b/src/app/components/floating-column/settings/settings.component.ts @@ -7,6 +7,7 @@ import { ToolsService } from '../../../services/tools.service'; import { UserNotificationService, NotificationSoundDefinition } from '../../../services/user-notification.service'; import { ServiceWorkerService } from '../../../services/service-worker.service'; import { ContentWarningPolicy, ContentWarningPolicyEnum, TimeLineModeEnum, TimeLineHeaderEnum } from '../../../states/settings.state'; +import { NotificationService } from '../../../services/notification.service'; @Component({ selector: 'app-settings', @@ -27,16 +28,9 @@ export class SettingsComponent implements OnInit { version: string; columnShortcutEnabled: ColumnShortcut = ColumnShortcut.Ctrl; - columnShortcutChanged = false; - timeLineHeader: TimeLineHeaderEnum = TimeLineHeaderEnum.Title_DomainName; - timeLineHeaderChanged = false; - timeLineMode: TimeLineModeEnum = TimeLineModeEnum.OnTop; - timeLineModeChanged = false; - contentWarningPolicy: ContentWarningPolicyEnum = ContentWarningPolicyEnum.None; - contentWarningPolicyChanged = false; private addCwOnContent: string; set setAddCwOnContent(value: string) { @@ -69,6 +63,7 @@ export class SettingsComponent implements OnInit { private formBuilder: FormBuilder, private serviceWorkersService: ServiceWorkerService, private readonly toolsService: ToolsService, + private readonly notificationService: NotificationService, private readonly userNotificationsService: UserNotificationService) { } ngOnInit() { @@ -104,7 +99,7 @@ export class SettingsComponent implements OnInit { onShortcutChange(id: ColumnShortcut) { this.columnShortcutEnabled = id; - this.columnShortcutChanged = true; + this.notifyRestartNeeded(); let settings = this.toolsService.getSettings(); settings.columnSwitchingWinAlt = id === ColumnShortcut.Win; @@ -113,7 +108,7 @@ export class SettingsComponent implements OnInit { onTimeLineHeaderChange(id: TimeLineHeaderEnum){ this.timeLineHeader = id; - this.timeLineHeaderChanged = true; + this.notifyRestartNeeded(); let settings = this.toolsService.getSettings(); settings.timelineHeader = id; @@ -122,7 +117,7 @@ export class SettingsComponent implements OnInit { onTimeLineModeChange(id: TimeLineModeEnum){ this.timeLineMode = id; - this.timeLineModeChanged = true; + this.notifyRestartNeeded(); let settings = this.toolsService.getSettings(); settings.timelineMode = id; @@ -131,13 +126,13 @@ export class SettingsComponent implements OnInit { onCwPolicyChange(id: ContentWarningPolicyEnum) { this.contentWarningPolicy = id; - this.contentWarningPolicyChanged = true; + this.notifyRestartNeeded(); this.setCwPolicy(id); } private setCwPolicy(id: ContentWarningPolicyEnum = null, addCw: string = null, removeCw: string = null, hide: string = null){ - this.contentWarningPolicyChanged = true; + this.notifyRestartNeeded(); let settings = this.toolsService.getSettings(); let cwPolicySettings = new ContentWarningPolicy(); @@ -172,10 +167,10 @@ export class SettingsComponent implements OnInit { return data.split(';').map(x => x.trim().toLowerCase()).filter((value, index, self) => self.indexOf(value) === index).filter(y => y !== ''); } - reload(): boolean { - window.location.reload(); - return false; - } + // reload(): boolean { + // window.location.reload(); + // return false; + // } onChange(soundId: string) { this.notificationSoundId = soundId; @@ -196,18 +191,21 @@ export class SettingsComponent implements OnInit { } onDisableAutofocusChanged() { + this.notifyRestartNeeded(); let settings = this.toolsService.getSettings(); settings.disableAutofocus = this.disableAutofocusEnabled; this.toolsService.saveSettings(settings); } onDisableRemoteStatusFetchingChanged() { + this.notifyRestartNeeded(); let settings = this.toolsService.getSettings(); settings.disableRemoteStatusFetching = this.disableRemoteStatusFetchingEnabled; this.toolsService.saveSettings(settings); } onDisableAvatarNotificationsChanged() { + this.notifyRestartNeeded(); let settings = this.toolsService.getSettings(); settings.disableAvatarNotifications = this.disableAvatarNotificationsEnabled; this.toolsService.saveSettings(settings); @@ -248,6 +246,10 @@ export class SettingsComponent implements OnInit { }); return false; } + + notifyRestartNeeded(){ + this.notificationService.notifyRestartNotification('Reload to apply changes'); + } } enum ColumnShortcut { diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts index 20fe2a3b..f1f43893 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.spec.ts @@ -31,6 +31,12 @@ describe('DatabindedTextComponent', () => { expect(component.processedText).toContain(sample); }); + it('should parse href text', () => { + const sample = '

href

'; + component.text = sample; + expect(component.processedText).toBe(sample); + }); + it('should parse hashtag', () => { const hashtag = 'programmers'; const url = 'https://test.social/tags/programmers'; diff --git a/src/app/components/stream/status/databinded-text/databinded-text.component.ts b/src/app/components/stream/status/databinded-text/databinded-text.component.ts index e8c3f6e1..efdc1160 100644 --- a/src/app/components/stream/status/databinded-text/databinded-text.component.ts +++ b/src/app/components/stream/status/databinded-text/databinded-text.component.ts @@ -28,7 +28,7 @@ export class DatabindedTextComponent implements OnInit { @Input('text') set text(value: string) { - //console.warn(value); + // console.warn(value); let parser = new DOMParser(); var dom = parser.parseFromString(value, 'text/html') @@ -133,6 +133,11 @@ export class DatabindedTextComponent implements OnInit { } private processLink(section: string) { + if(!section.includes('')){ + this.processedText += section; + return; + } + let extractedLinkAndNext = section.split('') let extractedUrl = extractedLinkAndNext[0].split('"')[1]; diff --git a/src/app/components/stream/user-profile/user-profile.component.ts b/src/app/components/stream/user-profile/user-profile.component.ts index c9c1b586..f89809ce 100644 --- a/src/app/components/stream/user-profile/user-profile.component.ts +++ b/src/app/components/stream/user-profile/user-profile.component.ts @@ -266,6 +266,7 @@ export class UserProfileComponent implements OnInit { refresh(): any { this.showFloatingHeader = false; + this.showFloatingStatusMenu = false; this.load(this.lastAccountName); } diff --git a/src/app/pipes/account-emoji.pipe.ts b/src/app/pipes/account-emoji.pipe.ts index 31480535..50cb6a96 100644 --- a/src/app/pipes/account-emoji.pipe.ts +++ b/src/app/pipes/account-emoji.pipe.ts @@ -4,19 +4,22 @@ import { EmojiConverter, EmojiTypeEnum } from '../tools/emoji.tools'; import { Account } from '../services/models/mastodon.interfaces'; @Pipe({ - name: "accountEmoji" + name: "accountEmoji" }) export class AccountEmojiPipe implements PipeTransform { - private emojiConverter = new EmojiConverter(); + private emojiConverter = new EmojiConverter(); - transform(value: Account, text?: string): any { + transform(value: Account, text?: string): any { + try { + let textToTransform = text; + if (!text) { + if (value.display_name) textToTransform = value.display_name; + else textToTransform = value.acct.split('@')[0]; + } - let textToTransform = text; - if(!text){ - if(value.display_name) textToTransform = value.display_name; - else textToTransform = value.acct.split('@')[0]; - } - - return this.emojiConverter.applyEmojis(value.emojis, textToTransform, EmojiTypeEnum.small) - } + return this.emojiConverter.applyEmojis(value.emojis, textToTransform, EmojiTypeEnum.small); + } catch (err){ + return ''; + } + } } diff --git a/src/app/services/mastodon-wrapper.service.ts b/src/app/services/mastodon-wrapper.service.ts index 694a73b6..db2cad4e 100644 --- a/src/app/services/mastodon-wrapper.service.ts +++ b/src/app/services/mastodon-wrapper.service.ts @@ -12,6 +12,7 @@ import { AppInfo, RegisteredAppsStateModel } from '../states/registered-apps.sta providedIn: 'root' }) export class MastodonWrapperService { + private refreshingToken: { [id: string]: Promise } = {}; constructor( private readonly store: Store, @@ -19,6 +20,10 @@ export class MastodonWrapperService { private readonly mastodonService: MastodonService) { } refreshAccountIfNeeded(accountInfo: AccountInfo): Promise { + if(this.refreshingToken[accountInfo.id]){ + return this.refreshingToken[accountInfo.id]; + } + let isExpired = false; let storedAccountInfo = this.getStoreAccountInfo(accountInfo.id); @@ -47,11 +52,8 @@ export class MastodonWrapperService { } if (storedAccountInfo.token.refresh_token && isExpired) { - console.log('>>> MARTY!! ------------'); - console.log('>>> RENEW TOKEN FFS ----'); - const app = this.getAllSavedApps().find(x => x.instance === storedAccountInfo.instance); - return this.authService.refreshToken(storedAccountInfo.instance, app.app.client_id, app.app.client_secret, storedAccountInfo.token.refresh_token) + let p = this.authService.refreshToken(storedAccountInfo.instance, app.app.client_id, app.app.client_secret, storedAccountInfo.token.refresh_token) .then((tokenData: TokenData) => { if (tokenData.refresh_token && !tokenData.created_at) { const nowEpoch = Date.now() / 1000 | 0; @@ -66,6 +68,13 @@ export class MastodonWrapperService { .catch(err => { return Promise.resolve(storedAccountInfo); }); + + p.then(() => { + this.refreshingToken[accountInfo.id] = null; + }); + + this.refreshingToken[accountInfo.id] = p; + return p; } else { return Promise.resolve(storedAccountInfo); } diff --git a/src/app/services/notification.service.ts b/src/app/services/notification.service.ts index a8406bce..80696dad 100644 --- a/src/app/services/notification.service.ts +++ b/src/app/services/notification.service.ts @@ -9,6 +9,7 @@ import { ToolsService } from './tools.service'; @Injectable() export class NotificationService { + public restartNotificationStream = new Subject(); public notifactionStream = new Subject(); public newRespondPostedStream = new Subject(); public hideAccountUrlStream = new Subject(); @@ -60,6 +61,10 @@ export class NotificationService { public deleteStatus(status: StatusWrapper) { this.deletedStatusStream.next(status); } + + public notifyRestartNotification(label: string){ + this.restartNotificationStream.next(label); + } } export class NotificatioData { diff --git a/src/app/services/service-worker.service.ts b/src/app/services/service-worker.service.ts index f5dfc761..6b477517 100644 --- a/src/app/services/service-worker.service.ts +++ b/src/app/services/service-worker.service.ts @@ -1,17 +1,19 @@ import { Injectable, ApplicationRef } from '@angular/core'; import { SwUpdate } from '@angular/service-worker'; -import { first } from 'rxjs/operators'; import { interval, concat, BehaviorSubject } from 'rxjs'; +import { NotificationService } from './notification.service'; + @Injectable({ providedIn: 'root' }) export class ServiceWorkerService { - newAppVersionIsAvailable = new BehaviorSubject(false); - private isListening = false; - constructor(appRef: ApplicationRef, private updates: SwUpdate) { + constructor( + appRef: ApplicationRef, + private updates: SwUpdate, + private notificationService: NotificationService) { //https://angular.io/guide/service-worker-communications @@ -19,7 +21,7 @@ export class ServiceWorkerService { console.log('current version is', event.current); console.log('available version is', event.available); - this.newAppVersionIsAvailable.next(true); + this.notificationService.notifyRestartNotification('A new version is available!'); }); // Allow the app to stabilize first, before starting polling for updates with `interval()`. @@ -47,6 +49,6 @@ export class ServiceWorkerService { } checkForUpdates(): Promise { - return this.updates.checkForUpdate(); + return this.updates.checkForUpdate(); } } \ No newline at end of file diff --git a/src/app/services/user-notification.service.ts b/src/app/services/user-notification.service.ts index 7c0413c5..f2545b53 100644 --- a/src/app/services/user-notification.service.ts +++ b/src/app/services/user-notification.service.ts @@ -25,82 +25,94 @@ export class UserNotificationService { private soundJustPlayed = false; private soundFileId: string; + private accountSub: Subscription; + private loadedAccounts: AccountInfo[] = []; + constructor( private readonly streamingService: StreamingService, private readonly toolsService: ToolsService, private readonly notificationService: NotificationService, private readonly mastodonService: MastodonWrapperService, private readonly store: Store) { - + this.fetchNotifications(); - } + } private fetchNotifications() { let accounts = this.store.snapshot().registeredaccounts.accounts; - // let promises: Promise[] = []; accounts.forEach((account: AccountInfo) => { - // let sinceId = null; - // if (this.sinceIds[account.id]) { - // sinceId = this.sinceIds[account.id]; - // } - - let getMentionsPromise = this.mastodonService.getNotifications(account, ['favourite', 'follow', 'reblog', 'poll'], null, null, 10) - .then((notifications: Notification[]) => { - this.processMentionsAndNotifications(account, notifications, NotificationTypeEnum.UserMention); - }) - .catch(err => { - this.notificationService.notifyHttpError(err, account); - }); - - let getNotificationPromise = this.mastodonService.getNotifications(account, ['mention'], null, null, 10) - .then((notifications: Notification[]) => { - this.processMentionsAndNotifications(account, notifications, NotificationTypeEnum.UserNotification); - }) - .catch(err => { - this.notificationService.notifyHttpError(err, account); - }); - - Promise.all([getMentionsPromise, getNotificationPromise]) - .then(() => { - let streamElement = new StreamElement(StreamTypeEnum.personnal, 'activity', account.id, null, null, null, account.instance); - - let streaming = this.streamingService.getStreaming(account, streamElement); - streaming.statusUpdateSubjet.subscribe((notification: StatusUpdate) => { - if (notification && notification.type === EventEnum.notification) { - this.processNewUpdate(account, notification); - } - }); - }) - .catch(err => { }); + this.loadedAccounts.push(account); + this.startFetchingNotifications(account); }); + + this.accountSub = this.store.select(state => state.registeredaccounts.accounts) + .subscribe((accounts: AccountInfo[]) => { + accounts.forEach(a => { + if(!this.loadedAccounts.find(x => x.id === a.id)){ + this.loadedAccounts.push(a); + this.startFetchingNotifications(a); + } + }); + }); + } + + private startFetchingNotifications(account: AccountInfo) { + let getMentionsPromise = this.mastodonService.getNotifications(account, ['favourite', 'follow', 'reblog', 'poll'], null, null, 10) + .then((notifications: Notification[]) => { + this.processMentionsAndNotifications(account, notifications, NotificationTypeEnum.UserMention); + }) + .catch(err => { + this.notificationService.notifyHttpError(err, account); + }); + + let getNotificationPromise = this.mastodonService.getNotifications(account, ['mention'], null, null, 10) + .then((notifications: Notification[]) => { + this.processMentionsAndNotifications(account, notifications, NotificationTypeEnum.UserNotification); + }) + .catch(err => { + this.notificationService.notifyHttpError(err, account); + }); + + Promise.all([getMentionsPromise, getNotificationPromise]) + .then(() => { + let streamElement = new StreamElement(StreamTypeEnum.personnal, 'activity', account.id, null, null, null, account.instance); + + let streaming = this.streamingService.getStreaming(account, streamElement); + streaming.statusUpdateSubjet.subscribe((notification: StatusUpdate) => { + if (notification && notification.type === EventEnum.notification) { + this.processNewUpdate(account, notification); + } + }); + }) + .catch(err => { }); } private playSoundNotification() { const settings = this.toolsService.getSettings(); - if(settings.disableSounds) return; - if(this.soundJustPlayed) return; + if (settings.disableSounds) return; + if (this.soundJustPlayed) return; this.soundJustPlayed = true; - + this.setNotificationSound(); this.sound.play(); - setTimeout(() => { + setTimeout(() => { this.soundJustPlayed = false; }, 2000); } private setNotificationSound() { let settings = this.toolsService.getSettings(); - let soundId = settings.notificationSoundFileId; - - if(!soundId){ + let soundId = settings.notificationSoundFileId; + + if (!soundId) { soundId = '0'; settings.notificationSoundFileId = '0'; this.toolsService.saveSettings(settings); } - if(this.soundFileId === soundId) return; + if (this.soundFileId === soundId) return; var sound = this.getAllNotificationSounds().find(x => x.id === soundId); this.sound = new Howl({ @@ -110,9 +122,9 @@ export class UserNotificationService { } private processNewUpdate(account: AccountInfo, notification: StatusUpdate) { - if(!notification && !notification.notification) return; + if (!notification && !notification.notification) return; - if(!notification.muteSound){ + if (!notification.muteSound) { this.playSoundNotification(); } @@ -129,15 +141,15 @@ export class UserNotificationService { } let currentNotifications = this.userNotifications.value; - let currentAccountNotifications = currentNotifications.find(x => x.account.id === account.id); + let currentAccountNotifications = currentNotifications.find(x => x.account.id === account.id); if (currentAccountNotifications) { currentAccountNotifications = this.analyseNotifications(account, currentAccountNotifications, notifications, type); //if (currentAccountNotifications.hasNewMentions || currentAccountNotifications.hasNewNotifications) { - currentNotifications = currentNotifications.filter(x => x.account.id !== account.id); - currentNotifications.push(currentAccountNotifications); - this.userNotifications.next(currentNotifications); + currentNotifications = currentNotifications.filter(x => x.account.id !== account.id); + currentNotifications.push(currentAccountNotifications); + this.userNotifications.next(currentNotifications); //} } else { let newNotifications = new UserNotification(); @@ -230,7 +242,7 @@ export class UserNotificationService { new NotificationSoundDefinition('0', 'assets/audio/all-eyes-on-me.mp3', 'All eyes on me'), new NotificationSoundDefinition('1', 'assets/audio/exquisite.mp3', 'Exquisite'), new NotificationSoundDefinition('2', 'assets/audio/appointed.mp3', 'Appointed'), - new NotificationSoundDefinition('3', 'assets/audio/boop.mp3', 'Mastodon boop'), + new NotificationSoundDefinition('3', 'assets/audio/boop.mp3', 'Mastodon boop'), ]; return defs; } @@ -261,5 +273,5 @@ export class NotificationSoundDefinition { constructor( public readonly id: string, public readonly path: string, - public readonly name: string) {} + public readonly name: string) { } } \ No newline at end of file