created service and account animation #55
This commit is contained in:
parent
a8430b0354
commit
22b39b3c53
|
@ -1,4 +1,5 @@
|
||||||
<a class="account-icon"
|
<a class="account-icon"
|
||||||
href title="{{ account.info.id }}" (click)="toogleAccount()" (contextmenu)="openMenu()">
|
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>
|
</a>
|
||||||
|
|
|
@ -1,45 +1,98 @@
|
||||||
.account-icon {
|
.account-icon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
width: 50px;
|
width: 50px; // padding-top: 4px;
|
||||||
// padding-top: 4px;
|
|
||||||
// margin-left: 5px;
|
// margin-left: 5px;
|
||||||
margin: 0 0 5px 5px;
|
margin: 0 0 5px 5px;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
&__avatar {
|
&__avatar {
|
||||||
border-radius: 50%;
|
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
width: 40px;
|
width: 40px;
|
||||||
opacity: .3;
|
opacity: .3;
|
||||||
transition: all .2s;
|
transition: all .2s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
filter: alpha(opacity=50);
|
filter: alpha(opacity=50);
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
}
|
}
|
||||||
|
|
||||||
&--selected {
|
&--selected {
|
||||||
// border-radius: 20%;
|
// border-radius: 20%;
|
||||||
filter: alpha(opacity=100);
|
filter: alpha(opacity=100);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
filter: alpha(opacity=100);
|
filter: alpha(opacity=100);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// & a {
|
@keyframes flickerAnimation {
|
||||||
// margin-left: 4px;
|
0% {
|
||||||
// /*margin-top: 4px;*/
|
opacity: 0;
|
||||||
// }
|
}
|
||||||
// & img {
|
50% {
|
||||||
// width: 40px;
|
opacity: 1;
|
||||||
// border-radius: 50%;
|
}
|
||||||
// }
|
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 { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
|
||||||
|
|
||||||
import { AccountWrapper } from '../../../models/account.models';
|
import { AccountWrapper } from '../../../models/account.models';
|
||||||
|
import { AccountWithNotificationWrapper } from '../left-side-bar.component';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-account-icon',
|
selector: 'app-account-icon',
|
||||||
templateUrl: './account-icon.component.html',
|
templateUrl: './account-icon.component.html',
|
||||||
styleUrls: ['./account-icon.component.scss']
|
styleUrls: ['./account-icon.component.scss']
|
||||||
})
|
})
|
||||||
export class AccountIconComponent implements OnInit {
|
export class AccountIconComponent implements OnInit {
|
||||||
@Input() account: AccountWrapper;
|
@Input() account: AccountWithNotificationWrapper;
|
||||||
@Output() toogleAccountNotify = new EventEmitter<AccountWrapper>();
|
@Output() toogleAccountNotify = new EventEmitter<AccountWrapper>();
|
||||||
@Output() openMenuNotify = new EventEmitter<AccountWrapper>();
|
@Output() openMenuNotify = new EventEmitter<AccountWrapper>();
|
||||||
|
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
toogleAccount(): boolean {
|
toogleAccount(): boolean {
|
||||||
this.toogleAccountNotify.emit(this.account);
|
this.toogleAccountNotify.emit(this.account);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
openMenu(): boolean {
|
openMenu(): boolean {
|
||||||
this.openMenuNotify.emit(this.account);
|
this.openMenuNotify.emit(this.account);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import { AccountInfo, SelectAccount } from "../../states/accounts.state";
|
||||||
import { NavigationService, LeftPanelType } from "../../services/navigation.service";
|
import { NavigationService, LeftPanelType } from "../../services/navigation.service";
|
||||||
import { MastodonService } from "../../services/mastodon.service";
|
import { MastodonService } from "../../services/mastodon.service";
|
||||||
import { NotificationService } from "../../services/notification.service";
|
import { NotificationService } from "../../services/notification.service";
|
||||||
|
import { UserNotificationServiceService, UserNotification } from '../../services/user-notification-service.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-left-side-bar",
|
selector: "app-left-side-bar",
|
||||||
|
@ -19,14 +20,15 @@ import { NotificationService } from "../../services/notification.service";
|
||||||
export class LeftSideBarComponent implements OnInit, OnDestroy {
|
export class LeftSideBarComponent implements OnInit, OnDestroy {
|
||||||
faCommentAlt = faCommentAlt;
|
faCommentAlt = faCommentAlt;
|
||||||
|
|
||||||
accounts: AccountWrapper[] = [];
|
accounts: AccountWithNotificationWrapper[] = [];
|
||||||
hasAccounts: boolean;
|
hasAccounts: boolean;
|
||||||
private accounts$: Observable<AccountInfo[]>;
|
private accounts$: Observable<AccountInfo[]>;
|
||||||
|
|
||||||
// private loadedAccounts: { [index: string]: AccountInfo } = {};
|
private accountSub: Subscription;
|
||||||
private sub: Subscription;
|
private notificationSub: Subscription;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private readonly userNotificationServiceService: UserNotificationServiceService,
|
||||||
private readonly notificationService: NotificationService,
|
private readonly notificationService: NotificationService,
|
||||||
private readonly navigationService: NavigationService,
|
private readonly navigationService: NavigationService,
|
||||||
private readonly mastodonService: MastodonService,
|
private readonly mastodonService: MastodonService,
|
||||||
|
@ -37,7 +39,7 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
private currentLoading: number;
|
private currentLoading: number;
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
this.accounts$.subscribe((accounts: AccountInfo[]) => {
|
this.accountSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
|
||||||
if (accounts) {
|
if (accounts) {
|
||||||
//Update and Add
|
//Update and Add
|
||||||
for (let acc of accounts) {
|
for (let acc of accounts) {
|
||||||
|
@ -45,8 +47,9 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
|
||||||
if (previousAcc) {
|
if (previousAcc) {
|
||||||
previousAcc.info.isSelected = acc.isSelected;
|
previousAcc.info.isSelected = acc.isSelected;
|
||||||
} else {
|
} else {
|
||||||
const accWrapper = new AccountWrapper();
|
const accWrapper = new AccountWithNotificationWrapper();
|
||||||
accWrapper.info = acc;
|
accWrapper.info = acc;
|
||||||
|
|
||||||
this.accounts.push(accWrapper);
|
this.accounts.push(accWrapper);
|
||||||
|
|
||||||
this.mastodonService.retrieveAccountDetails(acc)
|
this.mastodonService.retrieveAccountDetails(acc)
|
||||||
|
@ -61,17 +64,31 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
|
||||||
|
|
||||||
//Delete
|
//Delete
|
||||||
const deletedAccounts = this.accounts.filter(x => accounts.findIndex(y => y.id === x.info.id) === -1);
|
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.accounts = this.accounts.filter(x => x.info.id !== delAcc.info.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.hasAccounts = this.accounts.length > 0;
|
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 {
|
ngOnDestroy(): void {
|
||||||
this.sub.unsubscribe();
|
this.accountSub.unsubscribe();
|
||||||
|
this.notificationSub.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
onToogleAccountNotify(acc: AccountWrapper) {
|
onToogleAccountNotify(acc: AccountWrapper) {
|
||||||
|
@ -102,3 +119,14 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
|
||||||
return false;
|
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 { HttpHeaders, HttpClient, HttpResponse } from '@angular/common/http';
|
||||||
|
|
||||||
import { ApiRoutes } from './models/api.settings';
|
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 { AccountInfo } from '../states/accounts.state';
|
||||||
import { StreamTypeEnum } from '../states/streams.state';
|
import { StreamTypeEnum } from '../states/streams.state';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MastodonService {
|
export class MastodonService {
|
||||||
private apiRoutes = new ApiRoutes();
|
private apiRoutes = new ApiRoutes();
|
||||||
|
|
||||||
constructor(private readonly httpClient: HttpClient) { }
|
constructor(private readonly httpClient: HttpClient) { }
|
||||||
|
@ -229,6 +229,26 @@ export class MastodonService {
|
||||||
return this.httpClient.put<Attachment>(route, input, { headers: headers }).toPromise();
|
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 {
|
private formatArray(data: string[], paramName: string): string {
|
||||||
let result = '';
|
let result = '';
|
||||||
data.forEach(x => {
|
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