Merge pull request #3 from NicolasConstant/topic-start-column-handling

Topic start column handling
This commit is contained in:
Nicolas Constant 2018-09-12 20:34:32 -04:00 committed by GitHub
commit dd08f5025c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 712 additions and 280 deletions

View File

@ -26,6 +26,7 @@
], ],
"stylePreprocessorOptions": { "stylePreprocessorOptions": {
"includePaths": [ "includePaths": [
"./src/sass",
"./node_modules/bootstrap/scss" "./node_modules/bootstrap/scss"
] ]
}, },

View File

@ -5,6 +5,9 @@
</app-streams-main-display>--> </app-streams-main-display>-->
<div id="display-zone"> <div id="display-zone">
<app-floating-column id="floating-column" *ngIf="floatingColumnActive">
</app-floating-column>
<router-outlet> <router-outlet>
</router-outlet> </router-outlet>
</div> </div>

View File

@ -1,7 +1,3 @@
app-left-side-bar {
}
#display-zone { #display-zone {
position: absolute; position: absolute;
top: 0; top: 0;
@ -13,6 +9,14 @@ app-left-side-bar {
white-space: nowrap; white-space: nowrap;
} }
#floating-column {
top: 0;
left: 0;
bottom: 0;
z-index: 9999;
}
app-streams-selection-footer { app-streams-selection-footer {
position: absolute; position: absolute;
height: 30px; height: 30px;

View File

@ -1,5 +1,8 @@
import { Component } from '@angular/core'; import { Component, OnInit, OnDestroy } from '@angular/core';
import { ElectronService } from 'ngx-electron'; import { ElectronService } from 'ngx-electron';
import { NavigationService } from './services/navigation.service';
import { Subscription } from 'rxjs';
import { AccountWrapper } from './models/account.models';
@Component({ @Component({
@ -7,15 +10,29 @@ import { ElectronService } from 'ngx-electron';
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrls: ['./app.component.scss'] styleUrls: ['./app.component.scss']
}) })
export class AppComponent { export class AppComponent implements OnInit, OnDestroy{
title = 'app'; title = 'app';
constructor(private _electronService: ElectronService) { private floatingColumnActive: boolean;
private columnEditorSub: Subscription;
constructor(private readonly navigationService: NavigationService) {
} }
launchWindow(){ ngOnInit(): void {
this._electronService.shell.openExternal('http://google.com'); this.columnEditorSub = this.navigationService.openColumnEditorSubject.subscribe((acc: AccountWrapper) => {
if(acc) {
this.floatingColumnActive = true;
} else {
this.floatingColumnActive = false;
}
});
} }
ngOnDestroy(): void {
this.columnEditorSub.unsubscribe();
}
} }

View File

@ -19,11 +19,15 @@ import { TootComponent } from "./components/toot/toot.component";
import { RegisterNewAccountComponent } from "./pages/register-new-account/register-new-account.component"; import { RegisterNewAccountComponent } from "./pages/register-new-account/register-new-account.component";
import { AuthService } from "./services/auth.service"; import { AuthService } from "./services/auth.service";
import { AccountsService } from "./services/accounts.service"; import { AccountsService } from "./services/accounts.service";
import { StreamsService } from "./services/streams.service";
import { StreamingService } from "./services/streaming.service"; import { StreamingService } from "./services/streaming.service";
import { RegisteredAppsState } from "./states/registered-apps.state"; import { RegisteredAppsState } from "./states/registered-apps.state";
import { AccountsState } from "./states/accounts.state"; import { AccountsState } from "./states/accounts.state";
import { AccountIconComponent } from './components/left-side-bar/presentation/account-icon/account-icon.component'; import { AccountIconComponent } from './components/left-side-bar/presentation/account-icon/account-icon.component';
import { NavigationService } from "./services/navigation.service";
import { FloatingColumnComponent } from './components/floating-column/floating-column.component';
import { ColumnsEditorComponent } from './components/floating-column/columns-editor/columns-editor.component';
import { MessageEditorComponent } from './components/floating-column/message-editor/message-editor.component';
import { StreamsState } from "./states/streams.state";
const routes: Routes = [ const routes: Routes = [
{ path: "", redirectTo: "home", pathMatch: "full" }, { path: "", redirectTo: "home", pathMatch: "full" },
@ -41,7 +45,10 @@ const routes: Routes = [
StreamsSelectionFooterComponent, StreamsSelectionFooterComponent,
TootComponent, TootComponent,
RegisterNewAccountComponent, RegisterNewAccountComponent,
AccountIconComponent AccountIconComponent,
FloatingColumnComponent,
ColumnsEditorComponent,
MessageEditorComponent
], ],
imports: [ imports: [
BrowserModule, BrowserModule,
@ -53,11 +60,12 @@ const routes: Routes = [
NgxsModule.forRoot([ NgxsModule.forRoot([
RegisteredAppsState, RegisteredAppsState,
AccountsState AccountsState,
StreamsState
]), ]),
NgxsStoragePluginModule.forRoot() NgxsStoragePluginModule.forRoot()
], ],
providers: [AuthService, AccountsService, StreamsService, StreamingService, { provide: APP_INITIALIZER, useFactory: settingsServiceFactory, deps: [AccountsService], multi: true }], providers: [AuthService, NavigationService, AccountsService, StreamingService, { provide: APP_INITIALIZER, useFactory: settingsServiceFactory, deps: [AccountsService], multi: true }],
bootstrap: [AppComponent] bootstrap: [AppComponent]
}) })
export class AppModule { } export class AppModule { }

View File

@ -0,0 +1,23 @@
<div class="account-editor">
<h3 class="account-editor__title">Manage Account</h3>
<div class="account-editor__display-avatar">
<img class="account-editor__avatar" src="{{account.avatar}}" title="{{ account.username }} " />
</div>
<h4 class="add-column__label">add column:</h4>
<a class="add-column__link" href *ngFor="let stream of availableStreams" (click)="addStream(stream)">
{{ stream.name }}
</a>
<!-- <a class="add-column__link" href>
Global Timeline
</a>
<a class="add-column__link" href>
Personnal Timeline
</a>
<a class="add-column__link" href>
Lists, Favs, Activitires, etc
</a> -->
</div>

View File

@ -0,0 +1,43 @@
@import "variables";
.account-editor {
padding: 10px 10px 0 7px;
font-size: $small-font-size;
&__title {
font-size: 13px;
text-transform: uppercase;
margin: 6px 0 12px 0;
}
&__display-avatar {
text-align: center;
margin-bottom: 30px;
}
&__avatar {
// display: block;
width: 75px;
border-radius: 50px;
transform: translateX(15px); // margin: auto;
}
}
.add-column {
&__label {
// text-decoration: underline;
font-size: $small-font-size;
margin-left: 5px;
color: $font-color-secondary;
}
&__link {
text-decoration: none;
display: block; // width: calc(100% - 20px);
width: 100%; // height: 30px;
padding: 5px 10px; // border: solid 1px black;
background-color: $color-primary;
color: #fff;
&:not(:last-child) {
margin-bottom: 5px;
}
&:hover {
background-color: lighten($color-primary, 15);
}
}
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ColumnsEditorComponent } from './columns-editor.component';
describe('ColumnsEditorComponent', () => {
let component: ColumnsEditorComponent;
let fixture: ComponentFixture<ColumnsEditorComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ColumnsEditorComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ColumnsEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,32 @@
import { Component, OnInit, Input } from '@angular/core';
import { StreamElement, StreamTypeEnum, AddStream } from '../../../states/streams.state';
import { Store } from '@ngxs/store';
import { AccountsStateModel, AccountInfo } from '../../../states/accounts.state';
import { AccountWrapper } from '../../../models/account.models';
@Component({
selector: 'app-columns-editor',
templateUrl: './columns-editor.component.html',
styleUrls: ['./columns-editor.component.scss']
})
export class ColumnsEditorComponent implements OnInit {
@Input() account: AccountWrapper;
availableStreams: StreamElement[] = [];
constructor(private readonly store: Store) { }
ngOnInit() {
this.availableStreams.length = 0;
this.availableStreams.push(new StreamElement(StreamTypeEnum.global, 'Global Timeline', this.account.username));
this.availableStreams.push(new StreamElement(StreamTypeEnum.local, 'Local Timeline', this.account.username));
this.availableStreams.push(new StreamElement(StreamTypeEnum.personnal, 'Personnal Timeline', this.account.username));
}
addStream(stream: StreamElement): boolean {
if (stream) {
this.store.dispatch([new AddStream(stream)]);
}
return false;
}
}

View File

@ -0,0 +1,9 @@
<div class="floating-column">
<div class="floating-column__header">
<a class="close-button" href (click)="closePanel()" title="close">x</a>
</div>
<app-columns-editor *ngIf="columnEditorIsOpen" [account]="userAccountUsed"></app-columns-editor>
<app-message-editor *ngIf="messageEditorIsOpen"></app-message-editor>
</div>

View File

@ -0,0 +1,46 @@
@import "variables";
@import "mixins";
.floating-column {
width: calc(100%);
max-width: 330px;
background-color: $color-secondary;
overflow: hidden;
z-index: 99;
position: fixed;
top: 0;
bottom: $stream-selector-height;
padding: 0;
&__header {
// @include clearfix;
}
}
.close-button {
// display: inline-block;
background-color: $color-primary;
color: darken(white, 30);
border-radius: 999px;
width: 26px;
height: 26px;
text-align: center;
text-decoration: none;
padding: 1px;
z-index: 9999;
float: right;
margin: 10px;
transition: all .2s;
&:hover {
background-color: lighten($color-primary, 20);
color: white;
// transform: scale(1.2);
}
}

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { FloatingColumnComponent } from './floating-column.component';
describe('FloatingColumnComponent', () => {
let component: FloatingColumnComponent;
let fixture: ComponentFixture<FloatingColumnComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ FloatingColumnComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(FloatingColumnComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,34 @@
import { Component, OnInit } from '@angular/core';
import { NavigationService } from '../../services/navigation.service';
import { AccountWrapper } from '../../models/account.models';
@Component({
selector: 'app-floating-column',
templateUrl: './floating-column.component.html',
styleUrls: ['./floating-column.component.scss']
})
export class FloatingColumnComponent implements OnInit {
userAccountUsed: AccountWrapper;
columnEditorIsOpen: boolean;
messageEditorIsOpen: boolean;
constructor(private readonly navigationService: NavigationService) { }
ngOnInit() {
this.navigationService.openColumnEditorSubject.subscribe((acc: AccountWrapper) => {
this.userAccountUsed = acc;
if(this.userAccountUsed) {
this.columnEditorIsOpen = true;
} else {
this.columnEditorIsOpen = false;
}
});
}
closePanel(): boolean {
this.navigationService.closeColumnEditor();
return false;
}
}

View File

@ -0,0 +1,3 @@
<p>
message-editor works!
</p>

View File

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MessageEditorComponent } from './message-editor.component';
describe('MessageEditorComponent', () => {
let component: MessageEditorComponent;
let fixture: ComponentFixture<MessageEditorComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ MessageEditorComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MessageEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@ -0,0 +1,15 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-message-editor',
templateUrl: './message-editor.component.html',
styleUrls: ['./message-editor.component.scss']
})
export class MessageEditorComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View File

@ -1,22 +1,16 @@
<div id="mam-left-bar"> <div class="left-bar" >
<div id="mam-create-toot"> <div id="create-toot">
<a href title="write toot!" (click)="createNewToot()">Toot!</a> <a class="create-toot__link left-bar-link" href title="write toot!" (click)="createNewToot()">Toot!</a>
</div> </div>
<div *ngFor="let account of accounts" >
<app-account-icon [account]="account" ></app-account-icon>
<!-- <a href title="{{ account.username }}" (click)="toogleAccount(account.id)"><img src="{{ account.avatar }}" /></a> -->
</div>
<div id="mam-account-add">
<a href title="add new account" [routerLink]="['/register']">+</a>
</div>
<div *ngFor="let account of accounts">
<app-account-icon [account]="account" (toogleAccountNotify)="onToogleAccountNotify($event)" (openMenuNotify)="onOpenMenuNotify($event)">
</app-account-icon>
<!-- <a href title="{{ account.username }}" (click)="toogleAccount(account.id)"><img src="{{ account.avatar }}" /></a> -->
</div>
<div class="add-account">
<a class="add-account__link left-bar-link" href title="add new account" [routerLink]="['/register']">+</a>
</div>
</div> </div>

View File

@ -1,55 +1,34 @@
#mam-left-bar { @import "variables";
width: 50px; .left-bar {
height: calc(100%); width: 50px;
background: green; height: calc(100%);
background: #090b10; background: $color-secondary;
/*outline: 1px dotted red;*/
} }
#mam-create-toot { .create-toot {
width: 50px; width: 50px;
&__link {
/* background-color: black; */ font-size: 0.8em;
margin: 0 0 0 10px;
}
} }
#mam-create-toot a { .add-account {
font-size: 0.8em; width: 50px;
/* color: white; */ height: 30px;
padding-top: 7px;
&__link {
font-size: 2em;
height: 10px;
margin: 0 15px;
line-height: 0;
}
}
.left-bar-link {
color: $font-link-primary;
text-decoration: none; text-decoration: none;
margin: 0 0 0 10px; &:hover {
} color: $font-link-primary-hover;
}
.mam-account-selector {
width: 50px;
padding-top: 4px;
}
.mam-account-selector a {
margin-left: 4px;
/*margin-top: 4px;*/
}
.mam-account-selector img {
width: 40px;
border-radius: 50%;
}
#mam-account-add {
width: 50px;
/*height: 50px;*/
/* background-color: black; */
}
a {
font-size: 2em;
color: #595c67;
text-decoration: none;
margin: 10px 0 0 15px;
}
a:hover {
color: #8f93a2;
} }

View File

@ -6,6 +6,7 @@ import { Account } from "../../services/models/mastodon.interfaces";
import { AccountWrapper } from "../../models/account.models"; import { AccountWrapper } from "../../models/account.models";
import { AccountsService } from "../../services/accounts.service"; import { AccountsService } from "../../services/accounts.service";
import { AccountsStateModel, AccountInfo } from "../../states/accounts.state"; import { AccountsStateModel, AccountInfo } from "../../states/accounts.state";
import { NavigationService } from "../../services/navigation.service";
@Component({ @Component({
@ -17,9 +18,11 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
accounts: AccountWrapper[] = []; accounts: AccountWrapper[] = [];
accounts$: Observable<AccountInfo[]>; accounts$: Observable<AccountInfo[]>;
private loadedAccounts: { [index: string]: AccountInfo } = {};
private sub: Subscription; private sub: Subscription;
constructor( constructor(
private readonly navigationService: NavigationService,
private readonly accountsService: AccountsService, private readonly accountsService: AccountsService,
private readonly store: Store) { private readonly store: Store) {
@ -29,24 +32,19 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
private currentLoading: number; private currentLoading: number;
ngOnInit() { ngOnInit() {
this.accounts$.subscribe((accounts: AccountInfo[]) => { this.accounts$.subscribe((accounts: AccountInfo[]) => {
console.warn(' this.accounts$.subscribe(');
console.warn(accounts);
if (accounts) { if (accounts) {
for (let acc of accounts) { this.loadedAccounts = {};
this.accounts.length = 0;
for (let acc of accounts) {
const accWrapper = new AccountWrapper(); const accWrapper = new AccountWrapper();
accWrapper.username = `${acc.username}@${acc.instance}`; accWrapper.username = `${acc.username}@${acc.instance}`;
this.accounts.push(accWrapper); this.accounts.push(accWrapper);
this.loadedAccounts[accWrapper.username] = acc;
this.accountsService.retrieveAccountDetails(acc) this.accountsService.retrieveAccountDetails(acc)
.then((result: Account) => { .then((result: Account) => {
console.error(result); accWrapper.avatar = result.avatar;
const accounts = this.accounts.filter(x => result.url.includes(acc.username) && result.url.includes(acc.instance));
for (const account of accounts) {
account.avatar = result.avatar;
}
}); });
} }
} }
@ -57,8 +55,13 @@ export class LeftSideBarComponent implements OnInit, OnDestroy {
this.sub.unsubscribe(); this.sub.unsubscribe();
} }
addNewAccount(): boolean { onToogleAccountNotify(acc: AccountWrapper) {
return false; console.warn(`onToogleAccountNotify username ${acc.username}`);
}
onOpenMenuNotify(acc: AccountWrapper) {
console.warn(`onOpenMenuNotify username ${acc.username}`);
this.navigationService.openColumnEditor(acc);
} }
createNewToot(): boolean { createNewToot(): boolean {

View File

@ -1,3 +1,4 @@
<a class="account-icon" href title="{{ account.username }}" (click)="toogleAccount()" (contextmenu)="openMenu()"> <a class="account-icon"
<img class="account-icon__avatar" src="{{ account.avatar }}" /> href title="{{ account.username }}" (click)="toogleAccount()" (contextmenu)="openMenu()">
<img class="account-icon__avatar" [class.account-icon__avatar--selected]="isSelected" src="{{ account.avatar }}" />
</a> </a>

View File

@ -1,15 +1,39 @@
.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: 50%;
border-radius: 2px;
width: 40px; width: 40px;
opacity: .3;
transition: all .2s;
&:hover {
filter: alpha(opacity=50);
opacity: .5;
}
&--selected {
// border-radius: 20%;
filter: alpha(opacity=100);
opacity: 1;
&:hover {
filter: alpha(opacity=100);
opacity: 1;
}
}
} }
// & a { // & a {
// margin-left: 4px; // margin-left: 4px;
// /*margin-top: 4px;*/ // /*margin-top: 4px;*/

View File

@ -1,8 +1,5 @@
import { Component, OnInit, Input } from '@angular/core'; import { Component, OnInit, Input, EventEmitter, Output } from '@angular/core';
import { AccountInfo } from '../../../../states/accounts.state';
import { AccountsService } from '../../../../services/accounts.service';
import { AccountWrapper } from '../../../../models/account.models'; import { AccountWrapper } from '../../../../models/account.models';
import { Account } from "../../../../services/models/mastodon.interfaces";
@Component({ @Component({
selector: 'app-account-icon', selector: 'app-account-icon',
@ -11,6 +8,10 @@ import { Account } from "../../../../services/models/mastodon.interfaces";
}) })
export class AccountIconComponent implements OnInit { export class AccountIconComponent implements OnInit {
@Input() account: AccountWrapper; @Input() account: AccountWrapper;
@Output() toogleAccountNotify = new EventEmitter<AccountWrapper>();
@Output() openMenuNotify = new EventEmitter<AccountWrapper>();
isSelected: boolean = false;
constructor() { } constructor() { }
@ -18,12 +19,13 @@ export class AccountIconComponent implements OnInit {
} }
toogleAccount(): boolean { toogleAccount(): boolean {
console.warn(`click`); this.toogleAccountNotify.emit(this.account);
this.isSelected = !this.isSelected;
return false; return false;
} }
openMenu(event): boolean { openMenu(event): boolean {
console.warn(`openMenu`); this.openMenuNotify.emit(this.account);
return false; return false;
} }
} }

View File

@ -1,8 +1,8 @@
<div id="mam-stream-column"> <div class="stream-column">
<div id="mam-stream-header"> <div class="stream-column__stream-header">
<a href title="return to top" (click)="goToTop()"><h1>{{ stream.streamName.toUpperCase() }}</h1></a> <a href title="return to top" (click)="goToTop()"><h1>{{ stream.streamName.toUpperCase() }}</h1></a>
</div> </div>
<div id="mam-stream-toots" data-simplebar> <div class="stream-toots" data-simplebar>
<div *ngFor="let toot of toots"> <div *ngFor="let toot of toots">
<app-toot [toot]="toot"></app-toot> <app-toot [toot]="toot"></app-toot>
</div> </div>

View File

@ -1,33 +1,31 @@
#mam-stream-column { @import "variables";
width: 320px; /*320*/ .stream-column {
width: $stream-column-width;
height: calc(100%); height: calc(100%);
background-color: #090b10;
background-color: #0f111a; background-color: #0f111a;
margin: 0 0 0 $stream-column-separator;
} &__stream-header {
width: calc(100%);
height: 30px;
#mam-stream-header { background-color: black;
width: calc(100%);
height: 30px;
background-color: black; border-bottom: 1px solid black;
border-bottom: 1px solid black; & h1 {
} color: whitesmoke;
font-size: 0.8em;
#mam-stream-header h1 { font-weight: normal;
color: whitesmoke; margin: 0;
font-size: 0.8em; padding: 8px 0 0 10px;
font-weight: normal; }
margin: 0;
padding: 8px 0 0 10px;
} }
}
.stream-toots {
#mam-stream-toots {
height: calc(100% - 30px); height: calc(100% - 30px);
width: 320px; width: 320px;
overflow: auto; overflow: auto;
} }

View File

@ -1,3 +1,5 @@
<div id="mam-streams-selection-footer"> <div class="streams-selection-footer">
<a class="stream-selection" *ngFor="let str of streams; let i=index" href (click)="onColumnSelection(i)" >
<span class="stream-selection__column-reprensentation"></span>
</a>
</div> </div>

View File

@ -1,6 +1,29 @@
#mam-streams-selection-footer { @import "variables";
width: calc(100%);
height: 30px;
background-color: #090b10; .streams-selection-footer {
width: calc(100%);
height: $stream-selector-height;
text-align: center;
background-color: $color-secondary;
}
.stream-selection {
display: inline-block;
width: 9px;
padding: 4px 5px 0 5px;
height: $stream-selector-height;
&__column-reprensentation {
display: inline-block;
width: 5px;
height: $stream-selector-height - 8px;
background-color:$font-link-primary;
}
&:hover &__column-reprensentation{
background-color:$font-link-primary-hover;
}
} }

View File

@ -1,4 +1,8 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { StreamElement } from '../../states/streams.state';
import { Store } from '@ngxs/store';
import { NavigationService } from '../../services/navigation.service';
@Component({ @Component({
selector: 'app-streams-selection-footer', selector: 'app-streams-selection-footer',
@ -6,10 +10,25 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./streams-selection-footer.component.scss'] styleUrls: ['./streams-selection-footer.component.scss']
}) })
export class StreamsSelectionFooterComponent implements OnInit { export class StreamsSelectionFooterComponent implements OnInit {
streams: StreamElement[] = [];
private streams$: Observable<StreamElement[]>;
constructor() { } constructor(
private readonly navigationService: NavigationService,
private readonly store: Store) {
this.streams$ = this.store.select(state => state.streamsstatemodel.streams);
}
ngOnInit() { ngOnInit() {
this.streams$.subscribe((streams: StreamElement[]) => {
this.streams = streams;
});
}
onColumnSelection(index: number): boolean {
console.warn(`column selected: ${index}`);
this.navigationService.columnSelected(index);
return false;
} }
} }

View File

@ -6,6 +6,7 @@ import { AccountWrapper } from "./account.models";
import { ApiRoutes } from "../services/models/api.settings"; import { ApiRoutes } from "../services/models/api.settings";
import { Account, Status } from "../services/models/mastodon.interfaces"; import { Account, Status } from "../services/models/mastodon.interfaces";
import { StreamingService, StreamingWrapper } from "../services/streaming.service"; import { StreamingService, StreamingWrapper } from "../services/streaming.service";
import { StreamTypeEnum } from "../states/streams.state";
export class Stream { export class Stream {
private apiRoutes = new ApiRoutes(); private apiRoutes = new ApiRoutes();
@ -46,22 +47,22 @@ export class Stream {
private getTimelineRoute(): string { private getTimelineRoute(): string {
switch (this.type) { switch (this.type) {
case StreamTypeEnum.Home: case StreamTypeEnum.personnal:
return this.apiRoutes.getHomeTimeline; return this.apiRoutes.getHomeTimeline;
case StreamTypeEnum.Local: case StreamTypeEnum.local:
return this.apiRoutes.getPublicTimeline + `?Local=true`; return this.apiRoutes.getPublicTimeline + `?Local=true`;
case StreamTypeEnum.Public: case StreamTypeEnum.global:
return this.apiRoutes.getPublicTimeline + `?Local=false`; return this.apiRoutes.getPublicTimeline + `?Local=false`;
} }
} }
} }
export enum StreamTypeEnum { // export enum StreamTypeEnum {
Home, // Home,
Public, // Public,
Local // Local
} // }
export class TootWrapper { export class TootWrapper {

View File

@ -43,8 +43,8 @@ export class RegisterNewAccountComponent implements OnInit {
this.authService.getToken(appDataWrapper.instance, appInfo.app.client_id, appInfo.app.client_secret, code, appInfo.app.redirect_uri) this.authService.getToken(appDataWrapper.instance, appInfo.app.client_id, appInfo.app.client_secret, code, appInfo.app.redirect_uri)
.then((tokenData: TokenData) => { .then((tokenData: TokenData) => {
const accountInfo = new AccountInfo(); const accountInfo = new AccountInfo();
accountInfo.username = appDataWrapper.username; accountInfo.username = appDataWrapper.username.toLowerCase();
accountInfo.instance = appDataWrapper.instance; accountInfo.instance = appDataWrapper.instance.toLowerCase();
accountInfo.token = tokenData; accountInfo.token = tokenData;
this.store.dispatch([new AddAccount(accountInfo)]) this.store.dispatch([new AddAccount(accountInfo)])
@ -120,7 +120,8 @@ export class RegisterNewAccountComponent implements OnInit {
const appDataTemp = new CurrentAuthProcess(username, instance); const appDataTemp = new CurrentAuthProcess(username, instance);
localStorage.setItem('tempAuth', JSON.stringify(appDataTemp)); localStorage.setItem('tempAuth', JSON.stringify(appDataTemp));
let instanceUrl = `https://${instance}/oauth/authorize?scope=${encodeURIComponent('read write follow')}&response_type=code&redirect_uri=${encodeURIComponent(app.redirect_uri)}&client_id=${app.client_id}`; let instanceUrl = this.authService.getInstanceLoginUrl(instance, app.client_id, app.redirect_uri);
// let instanceUrl = `https://${instance}/oauth/authorize?scope=${encodeURIComponent('read write follow')}&response_type=code&redirect_uri=${encodeURIComponent(app.redirect_uri)}&client_id=${app.client_id}`;
window.location.href = instanceUrl; window.location.href = instanceUrl;
} }

View File

@ -1,6 +1,5 @@
<div id="mam-main-display" class="flexcroll"> <div class="main-display flexcroll">
<div *ngFor="let s of streams" class="mam-stream-column"> <div class="main-display__stream-column" *ngFor="let s of streams">
<app-stream [stream]="s"></app-stream> <app-stream [stream]="s" #stream></app-stream>
</div> </div>
</div> </div>

View File

@ -1,53 +1,36 @@
#mam-main-display { @import "variables";
width: calc(100%); .main-display {
height: calc(100%); width: calc(100%);
overflow-x: auto; height: calc(100%);
overflow-y: hidden; overflow-x: auto;
overflow-y: hidden;
/* background: black; */ &__stream-column {
/*outline: 1px dotted red;*/ height: calc(100%);
width: $stream-column-width + $stream-column-separator;
display: inline-block;
overflow-x: hidden;
overflow-y: hidden;
white-space: normal;
// margin: 0 0 0 $stream-column-separator;
margin: 0;
}
} }
.flexcroll {
.mam-stream-column { scrollbar-face-color: #08090d;
height: calc(100%); scrollbar-shadow-color: #08090d;
width: 320px; scrollbar-highlight-color: #08090d;
display: inline-block; scrollbar-3dlight-color: #08090d;
overflow-x: hidden; scrollbar-darkshadow-color: #08090d;
overflow-y: hidden; scrollbar-track-color: #08090d;
white-space: normal; scrollbar-arrow-color: #08090d;
margin-left: 7px; &::-webkit-scrollbar {
} height: 7px;
}
&::-webkit-scrollbar-thumb {
.flexcroll{ -webkit-border-radius: 0px;
scrollbar-face-color: #08090d; border-radius: 0px;
scrollbar-shadow-color: #08090d; background: #08090d;
scrollbar-highlight-color: #08090d; -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.5);
scrollbar-3dlight-color: #08090d; }
scrollbar-darkshadow-color: #08090d;
scrollbar-track-color: #08090d;
scrollbar-arrow-color: #08090d;
}
/* Let's get this party started */
.flexcroll::-webkit-scrollbar {
/* width: 16px !important; */
height: 7px;
}
/* Track */
/* .flexcroll::-webkit-scrollbar-track {
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
-webkit-border-radius: 10px;
border-radius: 10px;
} */
/* Handle */
.flexcroll::-webkit-scrollbar-thumb {
-webkit-border-radius: 0px;
border-radius: 0px;
/* background: rgba(255,0,0,0.8); */
background: #08090d;
-webkit-box-shadow: inset 0 0 3px rgba(0,0,0,0.5);
} }

View File

@ -1,32 +1,65 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit, OnDestroy, ViewChild, QueryList, ViewChildren, ElementRef } from "@angular/core";
import { Stream } from "../../models/stream.models"; import { Stream } from "../../models/stream.models";
import { StreamsService } from "../../services/streams.service"; import { Observable, Subscription } from "rxjs";
import { StreamElement } from "../../states/streams.state";
import { Store } from "@ngxs/store";
import { Http } from "@angular/http";
import { NavigationService } from "../../services/navigation.service";
@Component({ @Component({
selector: "app-streams-main-display", selector: "app-streams-main-display",
templateUrl: "./streams-main-display.component.html", templateUrl: "./streams-main-display.component.html",
styleUrls: ["./streams-main-display.component.scss"] styleUrls: ["./streams-main-display.component.scss"]
}) })
export class StreamsMainDisplayComponent implements OnInit { export class StreamsMainDisplayComponent implements OnInit, OnDestroy {
streams: Stream[] = [];
constructor(private readonly streamService: StreamsService) { streams: Stream[] = [];
private streams$: Observable<StreamElement[]>;
private streamsStateSub: Subscription;
private columnSelectedSub: Subscription;
} constructor(
private readonly navigationService: NavigationService,
private readonly http: Http,
private readonly store: Store) {
this.streams$ = this.store.select(state => state.streamsstatemodel.streams);
ngOnInit() { }
this.streamService.streamsSubject.subscribe((streams: Stream[]) => {
for (let s of streams) {
this.streams.push(s);
}
});
//for (let i = 0; i < 3; i++) { ngOnInit() {
// this.streams.push(new Stream()); this.streamsStateSub = this.streams$.subscribe((streams: StreamElement[]) => {
//} this.streams.length = 0;
} for (const stream of streams) {
const newStream = new Stream(this.http, stream.name, stream.type);
this.streams.push(newStream);
}
this.columnSelectedSub = this.navigationService.columnSelectedSubject.subscribe((columnIndex: number) => {
this.focusOnColumn(columnIndex);
});
});
}
ngOnDestroy(): void {
this.streamsStateSub.unsubscribe();
this.columnSelectedSub.unsubscribe();
}
@ViewChildren('stream', { read: ElementRef }) public streamsElementRef: QueryList<ElementRef>;;
private focusOnColumn(columnIndex: number): void {
console.warn(`col selected: ${columnIndex}`);
if (columnIndex > -1) {
console.warn(this.streamsElementRef);
setTimeout(() => {
this.streamsElementRef.toArray()[columnIndex].nativeElement.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'start' });
});
}
}
} }

View File

@ -1,7 +1,8 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { HttpClient } from "@angular/common/http";
import { ApiRoutes } from './models/api.settings'; import { ApiRoutes } from './models/api.settings';
import { AppData, TokenData } from "./models/mastodon.interfaces"; import { AppData, TokenData } from "./models/mastodon.interfaces";
import { HttpClient } from "@angular/common/http";
@Injectable() @Injectable()
export class AuthService { export class AuthService {
@ -11,6 +12,10 @@ export class AuthService {
private readonly httpClient: HttpClient) { private readonly httpClient: HttpClient) {
} }
getInstanceLoginUrl(instance: string, client_id: string, redirect_uri: string): string{
return `https://${instance}/oauth/authorize?scope=${encodeURIComponent('read write follow')}&response_type=code&redirect_uri=${encodeURIComponent(redirect_uri)}&client_id=${client_id}`;
}
getToken(instance: string, client_id: string, client_secret: string, code: string, redirect_uri: string): Promise<TokenData> { getToken(instance: string, client_id: string, client_secret: string, code: string, redirect_uri: string): Promise<TokenData> {
const url = `https://${instance}/oauth/token?client_id=${client_id}&client_secret=${client_secret}&grant_type=authorization_code&code=${code}&redirect_uri=${encodeURIComponent(redirect_uri)}`; const url = `https://${instance}/oauth/token?client_id=${client_id}&client_secret=${client_secret}&grant_type=authorization_code&code=${code}&redirect_uri=${encodeURIComponent(redirect_uri)}`;

View File

@ -0,0 +1,15 @@
import { TestBed, inject } from '@angular/core/testing';
import { NavigationService } from './navigation.service';
describe('NavigationService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [NavigationService]
});
});
it('should be created', inject([NavigationService], (service: NavigationService) => {
expect(service).toBeTruthy();
}));
});

View File

@ -0,0 +1,24 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs';
import { AccountWrapper } from '../models/account.models';
@Injectable()
export class NavigationService {
openColumnEditorSubject = new BehaviorSubject<AccountWrapper>(null);
columnSelectedSubject = new BehaviorSubject<number>(-1);
constructor() { }
openColumnEditor(acc: AccountWrapper) {
this.openColumnEditorSubject.next(acc);
}
closeColumnEditor() {
this.openColumnEditorSubject.next(null);
}
columnSelected(index: number): void {
this.columnSelectedSubject.next(index);
}
}

View File

@ -1,51 +0,0 @@
import { Injectable } from "@angular/core";
import { Http } from "@angular/http";
import { BehaviorSubject } from "rxjs";
import { Stream, StreamTypeEnum } from "../models/stream.models";
import { AccountsService } from "./accounts.service";
@Injectable()
export class StreamsService {
streamsSubject = new BehaviorSubject<Stream[]>([]);
constructor(
private readonly httpService: Http,
// private readonly accountsService: AccountsService
) {
// Return home/local/public of all accounts
// this.accountsService.accountsSubject
// .subscribe((accounts: LocalAccount[]) => {
// const streams: Stream[] = [];
// for (let acc of accounts) {
// const homeStream = new Stream(this.httpService, "Home", StreamTypeEnum.Home, acc);
// const localStream = new Stream(this.httpService, "Local", StreamTypeEnum.Local, acc);
// const publicStream = new Stream(this.httpService, "Public", StreamTypeEnum.Public, acc);
// streams.push(homeStream);
// streams.push(localStream);
// streams.push(publicStream);
// }
// this.streamsSubject.next(streams);
// });
}
//getStreams(): void {
// // Return home/local/public of all accounts
// this.accountsService.accountsSubject
// .map((accounts: LocalAccount[]) => {
// const streams: Stream[] = [];
// for (let acc of accounts) {
// const homeStream = new Stream(this.httpService, "Home", StreamTypeEnum.Home, acc);
// const localStream = new Stream(this.httpService, "Local", StreamTypeEnum.Local, acc);
// const publicStream = new Stream(this.httpService, "Public", StreamTypeEnum.Public, acc);
// streams.push(homeStream);
// streams.push(localStream);
// streams.push(publicStream);
// }
// this.streamsSubject.next(streams);
// });
}

View File

@ -19,7 +19,7 @@ export interface AccountsStateModel {
}) })
export class AccountsState { export class AccountsState {
@Action(AddAccount) @Action(AddAccount)
AddRegisteredApp(ctx: StateContext<AccountsStateModel>, action: AddAccount) { AddAccount(ctx: StateContext<AccountsStateModel>, action: AddAccount) {
const state = ctx.getState(); const state = ctx.getState();
ctx.patchState({ ctx.patchState({
accounts: [...state.accounts, action.account] accounts: [...state.accounts, action.account]

View File

@ -0,0 +1,44 @@
import { State, Action, StateContext } from '@ngxs/store';
export class AddStream {
static readonly type = '[Streams] Add stream';
constructor(public stream: StreamElement) {}
}
export interface StreamsStateModel {
streams: StreamElement[];
}
@State<StreamsStateModel>({
name: 'streamsstatemodel',
defaults: {
streams: []
}
})
export class StreamsState {
@Action(AddStream)
AddStream(ctx: StateContext<StreamsStateModel>, action: AddStream) {
const state = ctx.getState();
ctx.patchState({
streams: [...state.streams, action.stream]
});
}
}
export class StreamElement {
constructor(public type: StreamTypeEnum, public name: string, public username: string) {
}
}
export enum StreamTypeEnum {
unknown = 0,
global = 1,
local = 2,
personnal = 3,
favorites = 4,
activity = 5,
list = 6,
directmessages = 7,
}

View File

@ -0,0 +1,7 @@
@mixin clearfix {
&::after {
content: "";
display: table;
clear: both;
}
}

View File

@ -1,5 +1,18 @@
$font-color-primary: #e8eaf3; $font-color-primary: #e8eaf3;
$font-color-secondary: darken(#fff, 25);
$font-link-primary: #595c67;
$font-link-primary-hover: #8f93a2;
$color-primary: #141824; $color-primary: #141824;
$color-secondary: #090b10;
$default-font-size: 15px; $default-font-size: 15px;
$small-font-size: 12px;
// Block dispositions
$stream-selector-height: 30px;
$stream-column-separator: 7px;
$stream-column-width: 320px;