created service and account animation #55
This commit is contained in:
parent
a8430b0354
commit
22b39b3c53
|
@ -1,4 +1,5 @@
|
|||
<a class="account-icon"
|
||||
href title="{{ account.info.id }}" (click)="toogleAccount()" (contextmenu)="openMenu()">
|
||||
<img class="account-icon__avatar" [class.account-icon__avatar--selected]="account.info.isSelected" src="{{ account.avatar }}" />
|
||||
<span class="hasActivity" *ngIf="account.hasActivityNotifications">new</span>
|
||||
<img class="account-icon__avatar" [class.account-icon__avatar--selected]="account.info.isSelected" src="{{ account.avatar }}" />
|
||||
</a>
|
||||
|
|
|
@ -1,45 +1,98 @@
|
|||
.account-icon {
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
// padding-top: 4px;
|
||||
width: 50px; // padding-top: 4px;
|
||||
// margin-left: 5px;
|
||||
margin: 0 0 5px 5px;
|
||||
|
||||
|
||||
|
||||
|
||||
&__avatar {
|
||||
border-radius: 50%;
|
||||
border-radius: 2px;
|
||||
width: 40px;
|
||||
opacity: .3;
|
||||
transition: all .2s;
|
||||
|
||||
&:hover {
|
||||
filter: alpha(opacity=50);
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
&--selected {
|
||||
// border-radius: 20%;
|
||||
filter: alpha(opacity=100);
|
||||
opacity: 1;
|
||||
|
||||
&:hover {
|
||||
filter: alpha(opacity=100);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// & a {
|
||||
// margin-left: 4px;
|
||||
// /*margin-top: 4px;*/
|
||||
// }
|
||||
// & img {
|
||||
// width: 40px;
|
||||
// border-radius: 50%;
|
||||
// }
|
||||
}
|
||||
|
||||
@keyframes flickerAnimation {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@-o-keyframes flickerAnimation {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@-moz-keyframes flickerAnimation {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes flickerAnimation {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.hasActivity {
|
||||
|
||||
-webkit-animation: flickerAnimation 2s infinite;
|
||||
-moz-animation: flickerAnimation 2s infinite;
|
||||
-o-animation: flickerAnimation 2s infinite;
|
||||
animation: flickerAnimation 2s infinite;
|
||||
|
||||
border-radius: 2px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
position: absolute;
|
||||
border: 2px solid orange;
|
||||
z-index: 20;
|
||||
color: orange;
|
||||
font-size: 10px;
|
||||
font-style: italic;
|
||||
padding: 23px 0 0 3px;
|
||||
|
||||
background: rgba(0,0,0, .55);
|
||||
|
||||
&:hover {
|
||||
color: orange;
|
||||
}
|
||||
}
|
|
@ -1,28 +1,30 @@
|
|||
import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
|
||||
|
||||
import { AccountWrapper } from '../../../models/account.models';
|
||||
import { AccountWithNotificationWrapper } from '../left-side-bar.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-account-icon',
|
||||
templateUrl: './account-icon.component.html',
|
||||
styleUrls: ['./account-icon.component.scss']
|
||||
selector: 'app-account-icon',
|
||||
templateUrl: './account-icon.component.html',
|
||||
styleUrls: ['./account-icon.component.scss']
|
||||
})
|
||||
export class AccountIconComponent implements OnInit {
|
||||
@Input() account: AccountWrapper;
|
||||
@Output() toogleAccountNotify = new EventEmitter<AccountWrapper>();
|
||||
@Output() openMenuNotify = new EventEmitter<AccountWrapper>();
|
||||
@Input() account: AccountWithNotificationWrapper;
|
||||
@Output() toogleAccountNotify = new EventEmitter<AccountWrapper>();
|
||||
@Output() openMenuNotify = new EventEmitter<AccountWrapper>();
|
||||
|
||||
constructor() { }
|
||||
constructor() { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
toogleAccount(): boolean {
|
||||
this.toogleAccountNotify.emit(this.account);
|
||||
return false;
|
||||
}
|
||||
toogleAccount(): boolean {
|
||||
this.toogleAccountNotify.emit(this.account);
|
||||
return false;
|
||||
}
|
||||
|
||||
openMenu(): boolean {
|
||||
this.openMenuNotify.emit(this.account);
|
||||
return false;
|
||||
}
|
||||
openMenu(): boolean {
|
||||
this.openMenuNotify.emit(this.account);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import { AccountInfo, SelectAccount } from "../../states/accounts.state";
|
|||
import { NavigationService, LeftPanelType } from "../../services/navigation.service";
|
||||
import { MastodonService } from "../../services/mastodon.service";
|
||||
import { NotificationService } from "../../services/notification.service";
|
||||
import { UserNotificationServiceService, UserNotification } from '../../services/user-notification-service.service';
|
||||
|
||||
@Component({
|
||||
selector: "app-left-side-bar",
|
||||
|
@ -19,14 +20,15 @@ import { NotificationService } from "../../services/notification.service";
|
|||
export class LeftSideBarComponent implements OnInit, OnDestroy {
|
||||
faCommentAlt = faCommentAlt;
|
||||
|
||||
accounts: AccountWrapper[] = [];
|
||||
accounts: AccountWithNotificationWrapper[] = [];
|
||||
hasAccounts: boolean;
|
||||
private accounts$: Observable<AccountInfo[]>;
|
||||
|
||||
// private loadedAccounts: { [index: string]: AccountInfo } = {};
|
||||
private sub: Subscription;
|
||||
private accountSub: Subscription;
|
||||
private notificationSub: Subscription;
|
||||
|
||||
constructor(
|
||||
private readonly userNotificationServiceService: UserNotificationServiceService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly navigationService: NavigationService,
|
||||
private readonly mastodonService: MastodonService,
|
||||
|
@ -37,7 +39,7 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
|
|||
|
||||
private currentLoading: number;
|
||||
ngOnInit() {
|
||||
this.accounts$.subscribe((accounts: AccountInfo[]) => {
|
||||
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
|
||||
if (accounts) {
|
||||
//Update and Add
|
||||
for (let acc of accounts) {
|
||||
|
@ -45,8 +47,9 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
|
|||
if (previousAcc) {
|
||||
previousAcc.info.isSelected = acc.isSelected;
|
||||
} else {
|
||||
const accWrapper = new AccountWrapper();
|
||||
const accWrapper = new AccountWithNotificationWrapper();
|
||||
accWrapper.info = acc;
|
||||
|
||||
this.accounts.push(accWrapper);
|
||||
|
||||
this.mastodonService.retrieveAccountDetails(acc)
|
||||
|
@ -61,17 +64,31 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
|
|||
|
||||
//Delete
|
||||
const deletedAccounts = this.accounts.filter(x => accounts.findIndex(y => y.id === x.info.id) === -1);
|
||||
for(let delAcc of deletedAccounts){
|
||||
for (let delAcc of deletedAccounts) {
|
||||
this.accounts = this.accounts.filter(x => x.info.id !== delAcc.info.id);
|
||||
}
|
||||
|
||||
this.hasAccounts = this.accounts.length > 0;
|
||||
}
|
||||
});
|
||||
|
||||
this.notificationSub = this.userNotificationServiceService.userNotifications.subscribe((notifications: UserNotification[]) => {
|
||||
|
||||
notifications.forEach((notification: UserNotification) => {
|
||||
const acc = this.accounts.find(x => x.info.id === notification.account.id);
|
||||
if(acc){
|
||||
acc.hasActivityNotifications = notification.hasNewMentions || notification.hasNewNotifications;
|
||||
}
|
||||
});
|
||||
|
||||
console.warn('new notifications');
|
||||
console.warn(notifications);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.sub.unsubscribe();
|
||||
this.accountSub.unsubscribe();
|
||||
this.notificationSub.unsubscribe();
|
||||
}
|
||||
|
||||
onToogleAccountNotify(acc: AccountWrapper) {
|
||||
|
@ -102,3 +119,14 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class AccountWithNotificationWrapper extends AccountWrapper {
|
||||
// constructor(accountWrapper: AccountWrapper) {
|
||||
// super();
|
||||
|
||||
// this.avatar = accountWrapper.avatar;
|
||||
// this.info = accountWrapper.info;
|
||||
// }
|
||||
|
||||
hasActivityNotifications: boolean;
|
||||
}
|
|
@ -2,12 +2,12 @@ import { Injectable } from '@angular/core';
|
|||
import { HttpHeaders, HttpClient, HttpResponse } from '@angular/common/http';
|
||||
|
||||
import { ApiRoutes } from './models/api.settings';
|
||||
import { Account, Status, Results, Context, Relationship, Instance, Attachment } from "./models/mastodon.interfaces";
|
||||
import { Account, Status, Results, Context, Relationship, Instance, Attachment, Notification } from "./models/mastodon.interfaces";
|
||||
import { AccountInfo } from '../states/accounts.state';
|
||||
import { StreamTypeEnum } from '../states/streams.state';
|
||||
|
||||
@Injectable()
|
||||
export class MastodonService {
|
||||
export class MastodonService {
|
||||
private apiRoutes = new ApiRoutes();
|
||||
|
||||
constructor(private readonly httpClient: HttpClient) { }
|
||||
|
@ -229,6 +229,26 @@ export class MastodonService {
|
|||
return this.httpClient.put<Attachment>(route, input, { headers: headers }).toPromise();
|
||||
}
|
||||
|
||||
getNotifications(account: AccountInfo, excludeTypes: string[] = null, maxId: string = null, sinceId: string = null, limit: number = 15): Promise<Notification[]> {
|
||||
let route = `https://${account.instance}${this.apiRoutes.getNotifications}?limit=${limit}`;
|
||||
|
||||
if(maxId){
|
||||
route += `&max_id=${maxId}`;
|
||||
}
|
||||
|
||||
if(sinceId){
|
||||
route += `&since_id=${sinceId}`;
|
||||
}
|
||||
|
||||
if(excludeTypes && excludeTypes.length > 0) {
|
||||
const excludeTypeArray = this.formatArray(excludeTypes, 'exclude_types');
|
||||
route += `&${excludeTypeArray}`;
|
||||
}
|
||||
|
||||
const headers = new HttpHeaders({ 'Authorization': `Bearer ${account.token.access_token}` });
|
||||
return this.httpClient.get<Notification[]>(route, { headers: headers }).toPromise();
|
||||
}
|
||||
|
||||
private formatArray(data: string[], paramName: string): string {
|
||||
let result = '';
|
||||
data.forEach(x => {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { UserNotificationServiceService } from './user-notification-service.service';
|
||||
|
||||
xdescribe('UserNotificationServiceService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: UserNotificationServiceService = TestBed.get(UserNotificationServiceService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,94 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject, Subject, Observable, Subscription } from 'rxjs';
|
||||
import { Store } from '@ngxs/store';
|
||||
|
||||
import { Status, Notification } from './models/mastodon.interfaces';
|
||||
import { MastodonService } from './mastodon.service';
|
||||
import { AccountInfo } from '../states/accounts.state';
|
||||
import { NotificationService } from './notification.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UserNotificationServiceService {
|
||||
|
||||
userNotifications = new BehaviorSubject<UserNotification[]>([]);
|
||||
|
||||
constructor(
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly mastodonService: MastodonService,
|
||||
private readonly store: Store) {
|
||||
|
||||
this.fetchNotifications();
|
||||
}
|
||||
|
||||
private fetchNotifications() {
|
||||
let accounts = this.store.snapshot().registeredaccounts.accounts;
|
||||
let promises: Promise<any>[] = [];
|
||||
|
||||
accounts.forEach(account => {
|
||||
let getNotificationPromise = this.mastodonService.getNotifications(account)
|
||||
.then((notifications: Notification[]) => {
|
||||
this.processNotifications(account, notifications);
|
||||
})
|
||||
.catch(err => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
promises.push(getNotificationPromise);
|
||||
});
|
||||
|
||||
Promise.all(promises)
|
||||
.then(() => {
|
||||
setTimeout(() => {
|
||||
this.fetchNotifications();
|
||||
}, 15 * 1000);
|
||||
});
|
||||
}
|
||||
|
||||
private processNotifications(account: AccountInfo, notifications: Notification[]) {
|
||||
let currentNotifications = this.userNotifications.value;
|
||||
const currentAccountNotifications = currentNotifications.find(x => x.account.id === account.id);
|
||||
|
||||
const userNotifications = notifications.filter(x => x.type !== 'mention');
|
||||
const userMentions = notifications.filter(x => x.type === 'mention').map(x => x.status);
|
||||
|
||||
if (currentAccountNotifications) {
|
||||
const currentUserNotifications = currentAccountNotifications.notifications;
|
||||
const currentUserMentions = currentAccountNotifications.mentions;
|
||||
|
||||
const hasNewNotifications = (userNotifications.length === 0 && currentUserNotifications.length > 0)
|
||||
|| (userNotifications.length > 0 && currentUserNotifications.length > 0) && (userNotifications[0].id !== currentUserNotifications[0].id);
|
||||
const hasNewMentions = (userMentions.length === 0 && currentUserMentions.length > 0)
|
||||
|| (userMentions.length > 0 && currentUserMentions.length > 0) && (userMentions[0].id !== currentUserMentions[0].id);
|
||||
|
||||
if (hasNewNotifications || hasNewMentions) {
|
||||
currentAccountNotifications.hasNewMentions = hasNewMentions;
|
||||
currentAccountNotifications.hasNewNotifications = hasNewNotifications;
|
||||
currentAccountNotifications.notifications = userNotifications;
|
||||
currentAccountNotifications.mentions = userMentions;
|
||||
|
||||
currentNotifications = currentNotifications.filter(x => x.account.id !== account.id);
|
||||
currentNotifications.push(currentAccountNotifications);
|
||||
this.userNotifications.next(currentNotifications);
|
||||
}
|
||||
} else {
|
||||
const newNotifications = new UserNotification();
|
||||
newNotifications.account = account;
|
||||
newNotifications.hasNewNotifications = false; //TODO: check in local settings
|
||||
newNotifications.hasNewMentions = false; //TODO: check in local settings
|
||||
newNotifications.notifications = userNotifications;
|
||||
newNotifications.mentions = userMentions;
|
||||
|
||||
currentNotifications.push(newNotifications);
|
||||
this.userNotifications.next(currentNotifications);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class UserNotification {
|
||||
account: AccountInfo;
|
||||
hasNewNotifications: boolean;
|
||||
hasNewMentions: boolean;
|
||||
notifications: Notification[] = [];
|
||||
mentions: Status[] = [];
|
||||
}
|
Loading…
Reference in New Issue