mirror of
https://github.com/NicolasConstant/sengi
synced 2025-02-05 21:03:54 +01:00
commit
e8a4184916
2
main.js
2
main.js
@ -10,7 +10,7 @@ let win
|
||||
|
||||
function createWindow() {
|
||||
// Create the browser window.
|
||||
win = new BrowserWindow({ width: 395, height: 800, title: "Sengi", backgroundColor: '#FFF' });
|
||||
win = new BrowserWindow({ width: 393, height: 800, title: "Sengi", backgroundColor: '#FFF' });
|
||||
|
||||
var server = http.createServer(requestHandler).listen(9527);
|
||||
win.loadURL('http://localhost:9527');
|
||||
|
@ -5,11 +5,10 @@
|
||||
</app-streams-main-display>-->
|
||||
|
||||
<div id="display-zone">
|
||||
<app-floating-column id="floating-column" *ngIf="floatingColumnActive">
|
||||
|
||||
</app-floating-column>
|
||||
<router-outlet>
|
||||
</router-outlet>
|
||||
<app-tutorial id="tutorial" *ngIf="tutorialActive"></app-tutorial>
|
||||
<app-floating-column id="floating-column" *ngIf="floatingColumnActive"></app-floating-column>
|
||||
<app-notification-hub></app-notification-hub>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
|
||||
<app-streams-selection-footer>
|
||||
@ -20,4 +19,4 @@
|
||||
Welcome to {{ title }}!
|
||||
</h1>
|
||||
<button (click)="launchWindow()">Launch Window</button>
|
||||
</div>-->
|
||||
</div>-->
|
@ -1,26 +1,33 @@
|
||||
#display-zone {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 30px;
|
||||
left: 50px;
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 30px;
|
||||
left: 50px;
|
||||
overflow-y: hidden;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#floating-column {
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
#tutorial {
|
||||
position: relative;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
app-streams-selection-footer {
|
||||
position: absolute;
|
||||
height: 30px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 50px;
|
||||
}
|
||||
position: absolute;
|
||||
height: 30px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 50px;
|
||||
}
|
@ -1,38 +1,48 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { ElectronService } from 'ngx-electron';
|
||||
import { NavigationService, LeftPanelType } from './services/navigation.service';
|
||||
import { Subscription } from 'rxjs';
|
||||
import { AccountWrapper } from './models/account.models';
|
||||
import { Subscription, Observable } from 'rxjs';
|
||||
import { Select } from '@ngxs/store';
|
||||
// import { ElectronService } from 'ngx-electron';
|
||||
|
||||
import { NavigationService, LeftPanelType } from './services/navigation.service';
|
||||
import { StreamElement } from './states/streams.state';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent implements OnInit, OnDestroy{
|
||||
|
||||
title = 'app';
|
||||
export class AppComponent implements OnInit, OnDestroy {
|
||||
title = 'Sengi';
|
||||
|
||||
floatingColumnActive: boolean;
|
||||
private columnEditorSub: Subscription;
|
||||
floatingColumnActive: boolean;
|
||||
tutorialActive: boolean;
|
||||
private columnEditorSub: Subscription;
|
||||
|
||||
constructor(private readonly navigationService: NavigationService) {
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.columnEditorSub = this.navigationService.activatedPanelSubject.subscribe((type: LeftPanelType) => {
|
||||
if(type === LeftPanelType.Closed) {
|
||||
this.floatingColumnActive = false;
|
||||
} else {
|
||||
this.floatingColumnActive = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
@Select(state => state.streamsstatemodel.streams) streamElements$: Observable<StreamElement[]>;
|
||||
|
||||
constructor(private readonly navigationService: NavigationService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.streamElements$.subscribe((streams: StreamElement[]) => {
|
||||
if(streams && streams.length === 0){
|
||||
this.tutorialActive = true;
|
||||
} else {
|
||||
this.tutorialActive = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.columnEditorSub = this.navigationService.activatedPanelSubject.subscribe((type: LeftPanelType) => {
|
||||
if (type === LeftPanelType.Closed) {
|
||||
this.floatingColumnActive = false;
|
||||
} else {
|
||||
this.floatingColumnActive = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.columnEditorSub.unsubscribe();
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.columnEditorSub.unsubscribe();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -45,6 +45,9 @@ import { DatabindedTextComponent } from './components/stream/status/databinded-t
|
||||
import { TimeAgoPipe } from './pipes/time-ago.pipe';
|
||||
import { StreamStatusesComponent } from './components/stream/stream-statuses/stream-statuses.component';
|
||||
import { StreamEditionComponent } from './components/stream/stream-edition/stream-edition.component';
|
||||
import { TutorialComponent } from './components/tutorial/tutorial.component';
|
||||
import { NotificationHubComponent } from './components/notification-hub/notification-hub.component';
|
||||
import { NotificationService } from "./services/notification.service";
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "", redirectTo: "home", pathMatch: "full" },
|
||||
@ -80,7 +83,9 @@ const routes: Routes = [
|
||||
DatabindedTextComponent,
|
||||
TimeAgoPipe,
|
||||
StreamStatusesComponent,
|
||||
StreamEditionComponent
|
||||
StreamEditionComponent,
|
||||
TutorialComponent,
|
||||
NotificationHubComponent
|
||||
],
|
||||
imports: [
|
||||
FontAwesomeModule,
|
||||
@ -98,7 +103,7 @@ const routes: Routes = [
|
||||
]),
|
||||
NgxsStoragePluginModule.forRoot()
|
||||
],
|
||||
providers: [AuthService, NavigationService, MastodonService, StreamingService],
|
||||
providers: [AuthService, NavigationService, NotificationService, MastodonService, StreamingService],
|
||||
bootstrap: [AppComponent],
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA]
|
||||
})
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Store } from '@ngxs/store';
|
||||
|
||||
import { RegisteredAppsStateModel, AppInfo, AddRegisteredApp } from '../../../states/registered-apps.state';
|
||||
import { AuthService, CurrentAuthProcess } from '../../../services/auth.service';
|
||||
import { Store } from '@ngxs/store';
|
||||
import { AppData } from '../../../services/models/mastodon.interfaces';
|
||||
import { NotificationService } from '../../../services/notification.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-new-account',
|
||||
@ -13,6 +16,7 @@ export class AddNewAccountComponent implements OnInit {
|
||||
@Input() mastodonFullHandle: string;
|
||||
|
||||
constructor(
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly store: Store) { }
|
||||
|
||||
@ -28,6 +32,9 @@ export class AddNewAccountComponent implements OnInit {
|
||||
this.checkAndCreateApplication(instance)
|
||||
.then((appData: AppData) => {
|
||||
this.redirectToInstanceAuthPage(username, instance, appData);
|
||||
})
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
|
||||
return false;
|
||||
@ -38,10 +45,8 @@ export class AddNewAccountComponent implements OnInit {
|
||||
const instanceApps = alreadyRegisteredApps.filter(x => x.instance === instance);
|
||||
|
||||
if (instanceApps.length !== 0) {
|
||||
console.log('instance already registered');
|
||||
return Promise.resolve(instanceApps[0].app);
|
||||
} else {
|
||||
console.log('instance not registered');
|
||||
const redirect_uri = this.getLocalHostname() + '/register';
|
||||
return this.authService.createNewApplication(instance, 'Sengi', redirect_uri, 'read write follow', 'https://github.com/NicolasConstant/sengi')
|
||||
.then((appData: AppData) => {
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { Component, OnInit, Input, ElementRef, ViewChild } from '@angular/core';
|
||||
import { Store } from '@ngxs/store';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
import { AccountInfo } from '../../../states/accounts.state';
|
||||
import { MastodonService, VisibilityEnum } from '../../../services/mastodon.service';
|
||||
import { Status } from '../../../services/models/mastodon.interfaces';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { NotificationService } from '../../../services/notification.service';
|
||||
import { NavigationService } from '../../../services/navigation.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-add-new-status',
|
||||
@ -20,6 +23,8 @@ export class AddNewStatusComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private readonly store: Store,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly navigationService: NavigationService,
|
||||
private readonly mastodonService: MastodonService) { }
|
||||
|
||||
ngOnInit() {
|
||||
@ -32,9 +37,6 @@ export class AddNewStatusComponent implements OnInit {
|
||||
const accounts = this.getRegisteredAccounts();
|
||||
const selectedAccounts = accounts.filter(x => x.isSelected);
|
||||
|
||||
console.warn(`selectedAccounts ${selectedAccounts.length}`);
|
||||
console.warn(`statusHandle ${this.status}`);
|
||||
|
||||
let visibility: VisibilityEnum = VisibilityEnum.Unknown;
|
||||
switch (this.selectedPrivacy) {
|
||||
case 'Public':
|
||||
@ -59,9 +61,12 @@ export class AddNewStatusComponent implements OnInit {
|
||||
for (const acc of selectedAccounts) {
|
||||
this.mastodonService.postNewStatus(acc, this.status, visibility, spoiler)
|
||||
.then((res: Status) => {
|
||||
console.log(res);
|
||||
this.title = '';
|
||||
this.status = '';
|
||||
this.navigationService.closePanel();
|
||||
})
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
<div class="floating-column">
|
||||
<app-stream-overlay class="stream-overlay" *ngIf="overlayActive" (closeOverlay)="closeOverlay()"
|
||||
[browseAccountData]="overlayAccountToBrowse"
|
||||
[browseHashtagData]="overlayHashtagToBrowse"></app-stream-overlay>
|
||||
[browseHashtagData]="overlayHashtagToBrowse"
|
||||
[browseThreadData]="overlayThreadToBrowse"></app-stream-overlay>
|
||||
|
||||
<div class="floating-column__header">
|
||||
<a class="close-button" href (click)="closePanel()" title="close">x</a>
|
||||
@ -12,6 +13,7 @@
|
||||
<app-add-new-account *ngIf="openPanel === 'addNewAccount'"></app-add-new-account>
|
||||
<app-search *ngIf="openPanel === 'search'"
|
||||
(browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)"></app-search>
|
||||
(browseHashtagEvent)="browseHashtag($event)"
|
||||
(browseThreadEvent)="browseThread($event)"></app-search>
|
||||
<app-settings *ngIf="openPanel === 'settings'"></app-settings>
|
||||
</div>
|
@ -1,7 +1,6 @@
|
||||
@import "variables";
|
||||
@import "mixins";
|
||||
|
||||
$floating-column-size: 330px;
|
||||
|
||||
.floating-column {
|
||||
width: calc(100%);
|
||||
@ -9,12 +8,14 @@ $floating-column-size: 330px;
|
||||
|
||||
background-color: $color-secondary;
|
||||
overflow: hidden;
|
||||
z-index: 99;
|
||||
z-index: 200;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: $stream-selector-height;
|
||||
padding: 0;
|
||||
|
||||
white-space: normal;
|
||||
|
||||
// &__header {
|
||||
|
||||
// }
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { NavigationService, LeftPanelType } from '../../services/navigation.service';
|
||||
import { AccountWrapper } from '../../models/account.models';
|
||||
import { OpenThreadEvent } from '../../services/tools.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-floating-column',
|
||||
@ -11,6 +12,7 @@ export class FloatingColumnComponent implements OnInit {
|
||||
overlayActive: boolean;
|
||||
overlayAccountToBrowse: string;
|
||||
overlayHashtagToBrowse: string;
|
||||
overlayThreadToBrowse: OpenThreadEvent;
|
||||
|
||||
userAccountUsed: AccountWrapper;
|
||||
|
||||
@ -54,18 +56,22 @@ export class FloatingColumnComponent implements OnInit {
|
||||
browseAccount(account: string): void {
|
||||
this.overlayAccountToBrowse = account;
|
||||
this.overlayHashtagToBrowse = null;
|
||||
this.overlayThreadToBrowse = null;
|
||||
this.overlayActive = true;
|
||||
}
|
||||
|
||||
browseHashtag(hashtag: string): void {
|
||||
this.overlayAccountToBrowse = null;
|
||||
this.overlayHashtagToBrowse = hashtag;
|
||||
this.overlayThreadToBrowse = null;
|
||||
this.overlayActive = true;
|
||||
}
|
||||
|
||||
browseThread(thread: string): void {
|
||||
console.warn('browseThread');
|
||||
console.warn(thread);
|
||||
browseThread(openThreadEvent: OpenThreadEvent): void {
|
||||
this.overlayAccountToBrowse = null;
|
||||
this.overlayHashtagToBrowse = null;
|
||||
this.overlayThreadToBrowse = openThreadEvent;
|
||||
this.overlayActive = true;
|
||||
}
|
||||
|
||||
closeOverlay(): boolean {
|
||||
|
@ -4,6 +4,7 @@ import { Store } from '@ngxs/store';
|
||||
import { AccountsStateModel, AccountInfo, RemoveAccount } from '../../../states/accounts.state';
|
||||
import { AccountWrapper } from '../../../models/account.models';
|
||||
import { NavigationService } from '../../../services/navigation.service';
|
||||
import { NotificationService } from '../../../services/notification.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-manage-account',
|
||||
@ -17,7 +18,8 @@ export class ManageAccountComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private readonly store: Store,
|
||||
private readonly navigationService: NavigationService) { }
|
||||
private readonly navigationService: NavigationService,
|
||||
private notificationService: NotificationService) { }
|
||||
|
||||
ngOnInit() {
|
||||
const instance = this.account.info.instance;
|
||||
@ -29,7 +31,10 @@ export class ManageAccountComponent implements OnInit {
|
||||
|
||||
addStream(stream: StreamElement): boolean {
|
||||
if (stream) {
|
||||
this.store.dispatch([new AddStream(stream)]);
|
||||
this.store.dispatch([new AddStream(stream)]).toPromise()
|
||||
.then(() => {
|
||||
this.notificationService.notify(`${stream.displayableFullName} added`, false);
|
||||
});
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -32,7 +32,8 @@
|
||||
<div class="search-results__status" *ngFor="let statusWrapper of statuses">
|
||||
<app-status [statusWrapper]="statusWrapper"
|
||||
(browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)"></app-status>
|
||||
(browseHashtagEvent)="browseHashtag($event)"
|
||||
(browseThreadEvent)="browseThread($event)"></app-status>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -56,7 +56,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
&__status {
|
||||
&__status {
|
||||
font-size: 15px;
|
||||
border-top: 1px solid $separator-color;
|
||||
&:last-of-type {
|
||||
border-bottom: 1px solid $separator-color;
|
||||
|
@ -1,13 +1,12 @@
|
||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { Store } from '@ngxs/store';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
import { MastodonService } from '../../../services/mastodon.service';
|
||||
import { AccountInfo } from '../../../states/accounts.state';
|
||||
import { Results, Account, Status } from '../../../services/models/mastodon.interfaces';
|
||||
import { ToolsService } from '../../../services/tools.service';
|
||||
import { Results, Account } from '../../../services/models/mastodon.interfaces';
|
||||
import { ToolsService, OpenThreadEvent } from '../../../services/tools.service';
|
||||
import { StatusWrapper } from '../../stream/stream.component';
|
||||
import { StreamElement, StreamTypeEnum, AddStream } from './../../../states/streams.state';
|
||||
|
||||
import { NotificationService } from '../../../services/notification.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-search',
|
||||
@ -25,9 +24,10 @@ export class SearchComponent implements OnInit {
|
||||
|
||||
@Output() browseAccountEvent = new EventEmitter<string>();
|
||||
@Output() browseHashtagEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
|
||||
|
||||
constructor(
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly mastodonService: MastodonService) { }
|
||||
|
||||
@ -47,17 +47,14 @@ export class SearchComponent implements OnInit {
|
||||
return false;
|
||||
}
|
||||
|
||||
// addHashtag(hashtag: string): boolean {
|
||||
// if (hashtag) {
|
||||
// const newStream = new StreamElement(StreamTypeEnum.tag, `#${hashtag}`, this.lastAccountUsed.id, hashtag, null);
|
||||
// this.store.dispatch([new AddStream(newStream)]);
|
||||
// }
|
||||
|
||||
// return false;
|
||||
// }
|
||||
browseThread(openThreadEvent: OpenThreadEvent): boolean{
|
||||
if(openThreadEvent){
|
||||
this.browseThreadEvent.next(openThreadEvent);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
browseAccount(accountName: string): boolean {
|
||||
console.warn(accountName);
|
||||
if (accountName) {
|
||||
this.browseAccountEvent.next(accountName);
|
||||
}
|
||||
@ -71,8 +68,6 @@ export class SearchComponent implements OnInit {
|
||||
this.hashtags.length = 0;
|
||||
this.isLoading = true;
|
||||
|
||||
console.warn(`search: ${data}`);
|
||||
|
||||
const enabledAccounts = this.toolsService.getSelectedAccounts();
|
||||
//First candid implementation
|
||||
if (enabledAccounts.length > 0) {
|
||||
@ -80,7 +75,6 @@ export class SearchComponent implements OnInit {
|
||||
this.mastodonService.search(this.lastAccountUsed, data, true)
|
||||
.then((results: Results) => {
|
||||
if (results) {
|
||||
console.warn(results);
|
||||
this.accounts = results.accounts.slice(0, 5);
|
||||
this.hashtags = results.hashtags;
|
||||
|
||||
@ -88,11 +82,11 @@ export class SearchComponent implements OnInit {
|
||||
const statusWrapper = new StatusWrapper(status, this.lastAccountUsed);
|
||||
this.statuses.push(statusWrapper);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
.catch((err) => console.error(err))
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
})
|
||||
.then(() => { this.isLoading = false; });
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
<div class="left-bar">
|
||||
<a class="left-bar-button left-bar-button--status left-bar-link" href title="write new message" (click)="createNewStatus()">
|
||||
<ion-icon name="md-send"></ion-icon>
|
||||
<a class="left-bar-button left-bar-button--status left-bar-link" href title="write new message" (click)="createNewStatus()" *ngIf="hasAccounts">
|
||||
<fa-icon [icon]="faCommentAlt"></fa-icon>
|
||||
<!-- <ion-icon name="md-send"></ion-icon> -->
|
||||
</a>
|
||||
<a class="left-bar-button left-bar-button--search left-bar-link" href title="search" (click)="openSearch()">
|
||||
<a class="left-bar-button left-bar-button--search left-bar-link" href title="search" (click)="openSearch()" *ngIf="hasAccounts">
|
||||
<ion-icon name="md-search"></ion-icon>
|
||||
</a>
|
||||
|
||||
@ -11,11 +12,11 @@
|
||||
</app-account-icon>
|
||||
</div>
|
||||
|
||||
<a class="left-bar-button left-bar-button--add left-bar-link" href title="add new account" (click)="addNewAccount()">
|
||||
<a class="left-bar-button left-bar-button--add left-bar-link" [ngClass]="{'no-accounts': hasAccounts === false }" href title="add new account" (click)="addNewAccount()">
|
||||
<ion-icon name="md-add"></ion-icon>
|
||||
</a>
|
||||
|
||||
<a class="left-bar-button left-bar-button--cog left-bar-link" href title="settings" (click)="openSettings()">
|
||||
<a class="left-bar-button left-bar-button--cog left-bar-link" href title="settings" (click)="openSettings()" *ngIf="hasAccounts">
|
||||
<ion-icon name="md-cog"></ion-icon>
|
||||
</a>
|
||||
</div>
|
@ -1,6 +1,7 @@
|
||||
@import "variables";
|
||||
$width-button: 50px;
|
||||
$height-button: 40px;
|
||||
|
||||
.left-bar {
|
||||
width: $width-button;
|
||||
height: calc(100%);
|
||||
@ -22,12 +23,16 @@ $height-button: 40px;
|
||||
width: $width-button;
|
||||
height: $height-button;
|
||||
transition: all .2s;
|
||||
|
||||
// outline: 1px dotted greenyellow;
|
||||
&--status {
|
||||
padding: 5px 0 0 10px;
|
||||
//margin-top: 3px;
|
||||
font-size: 26px;
|
||||
padding: 8px 0 2px 12px;
|
||||
}
|
||||
&--search {
|
||||
padding: 0 0 0 9px;
|
||||
font-size: 28px;
|
||||
padding: 0 0 0 11px;
|
||||
}
|
||||
&--add {
|
||||
padding: 0 0 0 12px;
|
||||
@ -62,3 +67,7 @@ $height-button: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.no-accounts {
|
||||
padding-top: 10px;
|
||||
// color: cornflowerblue;
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
import { Component, OnInit, OnDestroy } from "@angular/core";
|
||||
import { Subscription, BehaviorSubject, Observable } from "rxjs";
|
||||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
import { Subscription, Observable } from "rxjs";
|
||||
import { Store } from "@ngxs/store";
|
||||
import { faCommentAlt } from "@fortawesome/free-regular-svg-icons";
|
||||
|
||||
import { Account } from "../../services/models/mastodon.interfaces";
|
||||
import { AccountWrapper } from "../../models/account.models";
|
||||
import { AccountsStateModel, AccountInfo, SelectAccount } from "../../states/accounts.state";
|
||||
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";
|
||||
|
||||
@Component({
|
||||
selector: "app-left-side-bar",
|
||||
@ -15,13 +17,17 @@ import { MastodonService } from "../../services/mastodon.service";
|
||||
styleUrls: ["./left-side-bar.component.scss"]
|
||||
})
|
||||
export class LeftSideBarComponent implements OnInit, OnDestroy {
|
||||
faCommentAlt = faCommentAlt;
|
||||
|
||||
accounts: AccountWrapper[] = [];
|
||||
hasAccounts: boolean;
|
||||
private accounts$: Observable<AccountInfo[]>;
|
||||
|
||||
// private loadedAccounts: { [index: string]: AccountInfo } = {};
|
||||
private sub: Subscription;
|
||||
|
||||
constructor(
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly navigationService: NavigationService,
|
||||
private readonly mastodonService: MastodonService,
|
||||
private readonly store: Store) {
|
||||
@ -46,6 +52,9 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
|
||||
this.mastodonService.retrieveAccountDetails(acc)
|
||||
.then((result: Account) => {
|
||||
accWrapper.avatar = result.avatar;
|
||||
})
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -55,6 +64,8 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
|
||||
for(let delAcc of deletedAccounts){
|
||||
this.accounts = this.accounts.filter(x => x.info.id !== delAcc.info.id);
|
||||
}
|
||||
|
||||
this.hasAccounts = this.accounts.length > 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -64,12 +75,10 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
onToogleAccountNotify(acc: AccountWrapper) {
|
||||
console.warn(`onToogleAccountNotify username ${acc.info.username}`);
|
||||
this.store.dispatch([new SelectAccount(acc.info)]);
|
||||
}
|
||||
|
||||
onOpenMenuNotify(acc: AccountWrapper) {
|
||||
console.warn(`onOpenMenuNotify username ${acc.info.username}`);
|
||||
this.navigationService.openColumnEditor(acc);
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
<div class="notification-hub">
|
||||
<div class="notification-hub__notification" [ngClass]="{'notification-hub__notification--error':notification.isError}" *ngFor="let notification of notifications" (click)="onClick(notification)" title="close">
|
||||
{{ notification.message }}
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,20 @@
|
||||
.notification-hub {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
z-index: 9999999;
|
||||
margin: 0 0 10px 0;
|
||||
|
||||
&__notification{
|
||||
background-color: #22b90e;
|
||||
color: black;
|
||||
padding: 5px 10px;
|
||||
border-radius: 2px;
|
||||
margin: 0 0 5px 15px;
|
||||
cursor: pointer;
|
||||
|
||||
&--error{
|
||||
background-color: #be0a0a;
|
||||
color: whitesmoke;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NotificationHubComponent } from './notification-hub.component';
|
||||
|
||||
xdescribe('NotificationHubComponent', () => {
|
||||
let component: NotificationHubComponent;
|
||||
let fixture: ComponentFixture<NotificationHubComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ NotificationHubComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NotificationHubComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,37 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { NotificationService, NotificatioData } from '../../services/notification.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-notification-hub',
|
||||
templateUrl: './notification-hub.component.html',
|
||||
styleUrls: ['./notification-hub.component.scss']
|
||||
})
|
||||
export class NotificationHubComponent implements OnInit {
|
||||
notifications: NotificatioData[] = [];
|
||||
|
||||
constructor(private notificationService: NotificationService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.notificationService.notifactionStream.subscribe((notification: NotificatioData) => {
|
||||
this.notifications.push(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
this.notifications = this.notifications.filter(x => x.id !== notification.id);
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
//this.autoSubmit();
|
||||
}
|
||||
|
||||
autoSubmit(): any {
|
||||
this.notificationService.notify("test message", true);
|
||||
|
||||
setTimeout(() => {
|
||||
this.autoSubmit();
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
onClick(notification: NotificatioData): void{
|
||||
this.notifications = this.notifications.filter(x => x.id !== notification.id);
|
||||
}
|
||||
}
|
@ -6,9 +6,10 @@
|
||||
<button class="btn-custom-secondary hashtag-header__add-column" (click)="addColumn($event)" title="add column to board">add column</button>
|
||||
</a>
|
||||
</div>
|
||||
<app-stream-statuses class="hashtag-stream" *ngIf="hashtagElement"
|
||||
<app-stream-statuses #appStreamStatuses class="hashtag-stream" *ngIf="hashtagElement"
|
||||
[streamElement]="hashtagElement"
|
||||
[goToTop]="goToTopSubject.asObservable()"
|
||||
[userLocked]="false"
|
||||
(browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)"
|
||||
(browseThreadEvent)="browseThread($event)"></app-stream-statuses>
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core';
|
||||
import { Component, OnInit, Output, EventEmitter, Input, ViewChild } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { Store } from '@ngxs/store';
|
||||
|
||||
import { StreamElement, StreamTypeEnum, AddStream } from '../../../states/streams.state';
|
||||
import { OpenThreadEvent, ToolsService } from '../../../services/tools.service';
|
||||
import { StreamStatusesComponent } from '../stream-statuses/stream-statuses.component';
|
||||
import { AccountInfo } from '../../../states/accounts.state';
|
||||
|
||||
@Component({
|
||||
selector: 'app-hashtag',
|
||||
@ -10,16 +13,30 @@ import { StreamElement, StreamTypeEnum, AddStream } from '../../../states/stream
|
||||
styleUrls: ['./hashtag.component.scss']
|
||||
})
|
||||
export class HashtagComponent implements OnInit {
|
||||
|
||||
@Output() browseAccountEvent = new EventEmitter<string>();
|
||||
@Output() browseHashtagEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
|
||||
|
||||
@Input() hashtagElement: StreamElement;
|
||||
private _hashtagElement: StreamElement;
|
||||
@Input()
|
||||
set hashtagElement(hashtagElement: StreamElement){
|
||||
this._hashtagElement = hashtagElement;
|
||||
this.lastUsedAccount = this.toolsService.getSelectedAccounts()[0];
|
||||
}
|
||||
get hashtagElement(): StreamElement{
|
||||
return this._hashtagElement;
|
||||
}
|
||||
|
||||
@ViewChild('appStreamStatuses') appStreamStatuses: StreamStatusesComponent;
|
||||
|
||||
goToTopSubject: Subject<void> = new Subject<void>();
|
||||
|
||||
private lastUsedAccount: AccountInfo;
|
||||
|
||||
constructor(
|
||||
private readonly store: Store) { }
|
||||
private readonly store: Store,
|
||||
private readonly toolsService: ToolsService) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
@ -33,12 +50,17 @@ export class HashtagComponent implements OnInit {
|
||||
event.stopPropagation();
|
||||
|
||||
const hashtag = this.hashtagElement.tag;
|
||||
const newStream = new StreamElement(StreamTypeEnum.tag, `${hashtag}`, this.hashtagElement.accountId, hashtag, null, this.hashtagElement.displayableFullName);
|
||||
const newStream = new StreamElement(StreamTypeEnum.tag, `${hashtag}`, this.lastUsedAccount.id, hashtag, null, this.hashtagElement.displayableFullName);
|
||||
this.store.dispatch([new AddStream(newStream)]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
refresh(): any {
|
||||
this.lastUsedAccount = this.toolsService.getSelectedAccounts()[0];
|
||||
this.appStreamStatuses.refresh();
|
||||
}
|
||||
|
||||
browseAccount(account: string) {
|
||||
this.browseAccountEvent.next(account);
|
||||
}
|
||||
@ -47,7 +69,7 @@ export class HashtagComponent implements OnInit {
|
||||
this.browseHashtagEvent.next(hashtag);
|
||||
}
|
||||
|
||||
browseThread(statusUri: string): void {
|
||||
this.browseThreadEvent.next(statusUri);
|
||||
browseThread(openThreadEvent: OpenThreadEvent): void {
|
||||
this.browseThreadEvent.next(openThreadEvent);
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { Component, OnInit, OnDestroy, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Store } from '@ngxs/store';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
@ -6,6 +7,8 @@ import { StatusWrapper } from '../../stream.component';
|
||||
import { MastodonService } from '../../../../services/mastodon.service';
|
||||
import { AccountInfo } from '../../../../states/accounts.state';
|
||||
import { Status, Results } from '../../../../services/models/mastodon.interfaces';
|
||||
import { ToolsService } from '../../../../services/tools.service';
|
||||
import { NotificationService } from '../../../../services/notification.service';
|
||||
// import { map } from "rxjs/operators";
|
||||
|
||||
@Component({
|
||||
@ -35,15 +38,14 @@ export class ActionBarComponent implements OnInit, OnDestroy {
|
||||
|
||||
constructor(
|
||||
private readonly store: Store,
|
||||
private readonly mastodonService: MastodonService) {
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly mastodonService: MastodonService,
|
||||
private readonly notificationService: NotificationService) {
|
||||
|
||||
this.accounts$ = this.store.select(state => state.registeredaccounts.accounts);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
// const selectedAccounts = this.getSelectedAccounts();
|
||||
// this.checkStatus(selectedAccounts);
|
||||
|
||||
const status = this.statusWrapper.status;
|
||||
const account = this.statusWrapper.provider;
|
||||
this.favoriteStatePerAccountId[account.id] = status.favourited;
|
||||
@ -86,23 +88,11 @@ export class ActionBarComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
boost(): boolean {
|
||||
//TODO get rid of that
|
||||
this.selectedAccounts.forEach((account: AccountInfo) => {
|
||||
const isProvider = this.statusWrapper.provider.id === account.id;
|
||||
|
||||
let pipeline: Promise<Status> = Promise.resolve(this.statusWrapper.status);
|
||||
|
||||
if (!isProvider) {
|
||||
pipeline = pipeline.then((foreignStatus: Status) => {
|
||||
const statusUrl = foreignStatus.url;
|
||||
return this.mastodonService.search(account, statusUrl)
|
||||
.then((results: Results) => {
|
||||
//TODO check and type errors
|
||||
return results.statuses[0];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pipeline
|
||||
const usableStatus = this.toolsService.getStatusUsableByAccount(account, this.statusWrapper);
|
||||
usableStatus
|
||||
.then((status: Status) => {
|
||||
if (this.isBoosted) {
|
||||
return this.mastodonService.unreblog(account, status);
|
||||
@ -115,8 +105,8 @@ export class ActionBarComponent implements OnInit, OnDestroy {
|
||||
this.checkIfBoosted();
|
||||
// this.isBoosted = !this.isBoosted;
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
});
|
||||
|
||||
@ -125,22 +115,9 @@ export class ActionBarComponent implements OnInit, OnDestroy {
|
||||
|
||||
favorite(): boolean {
|
||||
this.selectedAccounts.forEach((account: AccountInfo) => {
|
||||
const isProvider = this.statusWrapper.provider.id === account.id;
|
||||
|
||||
let pipeline: Promise<Status> = Promise.resolve(this.statusWrapper.status);
|
||||
|
||||
if (!isProvider) {
|
||||
pipeline = pipeline.then((foreignStatus: Status) => {
|
||||
const statusUrl = foreignStatus.url;
|
||||
return this.mastodonService.search(account, statusUrl)
|
||||
.then((results: Results) => {
|
||||
//TODO check and type errors
|
||||
return results.statuses[0];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
pipeline
|
||||
|
||||
const usableStatus = this.toolsService.getStatusUsableByAccount(account, this.statusWrapper);
|
||||
usableStatus
|
||||
.then((status: Status) => {
|
||||
if (this.isFavorited) {
|
||||
return this.mastodonService.unfavorite(account, status);
|
||||
@ -153,8 +130,8 @@ export class ActionBarComponent implements OnInit, OnDestroy {
|
||||
this.checkIfFavorited();
|
||||
// this.isFavorited = !this.isFavorited;
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
});
|
||||
return false;
|
||||
@ -180,7 +157,7 @@ export class ActionBarComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
more(): boolean {
|
||||
console.warn('more');
|
||||
console.warn('more'); //TODO
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,8 @@ import { MastodonService, VisibilityEnum } from '../../../../services/mastodon.s
|
||||
import { StatusWrapper } from '../../stream.component';
|
||||
import { Status } from '../../../../services/models/mastodon.interfaces';
|
||||
import { ToolsService } from '../../../../services/tools.service';
|
||||
import { NotificationService } from '../../../../services/notification.service';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
@Component({
|
||||
selector: 'app-reply-to-status',
|
||||
@ -24,25 +26,28 @@ export class ReplyToStatusComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
// private readonly store: Store,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly mastodonService: MastodonService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.statusReplyingTo = this.statusReplyingToWrapper.status;
|
||||
if (this.statusReplyingToWrapper.status.reblog) {
|
||||
this.statusReplyingTo = this.statusReplyingToWrapper.status.reblog;
|
||||
} else {
|
||||
this.statusReplyingTo = this.statusReplyingToWrapper.status;
|
||||
}
|
||||
|
||||
this.status += `@${this.statusReplyingTo.account.acct} `;
|
||||
for (const mention of this.statusReplyingTo.mentions) {
|
||||
this.status += `@${mention.acct} `;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
setTimeout(() => {
|
||||
this.replyElement.nativeElement.focus();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
onSubmit(): boolean {
|
||||
const selectedAccounts = this.toolsService.getSelectedAccounts();
|
||||
|
||||
let visibility: VisibilityEnum = VisibilityEnum.Unknown;
|
||||
switch (this.selectedPrivacy) {
|
||||
case 'Public':
|
||||
@ -61,12 +66,20 @@ export class ReplyToStatusComponent implements OnInit {
|
||||
|
||||
let spoiler = this.statusReplyingTo.spoiler_text;
|
||||
|
||||
const selectedAccounts = this.toolsService.getSelectedAccounts();
|
||||
for (const acc of selectedAccounts) {
|
||||
this.mastodonService.postNewStatus(acc, this.status, visibility, spoiler, this.statusReplyingTo.id)
|
||||
|
||||
const usableStatus = this.toolsService.getStatusUsableByAccount(acc, this.statusReplyingToWrapper);
|
||||
usableStatus
|
||||
.then((status: Status) => {
|
||||
return this.mastodonService.postNewStatus(acc, this.status, visibility, spoiler, status.id);
|
||||
})
|
||||
.then((res: Status) => {
|
||||
console.log(res);
|
||||
this.status = '';
|
||||
this.onClose.emit();
|
||||
})
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Component, OnInit, Input, Output, EventEmitter } from "@angular/core";
|
||||
import { Status, Account } from "../../../services/models/mastodon.interfaces";
|
||||
import { StatusWrapper } from "../stream.component";
|
||||
import { OpenThreadEvent } from "../../../services/tools.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-status",
|
||||
@ -15,7 +16,7 @@ export class StatusComponent implements OnInit {
|
||||
|
||||
@Output() browseAccountEvent = new EventEmitter<string>();
|
||||
@Output() browseHashtagEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
|
||||
|
||||
private _statusWrapper: StatusWrapper;
|
||||
status: Status;
|
||||
@ -78,11 +79,15 @@ export class StatusComponent implements OnInit {
|
||||
|
||||
textSelected(): void {
|
||||
const status = this._statusWrapper.status;
|
||||
const accountInfo = this._statusWrapper.provider;
|
||||
|
||||
if (status.reblog) {
|
||||
this.browseThreadEvent.next(status.reblog.uri);
|
||||
let openThread: OpenThreadEvent;
|
||||
if (status.reblog) {
|
||||
openThread = new OpenThreadEvent(status.reblog, accountInfo);
|
||||
} else {
|
||||
this.browseThreadEvent.next(this._statusWrapper.status.uri);
|
||||
openThread = new OpenThreadEvent(status, accountInfo);
|
||||
}
|
||||
|
||||
this.browseThreadEvent.next(openThread);
|
||||
}
|
||||
}
|
||||
|
@ -6,15 +6,15 @@
|
||||
<a href class="overlay-next" *ngIf="canGoForward" (click)="next()">NEXT</a>
|
||||
</div>
|
||||
|
||||
<app-user-profile *ngIf="accountName" [currentAccount]="accountName"
|
||||
<app-user-profile #appUserProfile *ngIf="accountName" [currentAccount]="accountName"
|
||||
(browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)"
|
||||
(browseThreadEvent)="browseThread($event)"></app-user-profile>
|
||||
<app-hashtag *ngIf="hashtagElement" [hashtagElement]="hashtagElement"
|
||||
<app-hashtag #appHashtag *ngIf="hashtagElement" [hashtagElement]="hashtagElement"
|
||||
(browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)"
|
||||
(browseThreadEvent)="browseThread($event)"></app-hashtag>
|
||||
<app-thread *ngIf="browseThread" [currentThread]="thread"
|
||||
<app-thread #appThread *ngIf="browseThread" [currentThread]="thread"
|
||||
(browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)"
|
||||
(browseThreadEvent)="browseThread($event)"></app-thread>
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { Component, OnInit, Output, EventEmitter, Input } from '@angular/core';
|
||||
import { Account, Results } from "../../../services/models/mastodon.interfaces";
|
||||
import { MastodonService } from '../../../services/mastodon.service';
|
||||
import { ToolsService } from '../../../services/tools.service';
|
||||
import { Component, OnInit, Output, EventEmitter, Input, ViewChild } from '@angular/core';
|
||||
|
||||
import { ToolsService, OpenThreadEvent } from '../../../services/tools.service';
|
||||
import { StreamElement, StreamTypeEnum } from '../../../states/streams.state';
|
||||
import { ThreadComponent } from '../thread/thread.component';
|
||||
import { UserProfileComponent } from '../user-profile/user-profile.component';
|
||||
import { HashtagComponent } from '../hashtag/hashtag.component';
|
||||
|
||||
@Component({
|
||||
selector: 'app-stream-overlay',
|
||||
@ -15,11 +17,11 @@ export class StreamOverlayComponent implements OnInit {
|
||||
private nextElements: OverlayBrowsing[] = [];
|
||||
private currentElement: OverlayBrowsing;
|
||||
|
||||
canRefresh: boolean;
|
||||
canRefresh: boolean = true;
|
||||
canGoForward: boolean;
|
||||
|
||||
accountName: string;
|
||||
thread: string;
|
||||
thread: OpenThreadEvent;
|
||||
// hashtag: string;
|
||||
hashtagElement: StreamElement;
|
||||
|
||||
@ -32,8 +34,8 @@ export class StreamOverlayComponent implements OnInit {
|
||||
}
|
||||
|
||||
@Input('browseThreadData')
|
||||
set browseThreadData(statusUri: string) {
|
||||
this.browseThread(statusUri);
|
||||
set browseThreadData(openThread: OpenThreadEvent) {
|
||||
this.browseThread(openThread);
|
||||
}
|
||||
|
||||
@Input('browseHashtagData')
|
||||
@ -41,7 +43,11 @@ export class StreamOverlayComponent implements OnInit {
|
||||
this.browseHashtag(hashtag);
|
||||
}
|
||||
|
||||
constructor(private toolsService: ToolsService) { }
|
||||
@ViewChild('appUserProfile') appUserProfile: UserProfileComponent;
|
||||
@ViewChild('appHashtag') appHashtag: HashtagComponent;
|
||||
@ViewChild('appThread') appThread: ThreadComponent;
|
||||
|
||||
constructor(private readonly toolsService: ToolsService) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
@ -52,8 +58,6 @@ export class StreamOverlayComponent implements OnInit {
|
||||
}
|
||||
|
||||
next(): boolean {
|
||||
console.log('next');
|
||||
|
||||
if (this.nextElements.length === 0) {
|
||||
return false;
|
||||
}
|
||||
@ -70,8 +74,6 @@ export class StreamOverlayComponent implements OnInit {
|
||||
}
|
||||
|
||||
previous(): boolean {
|
||||
console.log('previous');
|
||||
|
||||
if (this.previousElements.length === 0) {
|
||||
this.closeOverlay.next();
|
||||
return false;
|
||||
@ -89,14 +91,20 @@ export class StreamOverlayComponent implements OnInit {
|
||||
}
|
||||
|
||||
refresh(): boolean {
|
||||
console.log('refresh');
|
||||
if(this.thread){
|
||||
this.appThread.refresh();
|
||||
} else if(this.hashtagElement){
|
||||
this.appHashtag.refresh();
|
||||
} else if(this.accountName){
|
||||
this.appUserProfile.refresh();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
browseAccount(accountName: string): void {
|
||||
if(!accountName) return;
|
||||
|
||||
console.log('accountSelected');
|
||||
this.nextElements.length = 0;
|
||||
if (this.currentElement) {
|
||||
this.previousElements.push(this.currentElement);
|
||||
@ -109,7 +117,6 @@ export class StreamOverlayComponent implements OnInit {
|
||||
browseHashtag(hashtag: string): void {
|
||||
if(!hashtag) return;
|
||||
|
||||
console.log('hashtagSelected');
|
||||
this.nextElements.length = 0;
|
||||
if (this.currentElement) {
|
||||
this.previousElements.push(this.currentElement);
|
||||
@ -122,16 +129,15 @@ export class StreamOverlayComponent implements OnInit {
|
||||
this.canGoForward = false;
|
||||
}
|
||||
|
||||
browseThread(statusUri: string): any {
|
||||
if(!statusUri) return;
|
||||
browseThread(openThread: OpenThreadEvent): any {
|
||||
if(!openThread) return;
|
||||
|
||||
console.log('thread selected')
|
||||
this.nextElements.length = 0;
|
||||
if (this.currentElement) {
|
||||
this.previousElements.push(this.currentElement);
|
||||
}
|
||||
|
||||
const newElement = new OverlayBrowsing(null, null, statusUri);
|
||||
const newElement = new OverlayBrowsing(null, null, openThread);
|
||||
this.loadElement(newElement);
|
||||
this.canGoForward = false;
|
||||
}
|
||||
@ -149,9 +155,7 @@ class OverlayBrowsing {
|
||||
constructor(
|
||||
public readonly hashtag: StreamElement,
|
||||
public readonly account: string,
|
||||
public readonly thread: string) {
|
||||
|
||||
console.warn(`OverlayBrowsing: ${hashtag} ${account} ${thread}`);
|
||||
public readonly thread: OpenThreadEvent) {
|
||||
|
||||
if (hashtag) {
|
||||
this.type = OverlayEnum.hashtag;
|
||||
|
@ -1,9 +1,13 @@
|
||||
<div class="stream-toots flexcroll" #statusstream (scroll)="onScroll()">
|
||||
<app-waiting-animation *ngIf="isLoading" class="waiting-icon"></app-waiting-animation>
|
||||
|
||||
<div *ngIf="displayError" class="stream-toots__error">{{displayError}}</div>
|
||||
|
||||
<!-- data-simplebar -->
|
||||
<div class="stream-toots__status" *ngFor="let statusWrapper of statuses">
|
||||
<app-status [statusWrapper]="statusWrapper" (browseAccountEvent)="browseAccount($event)" (browseHashtagEvent)="browseHashtag($event)"
|
||||
<app-status [statusWrapper]="statusWrapper"
|
||||
(browseAccountEvent)="browseAccount($event)"
|
||||
(browseHashtagEvent)="browseHashtag($event)"
|
||||
(browseThreadEvent)="browseThread($event)"></app-status>
|
||||
</div>
|
||||
</div>
|
@ -6,6 +6,12 @@
|
||||
width: calc(100%);
|
||||
|
||||
overflow: auto;
|
||||
|
||||
&__error {
|
||||
padding: 20px 20px 0 20px;
|
||||
color: rgb(255, 113, 113);
|
||||
}
|
||||
|
||||
&__status:not(:last-child) {
|
||||
border: solid #06070b;
|
||||
border-width: 0 0 1px 0;
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { Component, OnInit, Input, ViewChild, ElementRef, OnDestroy, EventEmitter, Output } from '@angular/core';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { Store } from '@ngxs/store';
|
||||
|
||||
import { StreamElement } from '../../../states/streams.state';
|
||||
@ -6,9 +8,9 @@ import { AccountInfo } from '../../../states/accounts.state';
|
||||
import { StreamingService, EventEnum, StreamingWrapper, StatusUpdate } from '../../../services/streaming.service';
|
||||
import { Status } from '../../../services/models/mastodon.interfaces';
|
||||
import { MastodonService } from '../../../services/mastodon.service';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { StatusWrapper } from '../stream.component';
|
||||
|
||||
import { NotificationService } from '../../../services/notification.service';
|
||||
import { OpenThreadEvent, ToolsService } from '../../../services/tools.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-stream-statuses',
|
||||
@ -16,7 +18,9 @@ import { StatusWrapper } from '../stream.component';
|
||||
styleUrls: ['./stream-statuses.component.scss']
|
||||
})
|
||||
export class StreamStatusesComponent implements OnInit, OnDestroy {
|
||||
|
||||
isLoading = false; //TODO
|
||||
displayError: string;
|
||||
|
||||
private _streamElement: StreamElement;
|
||||
private account: AccountInfo;
|
||||
@ -28,22 +32,12 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
||||
|
||||
@Output() browseAccountEvent = new EventEmitter<string>();
|
||||
@Output() browseHashtagEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
|
||||
|
||||
@Input()
|
||||
set streamElement(streamElement: StreamElement) {
|
||||
console.warn('new stream');
|
||||
this.resetStream();
|
||||
|
||||
this._streamElement = streamElement;
|
||||
|
||||
const splitedUserName = streamElement.accountId.split('@');
|
||||
const user = splitedUserName[0];
|
||||
const instance = splitedUserName[1];
|
||||
this.account = this.getRegisteredAccounts().find(x => x.username == user && x.instance == instance);
|
||||
|
||||
this.retrieveToots();
|
||||
this.launchWebsocket();
|
||||
this.load(this._streamElement);
|
||||
}
|
||||
get streamElement(): StreamElement {
|
||||
return this._streamElement;
|
||||
@ -51,10 +45,14 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
||||
|
||||
@Input() goToTop: Observable<void>;
|
||||
|
||||
@Input() userLocked = true;
|
||||
|
||||
private goToTopSubscription: Subscription;
|
||||
|
||||
constructor(
|
||||
private readonly store: Store,
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly streamingService: StreamingService,
|
||||
private readonly mastodonService: MastodonService) {
|
||||
}
|
||||
@ -65,14 +63,34 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(){
|
||||
if( this.goToTopSubscription) this.goToTopSubscription.unsubscribe();
|
||||
ngOnDestroy() {
|
||||
if (this.goToTopSubscription) this.goToTopSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
refresh(): any {
|
||||
this.load(this._streamElement);
|
||||
}
|
||||
|
||||
private load(streamElement: StreamElement) {
|
||||
this.resetStream();
|
||||
|
||||
if (this.userLocked) {
|
||||
const splitedUserName = streamElement.accountId.split('@');
|
||||
const user = splitedUserName[0];
|
||||
const instance = splitedUserName[1];
|
||||
this.account = this.getRegisteredAccounts().find(x => x.username == user && x.instance == instance);
|
||||
} else {
|
||||
this.account = this.toolsService.getSelectedAccounts()[0];
|
||||
}
|
||||
|
||||
this.retrieveToots();
|
||||
this.launchWebsocket();
|
||||
}
|
||||
|
||||
private resetStream() {
|
||||
this.statuses.length = 0;
|
||||
this.bufferStream.length = 0;
|
||||
if(this.websocketStreaming) this.websocketStreaming.dispose();
|
||||
if (this.websocketStreaming) this.websocketStreaming.dispose();
|
||||
}
|
||||
|
||||
private launchWebsocket(): void {
|
||||
@ -95,7 +113,7 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ViewChild('statusstream') public statustream: ElementRef;
|
||||
private applyGoToTop(): boolean {
|
||||
this.loadBuffer();
|
||||
@ -115,7 +133,7 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
||||
|
||||
onScroll() {
|
||||
var element = this.statustream.nativeElement as HTMLElement;
|
||||
const atBottom = element.scrollHeight <= element.clientHeight + element.scrollTop + 1000;
|
||||
const atBottom = element.scrollHeight <= element.clientHeight + element.scrollTop + 1000;
|
||||
const atTop = element.scrollTop === 0;
|
||||
|
||||
this.streamPositionnedAtTop = false;
|
||||
@ -134,12 +152,12 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
||||
this.browseHashtagEvent.next(hashtag);
|
||||
}
|
||||
|
||||
browseThread(statusUri: string): void {
|
||||
this.browseThreadEvent.next(statusUri);
|
||||
browseThread(openThreadEvent: OpenThreadEvent): void {
|
||||
this.browseThreadEvent.next(openThreadEvent);
|
||||
}
|
||||
|
||||
textSelected(): void {
|
||||
console.warn(`status comp: textSelected`);
|
||||
console.warn(`status comp: textSelected`); //TODO
|
||||
}
|
||||
|
||||
private scrolledToTop() {
|
||||
@ -148,15 +166,15 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
||||
this.loadBuffer();
|
||||
}
|
||||
|
||||
private loadBuffer(){
|
||||
if(this.bufferWasCleared) {
|
||||
private loadBuffer() {
|
||||
if (this.bufferWasCleared) {
|
||||
this.statuses.length = 0;
|
||||
this.bufferWasCleared = false;
|
||||
}
|
||||
|
||||
for (const status of this.bufferStream) {
|
||||
const wrapper = new StatusWrapper(status, this.account);
|
||||
this.statuses.unshift(wrapper);
|
||||
this.statuses.unshift(wrapper);
|
||||
}
|
||||
|
||||
this.bufferStream.length = 0;
|
||||
@ -173,8 +191,8 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
||||
this.statuses.push(wrapper);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
})
|
||||
.then(() => {
|
||||
this.isProcessingInfiniteScroll = false;
|
||||
@ -186,7 +204,7 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
||||
return regAccounts;
|
||||
}
|
||||
|
||||
|
||||
|
||||
private retrieveToots(): void {
|
||||
this.mastodonService.getTimeline(this.account, this._streamElement.type, null, null, this.streamingService.nbStatusPerIteration, this._streamElement.tag, this._streamElement.list)
|
||||
.then((results: Status[]) => {
|
||||
@ -194,9 +212,12 @@ export class StreamStatusesComponent implements OnInit, OnDestroy {
|
||||
const wrapper = new StatusWrapper(s, this.account);
|
||||
this.statuses.push(wrapper);
|
||||
}
|
||||
})
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private checkAndCleanUpStream(): void {
|
||||
if (this.streamPositionnedAtTop && this.statuses.length > 3 * this.streamingService.nbStatusPerIteration) {
|
||||
this.statuses.length = 2 * this.streamingService.nbStatusPerIteration;
|
||||
|
@ -1,7 +1,9 @@
|
||||
<div class="stream-column">
|
||||
|
||||
<app-stream-overlay class="stream-overlay" *ngIf="overlayActive" (closeOverlay)="closeOverlay()"
|
||||
[browseAccountData]="overlayAccountToBrowse" [browseHashtagData]="overlayHashtagToBrowse" [browseThreadData]="overlayThreadToBrowse"></app-stream-overlay>
|
||||
[browseAccountData]="overlayAccountToBrowse"
|
||||
[browseHashtagData]="overlayHashtagToBrowse"
|
||||
[browseThreadData]="overlayThreadToBrowse"></app-stream-overlay>
|
||||
|
||||
<div class="stream-column__stream-header">
|
||||
<a class="stream-column__open-menu" href title="edit column" (click)="openEditionMenu()">
|
||||
|
@ -5,6 +5,7 @@ import { faHome, faGlobe, faUser, faHashtag, faListUl, faBars, IconDefinition }
|
||||
import { StreamElement, StreamTypeEnum } from "../../states/streams.state";
|
||||
import { Status } from "../../services/models/mastodon.interfaces";
|
||||
import { AccountInfo } from "../../states/accounts.state";
|
||||
import { OpenThreadEvent } from "../../services/tools.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-stream",
|
||||
@ -18,7 +19,7 @@ export class StreamComponent implements OnInit {
|
||||
overlayActive: boolean;
|
||||
overlayAccountToBrowse: string;
|
||||
overlayHashtagToBrowse: string;
|
||||
overlayThreadToBrowse: string;
|
||||
overlayThreadToBrowse: OpenThreadEvent;
|
||||
|
||||
goToTopSubject: Subject<void> = new Subject<void>();
|
||||
|
||||
@ -75,10 +76,10 @@ export class StreamComponent implements OnInit {
|
||||
this.overlayActive = true;
|
||||
}
|
||||
|
||||
browseThread(statusUri: string): void {
|
||||
browseThread(openThreadEvent: OpenThreadEvent): void {
|
||||
this.overlayAccountToBrowse = null;
|
||||
this.overlayHashtagToBrowse = null;
|
||||
this.overlayThreadToBrowse = statusUri;
|
||||
this.overlayThreadToBrowse = openThreadEvent;
|
||||
this.overlayActive = true;
|
||||
}
|
||||
|
||||
@ -91,7 +92,6 @@ export class StreamComponent implements OnInit {
|
||||
|
||||
editionPanelIsOpen: boolean;
|
||||
openEditionMenu(): boolean {
|
||||
console.log('opened menu');
|
||||
this.editionPanelIsOpen = !this.editionPanelIsOpen;
|
||||
return false;
|
||||
}
|
||||
|
@ -1,64 +1,107 @@
|
||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
import { StatusWrapper } from '../stream.component';
|
||||
import { MastodonService } from '../../../services/mastodon.service';
|
||||
import { ToolsService } from '../../../services/tools.service';
|
||||
import { Status, Results, Context } from '../../../services/models/mastodon.interfaces';
|
||||
import { ToolsService, OpenThreadEvent } from '../../../services/tools.service';
|
||||
import { Results, Context, Status } from '../../../services/models/mastodon.interfaces';
|
||||
import { NotificationService } from '../../../services/notification.service';
|
||||
import { AccountInfo } from '../../../states/accounts.state';
|
||||
|
||||
@Component({
|
||||
selector: 'app-thread',
|
||||
templateUrl: '../stream-statuses/stream-statuses.component.html',
|
||||
styleUrls: ['../stream-statuses/stream-statuses.component.scss']
|
||||
})
|
||||
export class ThreadComponent implements OnInit {
|
||||
export class ThreadComponent implements OnInit {
|
||||
statuses: StatusWrapper[] = [];
|
||||
isLoading: boolean;
|
||||
displayError: string;
|
||||
|
||||
private lastThreadEvent: OpenThreadEvent;
|
||||
|
||||
@Output() browseAccountEvent = new EventEmitter<string>();
|
||||
@Output() browseHashtagEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
|
||||
|
||||
@Input('currentThread')
|
||||
set currentThread(thread: string) {
|
||||
set currentThread(thread: OpenThreadEvent) {
|
||||
if (thread) {
|
||||
this.isLoading = true;
|
||||
this.lastThreadEvent = thread;
|
||||
this.getThread(thread);
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly mastodonService: MastodonService) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
private getThread(thread: string) {
|
||||
private getThread(openThreadEvent: OpenThreadEvent) {
|
||||
this.statuses.length = 0;
|
||||
|
||||
let currentAccount = this.toolsService.getSelectedAccounts()[0];
|
||||
|
||||
this.mastodonService.search(currentAccount, thread, true)
|
||||
.then((result: Results) => {
|
||||
if (result.statuses.length === 1) {
|
||||
const retrievedStatus = result.statuses[0];
|
||||
this.mastodonService.getStatusContext(currentAccount, retrievedStatus.id)
|
||||
.then((context: Context) => {
|
||||
this.isLoading = false;
|
||||
let contextStatuses = [...context.ancestors, retrievedStatus, ...context.descendants]
|
||||
const status = openThreadEvent.status;
|
||||
const sourceAccount = openThreadEvent.sourceAccount;
|
||||
|
||||
for (const s of contextStatuses) {
|
||||
const wrapper = new StatusWrapper(s, currentAccount);
|
||||
this.statuses.push(wrapper);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
//TODO handle error
|
||||
this.isLoading = false;
|
||||
console.error('could not retrieve status');
|
||||
}
|
||||
if (status.visibility === 'public' || status.visibility === 'unlisted') {
|
||||
var statusPromise: Promise<Status> = Promise.resolve(status);
|
||||
|
||||
if (sourceAccount.id !== currentAccount.id) {
|
||||
statusPromise = this.mastodonService.search(currentAccount, status.uri, true)
|
||||
.then((result: Results) => {
|
||||
if (result.statuses.length === 1) {
|
||||
const retrievedStatus = result.statuses[0];
|
||||
return retrievedStatus;
|
||||
}
|
||||
throw new Error('could not find status');
|
||||
});
|
||||
}
|
||||
|
||||
this.retrieveThread(currentAccount, statusPromise);
|
||||
|
||||
} else if (sourceAccount.id === currentAccount.id) {
|
||||
|
||||
var statusPromise = Promise.resolve(status);
|
||||
this.retrieveThread(currentAccount, statusPromise);
|
||||
|
||||
} else {
|
||||
this.isLoading = false;
|
||||
this.displayError = `You need to use your account ${sourceAccount.username}@${sourceAccount.instance} to show this thread`;
|
||||
}
|
||||
}
|
||||
|
||||
private retrieveThread(currentAccount: AccountInfo, pipeline: Promise<Status>) {
|
||||
pipeline
|
||||
.then((status: Status) => {
|
||||
this.mastodonService.getStatusContext(currentAccount, status.id)
|
||||
.then((context: Context) => {
|
||||
this.isLoading = false;
|
||||
let contextStatuses = [...context.ancestors, status, ...context.descendants]
|
||||
|
||||
for (const s of contextStatuses) {
|
||||
const wrapper = new StatusWrapper(s, currentAccount);
|
||||
this.statuses.push(wrapper);
|
||||
}
|
||||
})
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.isLoading = false;
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
refresh(): any {
|
||||
this.isLoading = true;
|
||||
this.statuses.length = 0;
|
||||
this.getThread(this.lastThreadEvent);
|
||||
}
|
||||
|
||||
onScroll() {
|
||||
//Do nothing
|
||||
}
|
||||
@ -71,7 +114,7 @@ export class ThreadComponent implements OnInit {
|
||||
this.browseHashtagEvent.next(hashtag);
|
||||
}
|
||||
|
||||
browseThread(statusUri: string): void {
|
||||
this.browseThreadEvent.next(statusUri);
|
||||
browseThread(openThreadEvent: OpenThreadEvent): void {
|
||||
this.browseThreadEvent.next(openThreadEvent);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +1,16 @@
|
||||
<div class="profile">
|
||||
<div class="profile ">
|
||||
<app-waiting-animation *ngIf="isLoading" class="waiting-icon"></app-waiting-animation>
|
||||
|
||||
<div *ngIf="account" class="profile-header" [ngStyle]="{'background-image':'url('+account.header+')'}">
|
||||
<div class="profile-header__inner">
|
||||
<!-- <img class="profile-header__header" src="{{account.header}}" alt="header" /> -->
|
||||
<img class="profile-header__avatar" src="{{account.avatar}}" alt="header" />
|
||||
<h2 class="profile-header__display-name">{{account.display_name}}</h2>
|
||||
<h2 class="profile-header__fullhandle"><a href="{{account.url}}" target="_blank">@{{account.acct}}</a></h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="profile-sub-header flexcroll">
|
||||
<div *ngIf="account" class="profile-header" [ngStyle]="{'background-image':'url('+account.header+')'}">
|
||||
<div class="profile-header__inner">
|
||||
<!-- <img class="profile-header__header" src="{{account.header}}" alt="header" /> -->
|
||||
<img class="profile-header__avatar" src="{{account.avatar}}" alt="header" />
|
||||
<h2 class="profile-header__display-name">{{account.display_name}}</h2>
|
||||
<h2 class="profile-header__fullhandle"><a href="{{account.url}}" target="_blank">@{{account.acct}}</a></h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="account && hasNote" class="profile-description">
|
||||
<app-databinded-text class="status__content" [textIsSelectable]="false" [text]="account.note"
|
||||
(accountSelected)="browseAccount($event)"
|
||||
|
@ -44,6 +44,7 @@ $header-height: 160px;
|
||||
&-sub-header {
|
||||
overflow: auto;
|
||||
height: calc(100% - #{$header-height});
|
||||
height: calc(100%);
|
||||
// height: calc(20% - 190px);
|
||||
// height: 150px;
|
||||
// border: 1px solid greenyellow;
|
||||
|
@ -1,8 +1,11 @@
|
||||
import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { Account, Status, Results } from "../../../services/models/mastodon.interfaces";
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
import { Account, Status } from "../../../services/models/mastodon.interfaces";
|
||||
import { MastodonService } from '../../../services/mastodon.service';
|
||||
import { ToolsService } from '../../../services/tools.service';
|
||||
import { ToolsService, OpenThreadEvent } from '../../../services/tools.service';
|
||||
import { StatusWrapper } from '../stream.component';
|
||||
import { NotificationService } from '../../../services/notification.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-user-profile',
|
||||
@ -10,6 +13,7 @@ import { StatusWrapper } from '../stream.component';
|
||||
styleUrls: ['./user-profile.component.scss']
|
||||
})
|
||||
export class UserProfileComponent implements OnInit {
|
||||
|
||||
account: Account;
|
||||
hasNote: boolean;
|
||||
|
||||
@ -19,15 +23,28 @@ export class UserProfileComponent implements OnInit {
|
||||
|
||||
statuses: StatusWrapper[] = [];
|
||||
|
||||
private accountName: string;
|
||||
private lastAccountName: string;
|
||||
|
||||
@Output() browseAccountEvent = new EventEmitter<string>();
|
||||
@Output() browseHashtagEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<string>();
|
||||
@Output() browseThreadEvent = new EventEmitter<OpenThreadEvent>();
|
||||
|
||||
@Input('currentAccount')
|
||||
//set currentAccount(account: Account) {
|
||||
set currentAccount(accountName: string) {
|
||||
this.lastAccountName = accountName;
|
||||
this.load(this.lastAccountName);
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly mastodonService: MastodonService,
|
||||
private readonly toolsService: ToolsService) { }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
private load(accountName: string) {
|
||||
this.statuses.length = 0;
|
||||
this.isLoading = true;
|
||||
|
||||
@ -37,19 +54,17 @@ export class UserProfileComponent implements OnInit {
|
||||
this.hasNote = account && account.note && account.note !== '<p></p>';
|
||||
return this.getStatuses(this.account);
|
||||
})
|
||||
.catch(err => {
|
||||
this.error = 'Error when retrieving account';
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
})
|
||||
.then(() => {
|
||||
this.isLoading = false;
|
||||
this.statusLoading = false;
|
||||
console.error(this.error);
|
||||
});
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly mastodonService: MastodonService,
|
||||
private readonly toolsService: ToolsService) { }
|
||||
|
||||
ngOnInit() {
|
||||
refresh(): any {
|
||||
this.load(this.lastAccountName);
|
||||
}
|
||||
|
||||
browseAccount(accountName: string): void {
|
||||
@ -60,13 +75,13 @@ export class UserProfileComponent implements OnInit {
|
||||
this.browseHashtagEvent.next(hashtag);
|
||||
}
|
||||
|
||||
browseThread(statusUri: string): void {
|
||||
this.browseThreadEvent.next(statusUri);
|
||||
browseThread(openThreadEvent: OpenThreadEvent): void {
|
||||
this.browseThreadEvent.next(openThreadEvent);
|
||||
}
|
||||
|
||||
private loadAccount(accountName: string): Promise<Account> {
|
||||
this.account = null;
|
||||
this.accountName = accountName;
|
||||
|
||||
let selectedAccounts = this.toolsService.getSelectedAccounts();
|
||||
|
||||
if (selectedAccounts.length === 0) {
|
||||
@ -96,11 +111,5 @@ export class UserProfileComponent implements OnInit {
|
||||
}
|
||||
this.statusLoading = false;
|
||||
});
|
||||
// .catch(err => {
|
||||
|
||||
// })
|
||||
// .then(() => {
|
||||
// this.statusLoading = false;
|
||||
// });
|
||||
}
|
||||
}
|
||||
|
19
src/app/components/tutorial/tutorial.component.html
Normal file
19
src/app/components/tutorial/tutorial.component.html
Normal file
@ -0,0 +1,19 @@
|
||||
<!-- <div class="tutorial"> -->
|
||||
<div class="add-account" *ngIf="showAddAccount">
|
||||
<!-- <div class="add-account__arrow"></div> -->
|
||||
<img class="add-account__arrow" src="assets/img/arrow_1.png" alt="arrow pointing the +">
|
||||
<h3 class="add-account__title">Welcome to Sengi!</h3>
|
||||
<p class="add-account__description">
|
||||
Let's start, click the "+" button to add a new account.
|
||||
</p>
|
||||
</div>
|
||||
<div class="open-account" *ngIf="showOpenAccount">
|
||||
<!-- <div class="open-account__arrow"></div> -->
|
||||
<img class="open-account__arrow" src="assets/img/arrow_2.png" alt="arrow pointing the first account">
|
||||
<div class="open-account__mouse-icon"></div>
|
||||
<h3 class="open-account__title">Nice!</h3>
|
||||
<p class="open-account__description">
|
||||
Now <span class="underline">left-click</span> on your avatar to open your account and be able to add some timelines!
|
||||
</p>
|
||||
</div>
|
||||
<!-- </div> -->
|
83
src/app/components/tutorial/tutorial.component.scss
Normal file
83
src/app/components/tutorial/tutorial.component.scss
Normal file
@ -0,0 +1,83 @@
|
||||
@import "variables";
|
||||
@import "mixins";
|
||||
|
||||
// .tutorial {
|
||||
// width: $floating-column-size;
|
||||
|
||||
// overflow: hidden;
|
||||
// z-index: 99;
|
||||
// position: fixed;
|
||||
// top: 0;
|
||||
// bottom: $stream-selector-height;
|
||||
// padding: 0;
|
||||
|
||||
|
||||
// font-size: $default-font-size;
|
||||
// }
|
||||
|
||||
.underline {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
|
||||
.add-account{
|
||||
position: absolute;
|
||||
|
||||
&__arrow {
|
||||
position: fixed;
|
||||
top: 15px;
|
||||
left: 60px;
|
||||
}
|
||||
|
||||
&__title{
|
||||
position: relative;
|
||||
top: 30px;
|
||||
left: 70px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
position: relative;
|
||||
top: 45px;
|
||||
left: 75px;
|
||||
|
||||
text-align: center;
|
||||
|
||||
width: 200px;
|
||||
display: inline-block;
|
||||
|
||||
//word-break: break-all;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.open-account{
|
||||
position: absolute;
|
||||
|
||||
&__arrow {
|
||||
position: fixed;
|
||||
top: 85px;
|
||||
left: 65px;
|
||||
}
|
||||
|
||||
&__title{
|
||||
position: relative;
|
||||
top: 30px;
|
||||
left: 160px;
|
||||
width: 50px;
|
||||
}
|
||||
|
||||
&__description {
|
||||
position: relative;
|
||||
top: 40px;
|
||||
left: 90px;
|
||||
|
||||
text-align: right;
|
||||
|
||||
width: 200px;
|
||||
display: inline-block;
|
||||
|
||||
// word-break: break-all;
|
||||
white-space: normal;
|
||||
}
|
||||
}
|
25
src/app/components/tutorial/tutorial.component.spec.ts
Normal file
25
src/app/components/tutorial/tutorial.component.spec.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TutorialComponent } from './tutorial.component';
|
||||
|
||||
xdescribe('TutorialComponent', () => {
|
||||
let component: TutorialComponent;
|
||||
let fixture: ComponentFixture<TutorialComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [ TutorialComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TutorialComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
62
src/app/components/tutorial/tutorial.component.ts
Normal file
62
src/app/components/tutorial/tutorial.component.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Select } from '@ngxs/store';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
|
||||
import { AccountInfo } from '../../states/accounts.state';
|
||||
import { StreamElement } from '../../states/streams.state';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tutorial',
|
||||
templateUrl: './tutorial.component.html',
|
||||
styleUrls: ['./tutorial.component.scss']
|
||||
})
|
||||
export class TutorialComponent implements OnInit, OnDestroy {
|
||||
public showAddAccount: boolean;
|
||||
public showOpenAccount: boolean;
|
||||
|
||||
private hasAccounts: boolean;
|
||||
private hasColumns: boolean;
|
||||
|
||||
@Select(state => state.streamsstatemodel.streams) streamElements$: Observable<StreamElement[]>;
|
||||
@Select(state => state.registeredaccounts.accounts) accounts$: Observable<AccountInfo[]>;
|
||||
|
||||
private accountsSub: Subscription;
|
||||
private steamsSub: Subscription;
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.accountsSub = this.accounts$.subscribe((accounts: AccountInfo[]) => {
|
||||
if (accounts) {
|
||||
if (accounts.length === 0) {
|
||||
this.showAddAccount = true;
|
||||
this.showOpenAccount = false;
|
||||
} else {
|
||||
this.hasAccounts = true;
|
||||
this.showAddAccount = false;
|
||||
|
||||
if (!this.hasColumns) {
|
||||
this.showOpenAccount = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.steamsSub = this.streamElements$.subscribe((streams: StreamElement[]) => {
|
||||
if (streams) {
|
||||
if (streams.length === 0 && this.hasAccounts) {
|
||||
this.showOpenAccount = true;
|
||||
} else if(streams.length > 0 && this.hasAccounts){
|
||||
this.hasColumns = true;
|
||||
this.showOpenAccount = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.accountsSub.unsubscribe();
|
||||
this.steamsSub.unsubscribe();
|
||||
}
|
||||
}
|
@ -1,13 +1,13 @@
|
||||
import { Component, OnInit, Input } from "@angular/core";
|
||||
import { Store, Select } from '@ngxs/store';
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { Observable } from "rxjs";
|
||||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
|
||||
import { AuthService, CurrentAuthProcess } from "../../services/auth.service";
|
||||
import { TokenData, AppData } from "../../services/models/mastodon.interfaces";
|
||||
import { AddRegisteredApp, RegisteredAppsState, RegisteredAppsStateModel, AppInfo } from "../../states/registered-apps.state";
|
||||
import { TokenData } from "../../services/models/mastodon.interfaces";
|
||||
import { RegisteredAppsStateModel, AppInfo } from "../../states/registered-apps.state";
|
||||
import { AccountInfo, AddAccount } from "../../states/accounts.state";
|
||||
import { MastodonService } from "../../services/mastodon.service";
|
||||
import { NotificationService } from "../../services/notification.service";
|
||||
|
||||
@Component({
|
||||
selector: "app-register-new-account",
|
||||
@ -23,6 +23,7 @@ export class RegisterNewAccountComponent implements OnInit {
|
||||
private authStorageKey: string = 'tempAuth';
|
||||
|
||||
constructor(
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly store: Store,
|
||||
private readonly activatedRoute: ActivatedRoute,
|
||||
@ -57,6 +58,9 @@ export class RegisterNewAccountComponent implements OnInit {
|
||||
localStorage.removeItem(this.authStorageKey);
|
||||
this.router.navigate(['/home']);
|
||||
});
|
||||
})
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
15
src/app/services/notification.service.spec.ts
Normal file
15
src/app/services/notification.service.spec.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { TestBed, inject } from '@angular/core/testing';
|
||||
|
||||
import { NotificationService } from './notification.service';
|
||||
|
||||
xdescribe('NotificationService', () => {
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
providers: [NotificationService]
|
||||
});
|
||||
});
|
||||
|
||||
it('should be created', inject([NotificationService], (service: NotificationService) => {
|
||||
expect(service).toBeTruthy();
|
||||
}));
|
||||
});
|
33
src/app/services/notification.service.ts
Normal file
33
src/app/services/notification.service.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Subject } from 'rxjs';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationService {
|
||||
public notifactionStream = new Subject<NotificatioData>();
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
public notify(message: string, isError: boolean){
|
||||
let newNotification = new NotificatioData(message, isError);
|
||||
this.notifactionStream.next(newNotification);
|
||||
}
|
||||
|
||||
public notifyHttpError(err: HttpErrorResponse){
|
||||
console.error(err.message);
|
||||
let message = `${err.status}: ${err.statusText}`;
|
||||
this.notify(message, true);
|
||||
}
|
||||
}
|
||||
|
||||
export class NotificatioData {
|
||||
public id: string;
|
||||
|
||||
constructor(
|
||||
public message: string,
|
||||
public isError: boolean
|
||||
) {
|
||||
this.id = `${message}${new Date().getTime()}`;
|
||||
}
|
||||
}
|
@ -3,7 +3,8 @@ import { Store } from '@ngxs/store';
|
||||
|
||||
import { AccountInfo } from '../states/accounts.state';
|
||||
import { MastodonService } from './mastodon.service';
|
||||
import { Account, Results } from "./models/mastodon.interfaces";
|
||||
import { Account, Results, Status } from "./models/mastodon.interfaces";
|
||||
import { StatusWrapper } from '../components/stream/stream.component';
|
||||
|
||||
|
||||
@Injectable({
|
||||
@ -24,10 +25,6 @@ export class ToolsService {
|
||||
findAccount(account: AccountInfo, accountName: string): Promise<Account> {
|
||||
return this.mastodonService.search(account, accountName, true)
|
||||
.then((result: Results) => {
|
||||
console.warn('findAccount');
|
||||
console.warn(`accountName ${accountName}`);
|
||||
console.warn(result);
|
||||
|
||||
if(accountName[0] === '@') accountName = accountName.substr(1);
|
||||
|
||||
const foundAccount = result.accounts.filter(
|
||||
@ -38,4 +35,29 @@ export class ToolsService {
|
||||
});
|
||||
}
|
||||
|
||||
getStatusUsableByAccount(account: AccountInfo, originalStatus: StatusWrapper): Promise<Status>{
|
||||
const isProvider = originalStatus.provider.id === account.id;
|
||||
|
||||
let statusPromise: Promise<Status> = Promise.resolve(originalStatus.status);
|
||||
|
||||
if (!isProvider) {
|
||||
statusPromise = statusPromise.then((foreignStatus: Status) => {
|
||||
const statusUrl = foreignStatus.url;
|
||||
return this.mastodonService.search(account, statusUrl)
|
||||
.then((results: Results) => {
|
||||
return results.statuses[0];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
return statusPromise;
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenThreadEvent {
|
||||
constructor(
|
||||
public status: Status,
|
||||
public sourceAccount: AccountInfo
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
@ -29,10 +29,13 @@ export interface AccountsStateModel {
|
||||
export class AccountsState {
|
||||
@Action(AddAccount)
|
||||
AddAccount(ctx: StateContext<AccountsStateModel>, action: AddAccount) {
|
||||
const state = ctx.getState();
|
||||
const newAcc = action.account;
|
||||
newAcc.id = `${newAcc.username}@${newAcc.instance}`;
|
||||
|
||||
const state = ctx.getState();
|
||||
if(state.accounts.filter(x => x.isSelected).length === 0)
|
||||
newAcc.isSelected = true;
|
||||
|
||||
ctx.patchState({
|
||||
accounts: [...state.accounts, newAcc]
|
||||
});
|
||||
@ -41,19 +44,25 @@ export class AccountsState {
|
||||
@Action(SelectAccount)
|
||||
SelectAccount(ctx: StateContext<AccountsStateModel>, action: SelectAccount){
|
||||
const state = ctx.getState();
|
||||
const multiSelection = action.multiselection;
|
||||
// const multiSelection = action.multiselection;
|
||||
const selectedAccount = action.account;
|
||||
const copyAccounts = [...state.accounts];
|
||||
if(!multiSelection) {
|
||||
copyAccounts
|
||||
.filter(x => x.id !== selectedAccount.id)
|
||||
.forEach(x => x.isSelected = false);
|
||||
}
|
||||
const acc = copyAccounts.find(x => x.id === selectedAccount.id);
|
||||
acc.isSelected = !acc.isSelected;
|
||||
|
||||
|
||||
// const copyAccounts = [...state.accounts];
|
||||
// copyAccounts
|
||||
// .filter(x => x.id !== selectedAccount.id)
|
||||
// .forEach(x => x.isSelected = false);
|
||||
|
||||
const oldSelectedAccount = state.accounts.find(x => x.isSelected);
|
||||
|
||||
if(selectedAccount.id === oldSelectedAccount.id) return;
|
||||
|
||||
const acc = state.accounts.find(x => x.id === selectedAccount.id);
|
||||
acc.isSelected = true;
|
||||
oldSelectedAccount.isSelected = false;
|
||||
|
||||
ctx.patchState({
|
||||
accounts: copyAccounts
|
||||
accounts: [...state.accounts]
|
||||
});
|
||||
}
|
||||
|
||||
@ -61,6 +70,10 @@ export class AccountsState {
|
||||
RemoveAccount(ctx: StateContext<AccountsStateModel>, action: RemoveAccount){
|
||||
const state = ctx.getState();
|
||||
const filteredAccounts = state.accounts.filter(x => x.id !== action.accountId);
|
||||
|
||||
if(filteredAccounts.length === 1)
|
||||
filteredAccounts[0].isSelected = true;
|
||||
|
||||
ctx.patchState({
|
||||
accounts: filteredAccounts
|
||||
});
|
||||
|
BIN
src/assets/img/arrow_1.png
Normal file
BIN
src/assets/img/arrow_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
BIN
src/assets/img/arrow_2.png
Normal file
BIN
src/assets/img/arrow_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
@ -1,7 +1,10 @@
|
||||
.panel{
|
||||
width: 100%;
|
||||
// width: 100%;
|
||||
width: calc(100%);
|
||||
height: calc(100%);
|
||||
padding: 10px 10px 0 7px;
|
||||
font-size: $small-font-size;
|
||||
white-space: normal;
|
||||
&__title {
|
||||
font-size: 13px;
|
||||
text-transform: uppercase;
|
||||
|
@ -31,6 +31,7 @@ $favorite-color: #ffc16f;
|
||||
$stream-selector-height: 30px;
|
||||
$stream-column-separator: 7px;
|
||||
$stream-column-width: 320px;
|
||||
$floating-column-size: 330px;
|
||||
$avatar-column-space: 70px;
|
||||
|
||||
//Bootstrap cuistomization
|
||||
|
Loading…
x
Reference in New Issue
Block a user