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,7 +26,8 @@
],
"stylePreprocessorOptions": {
"includePaths": [
"./node_modules/bootstrap/scss"
"./src/sass",
"./node_modules/bootstrap/scss"
]
},
"scripts": [

View File

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

View File

@ -1,7 +1,3 @@
app-left-side-bar {
}
#display-zone {
position: absolute;
top: 0;
@ -13,6 +9,14 @@ app-left-side-bar {
white-space: nowrap;
}
#floating-column {
top: 0;
left: 0;
bottom: 0;
z-index: 9999;
}
app-streams-selection-footer {
position: absolute;
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 { NavigationService } from './services/navigation.service';
import { Subscription } from 'rxjs';
import { AccountWrapper } from './models/account.models';
@Component({
@ -7,15 +10,29 @@ import { ElectronService } from 'ngx-electron';
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
export class AppComponent implements OnInit, OnDestroy{
title = 'app';
constructor(private _electronService: ElectronService) {
private floatingColumnActive: boolean;
private columnEditorSub: Subscription;
constructor(private readonly navigationService: NavigationService) {
}
ngOnInit(): void {
this.columnEditorSub = this.navigationService.openColumnEditorSubject.subscribe((acc: AccountWrapper) => {
if(acc) {
this.floatingColumnActive = true;
} else {
this.floatingColumnActive = false;
}
});
}
launchWindow(){
this._electronService.shell.openExternal('http://google.com');
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 { AuthService } from "./services/auth.service";
import { AccountsService } from "./services/accounts.service";
import { StreamsService } from "./services/streams.service";
import { StreamingService } from "./services/streaming.service";
import { RegisteredAppsState } from "./states/registered-apps.state";
import { AccountsState } from "./states/accounts.state";
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 = [
{ path: "", redirectTo: "home", pathMatch: "full" },
@ -41,7 +45,10 @@ const routes: Routes = [
StreamsSelectionFooterComponent,
TootComponent,
RegisterNewAccountComponent,
AccountIconComponent
AccountIconComponent,
FloatingColumnComponent,
ColumnsEditorComponent,
MessageEditorComponent
],
imports: [
BrowserModule,
@ -53,11 +60,12 @@ const routes: Routes = [
NgxsModule.forRoot([
RegisteredAppsState,
AccountsState
AccountsState,
StreamsState
]),
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]
})
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 id="mam-create-toot">
<a href title="write toot!" (click)="createNewToot()">Toot!</a>
</div>
<div class="left-bar" >
<div id="create-toot">
<a class="create-toot__link left-bar-link" href title="write toot!" (click)="createNewToot()">Toot!</a>
</div>
<div *ngFor="let account of accounts" >
<app-account-icon [account]="account" ></app-account-icon>
<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>
<!-- <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>
<div class="add-account">
<a class="add-account__link left-bar-link" href title="add new account" [routerLink]="['/register']">+</a>
</div>
</div>

View File

@ -1,55 +1,34 @@
#mam-left-bar {
width: 50px;
height: calc(100%);
background: green;
background: #090b10;
/*outline: 1px dotted red;*/
@import "variables";
.left-bar {
width: 50px;
height: calc(100%);
background: $color-secondary;
}
#mam-create-toot {
width: 50px;
/* background-color: black; */
.create-toot {
width: 50px;
&__link {
font-size: 0.8em;
margin: 0 0 0 10px;
}
}
#mam-create-toot a {
font-size: 0.8em;
/* color: white; */
.add-account {
width: 50px;
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;
margin: 0 0 0 10px;
}
.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;
}
&:hover {
color: $font-link-primary-hover;
}
}

View File

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

View File

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

View File

@ -1,15 +1,39 @@
.account-icon {
display: inline-block;
width: 50px;
padding-top: 4px;
// padding-top: 4px;
// margin-left: 5px;
margin: 0 0 5px 5px;
&__avatar {
border-radius: 50%;
border-radius: 2px;
width: 40px;
}
opacity: .3;
transition: all .2s;
&:hover {
filter: alpha(opacity=50);
opacity: .5;
}
&--selected {
// border-radius: 20%;
filter: alpha(opacity=100);
opacity: 1;
&:hover {
filter: alpha(opacity=100);
opacity: 1;
}
}
}
// & a {
// margin-left: 4px;
// /*margin-top: 4px;*/

View File

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

View File

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

View File

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

View File

@ -1,3 +1,5 @@
<div id="mam-streams-selection-footer">
</div>
<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>

View File

@ -1,6 +1,29 @@
#mam-streams-selection-footer {
width: calc(100%);
height: 30px;
@import "variables";
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 { Observable } from 'rxjs';
import { StreamElement } from '../../states/streams.state';
import { Store } from '@ngxs/store';
import { NavigationService } from '../../services/navigation.service';
@Component({
selector: 'app-streams-selection-footer',
@ -6,10 +10,25 @@ import { Component, OnInit } from '@angular/core';
styleUrls: ['./streams-selection-footer.component.scss']
})
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() {
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 { Account, Status } from "../services/models/mastodon.interfaces";
import { StreamingService, StreamingWrapper } from "../services/streaming.service";
import { StreamTypeEnum } from "../states/streams.state";
export class Stream {
private apiRoutes = new ApiRoutes();
@ -36,7 +37,7 @@ export class Stream {
// .then((res: Response) => {
// const statuses = (res.json() as Status[])
// .map((status: Status) => {
// return new TootWrapper(status);
// return new TootWrapper(status);
// });
// this.statuses.next(statuses);
@ -46,22 +47,22 @@ export class Stream {
private getTimelineRoute(): string {
switch (this.type) {
case StreamTypeEnum.Home:
case StreamTypeEnum.personnal:
return this.apiRoutes.getHomeTimeline;
case StreamTypeEnum.Local:
case StreamTypeEnum.local:
return this.apiRoutes.getPublicTimeline + `?Local=true`;
case StreamTypeEnum.Public:
case StreamTypeEnum.global:
return this.apiRoutes.getPublicTimeline + `?Local=false`;
}
}
}
export enum StreamTypeEnum {
Home,
Public,
Local
}
// export enum StreamTypeEnum {
// Home,
// Public,
// Local
// }
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)
.then((tokenData: TokenData) => {
const accountInfo = new AccountInfo();
accountInfo.username = appDataWrapper.username;
accountInfo.instance = appDataWrapper.instance;
accountInfo.username = appDataWrapper.username.toLowerCase();
accountInfo.instance = appDataWrapper.instance.toLowerCase();
accountInfo.token = tokenData;
this.store.dispatch([new AddAccount(accountInfo)])
@ -120,7 +120,8 @@ export class RegisterNewAccountComponent implements OnInit {
const appDataTemp = new CurrentAuthProcess(username, instance);
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;
}

View File

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

View File

@ -1,53 +1,36 @@
#mam-main-display {
width: calc(100%);
height: calc(100%);
overflow-x: auto;
overflow-y: hidden;
/* background: black; */
/*outline: 1px dotted red;*/
@import "variables";
.main-display {
width: calc(100%);
height: calc(100%);
overflow-x: auto;
overflow-y: hidden;
&__stream-column {
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;
}
}
.mam-stream-column {
height: calc(100%);
width: 320px;
display: inline-block;
overflow-x: hidden;
overflow-y: hidden;
white-space: normal;
margin-left: 7px;
}
.flexcroll{
scrollbar-face-color: #08090d;
scrollbar-shadow-color: #08090d;
scrollbar-highlight-color: #08090d;
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);
.flexcroll {
scrollbar-face-color: #08090d;
scrollbar-shadow-color: #08090d;
scrollbar-highlight-color: #08090d;
scrollbar-3dlight-color: #08090d;
scrollbar-darkshadow-color: #08090d;
scrollbar-track-color: #08090d;
scrollbar-arrow-color: #08090d;
&::-webkit-scrollbar {
height: 7px;
}
&::-webkit-scrollbar-thumb {
-webkit-border-radius: 0px;
border-radius: 0px;
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 { 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({
selector: "app-streams-main-display",
templateUrl: "./streams-main-display.component.html",
styleUrls: ["./streams-main-display.component.scss"]
selector: "app-streams-main-display",
templateUrl: "./streams-main-display.component.html",
styleUrls: ["./streams-main-display.component.scss"]
})
export class StreamsMainDisplayComponent implements OnInit {
streams: Stream[] = [];
export class StreamsMainDisplayComponent implements OnInit, OnDestroy {
constructor(private readonly streamService: StreamsService) {
streams: Stream[] = [];
}
private streams$: Observable<StreamElement[]>;
private streamsStateSub: Subscription;
private columnSelectedSub: Subscription;
ngOnInit() {
this.streamService.streamsSubject.subscribe((streams: Stream[]) => {
for (let s of streams) {
this.streams.push(s);
}
});
constructor(
private readonly navigationService: NavigationService,
private readonly http: Http,
private readonly store: Store) {
this.streams$ = this.store.select(state => state.streamsstatemodel.streams);
//for (let i = 0; i < 3; i++) {
// this.streams.push(new Stream());
//}
}
}
ngOnInit() {
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 { HttpClient } from "@angular/common/http";
import { ApiRoutes } from './models/api.settings';
import { AppData, TokenData } from "./models/mastodon.interfaces";
import { HttpClient } from "@angular/common/http";
@Injectable()
export class AuthService {
@ -11,6 +12,10 @@ export class AuthService {
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> {
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 {
@Action(AddAccount)
AddRegisteredApp(ctx: StateContext<AccountsStateModel>, action: AddAccount) {
AddAccount(ctx: StateContext<AccountsStateModel>, action: AddAccount) {
const state = ctx.getState();
ctx.patchState({
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-secondary: darken(#fff, 25);
$font-link-primary: #595c67;
$font-link-primary-hover: #8f93a2;
$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;