Merge pull request #2 from NicolasConstant/topic-scss-migration

Topic scss migration
This commit is contained in:
Nicolas Constant 2018-09-09 18:55:27 -04:00 committed by GitHub
commit 50a4580ad8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 512 additions and 385 deletions

View File

@ -22,9 +22,13 @@
],
"styles": [
"./node_modules/simplebar/dist/simplebar.min.css",
"./node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.css"
"src/sass/styles.scss"
],
"stylePreprocessorOptions": {
"includePaths": [
"./node_modules/bootstrap/scss"
]
},
"scripts": [
"./node_modules/simplebar/dist/simplebar.min.js"
]
@ -127,7 +131,7 @@
"schematics": {
"@schematics/angular:component": {
"prefix": "app",
"styleext": "css"
"styleext": "scss"
},
"@schematics/angular:directive": {
"prefix": "app"

141
main.js
View File

@ -1,62 +1,121 @@
const {app, BrowserWindow, shell} = require('electron')
const path = require('path')
const url = require('url')
const { app, Menu, server, BrowserWindow, shell } = require('electron');
const path = require('path');
const url = require('url');
const http = require('http');
const fs = require('fs');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
function createWindow () {
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let win
function createWindow() {
// Create the browser window.
win = new BrowserWindow({ width: 395, height: 800, title: "Sengi", backgroundColor: '#FFF'})
win.setMenu(null);
win = new BrowserWindow({ width: 395, height: 800, title: "Sengi", backgroundColor: '#FFF' });
// and load the index.html of the app.
win.loadURL(url.format({
pathname: path.join(__dirname, 'dist/index.html'),
protocol: 'file:',
slashes: true
}))
var server = http.createServer(requestHandler).listen(9527);
win.loadURL('http://localhost:9527');
const template = [
{
label: 'View',
submenu: [
{ role: 'reload' },
{ role: 'forcereload' },
{ type: 'separator' },
{ role: 'close' }
]
},
{
role: 'help',
submenu: [
{ role: 'toggledevtools' },
{
label: 'Open GitHub project',
click() { require('electron').shell.openExternal('https://github.com/NicolasConstant/sengi') }
}
]
}
]
const menu = Menu.buildFromTemplate(template);
win.setMenu(menu);
// Open the DevTools.
//win.webContents.openDevTools()
// win.webContents.openDevTools()
//open external links to browser
win.webContents.on('new-window', function(event, url){
event.preventDefault();
shell.openExternal(url);
});
win.webContents.on('new-window', function (event, url) {
event.preventDefault();
shell.openExternal(url);
});
// Emitted when the window is closed.
win.on('closed', () => {
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null
// Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element.
win = null
})
}
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', () => {
};
function requestHandler(req, res) {
var file = req.url == '/' ? '/index.html' : req.url,
root = __dirname + '/dist',
page404 = root + '/404.html';
if (file.includes('register') || file.includes('home')) file = '/index.html';
getFile((root + file), res, page404);
};
function getFile(filePath, res, page404) {
console.warn(`filePath: ${filePath}`)
fs.exists(filePath, function (exists) {
if (exists) {
fs.readFile(filePath, function (err, contents) {
if (!err) {
res.end(contents);
} else {
console.dir(err);
}
});
} else {
fs.readFile(page404, function (err, contents) {
if (!err) {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end(contents);
} else {
console.dir(err);
}
});
}
});
};
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
app.on('ready', createWindow)
// Quit when all windows are closed.
app.on('window-all-closed', () => {
// On macOS it is common for applications and their menu bar
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
app.quit()
}
})
app.on('activate', () => {
})
app.on('activate', () => {
// On macOS it's common to re-create a window in the app when the
// dock icon is clicked and there are no other windows open.
if (win === null) {
createWindow()
createWindow()
}
})
})
// In this file you can include the rest of your app's specific main process
// code. You can also put them in separate files and require them here.

6
package-lock.json generated
View File

@ -7124,9 +7124,9 @@
"dev": true
},
"nan": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==",
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz",
"integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==",
"dev": true,
"optional": true
},

View File

@ -12,7 +12,7 @@
"lint": "ng lint",
"e2e": "ng e2e",
"electron": "ng build && electron .",
"electron-aot": "ng build --prod && electron ."
"electron-prod": "ng build --prod && electron ."
},
"private": true,
"dependencies": {

View File

@ -5,7 +5,7 @@ import { ElectronService } from 'ngx-electron';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'app';

View File

@ -1,6 +1,7 @@
import { BrowserModule } from "@angular/platform-browser";
import { FormsModule } from "@angular/forms";
import { HttpModule } from "@angular/http";
import { HttpClientModule } from '@angular/common/http';
import { NgModule, APP_INITIALIZER } from "@angular/core";
import { RouterModule, Routes } from "@angular/router";
@ -21,6 +22,8 @@ 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';
const routes: Routes = [
{ path: "", redirectTo: "home", pathMatch: "full" },
@ -37,17 +40,20 @@ const routes: Routes = [
StreamComponent,
StreamsSelectionFooterComponent,
TootComponent,
RegisterNewAccountComponent
RegisterNewAccountComponent,
AccountIconComponent
],
imports: [
BrowserModule,
HttpModule,
HttpClientModule,
FormsModule,
NgxElectronModule,
RouterModule.forRoot(routes),
NgxsModule.forRoot([
RegisteredAppsState
RegisteredAppsState,
AccountsState
]),
NgxsStoragePluginModule.forRoot()
],

View File

@ -3,10 +3,14 @@
<a href title="write toot!" (click)="createNewToot()">Toot!</a>
</div>
<div *ngFor="let account of accounts" class="mam-account-selector">
<a href title="{{ account.username }}" (click)="toogleAccount(account.id)"><img src="{{ account.avatar }}" /></a>
<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>

View File

@ -1,55 +1,62 @@
import { Component, OnInit, OnDestroy } from "@angular/core";
import { Subscription, BehaviorSubject } from "rxjs";
import { Subscription, BehaviorSubject, Observable } from "rxjs";
import { Store } from "@ngxs/store";
import { Account } from "../../services/models/mastodon.interfaces";
import { AccountWrapper } from "../../models/account.models";
import { AccountsService, LocalAccount } from "../../services/accounts.service";
import { AccountsService } from "../../services/accounts.service";
import { AccountsStateModel, AccountInfo } from "../../states/accounts.state";
@Component({
selector: "app-left-side-bar",
templateUrl: "./left-side-bar.component.html",
styleUrls: ["./left-side-bar.component.css"]
styleUrls: ["./left-side-bar.component.scss"]
})
export class LeftSideBarComponent implements OnInit, OnDestroy {
accounts: AccountWrapper[] = [];
accounts$: Observable<AccountInfo[]>;
private sub: Subscription;
constructor(
private readonly accountsService: AccountsService) { }
private readonly accountsService: AccountsService,
private readonly store: Store) {
this.accounts$ = this.store.select(state => state.registeredaccounts.accounts);
}
private currentLoading: number;
ngOnInit() {
this.sub = this.accountsService.accountsSubject.subscribe((accounts: LocalAccount[]) => {
this.accounts.length = 0;
this.accounts$.subscribe((accounts: AccountInfo[]) => {
console.warn(' this.accounts$.subscribe(');
console.warn(accounts);
for (let acc of accounts) {
const accWrapper = new AccountWrapper();
accWrapper.username = `${acc.mastodonAccount.username}@${acc.mastodonInstance.replace("https://", "")}`;
accWrapper.avatar = acc.mastodonAccount.avatar;
this.accounts.push(accWrapper);
if (accounts) {
for (let acc of accounts) {
const accWrapper = new AccountWrapper();
accWrapper.username = `${acc.username}@${acc.instance}`;
this.accounts.push(accWrapper);
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;
}
});
}
}
});
//const acc1 = new AccountWrapper();
//acc1.username = "@mastodon.social@Gargron";
//acc1.avatar = "https://files.mastodon.social/accounts/avatars/000/000/001/original/4df197532c6b768c.png";
//this.accounts.push(acc1);
//const acc2 = new AccountWrapper();
//acc2.username = "@mastodon.art@DearMsDearn";
//acc2.avatar = "https://curate.mastodon.art/gallery/accounts/avatars/000/015/092/original/3a112863f2dd22a27764179912dc8984.gif";
//this.accounts.push(acc2);
}
ngOnDestroy(): void {
this.sub.unsubscribe();
}
toogleAccount(accountId: number): boolean {
return false;
}
addNewAccount(): boolean {
return false;
}

View File

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

View File

@ -0,0 +1,21 @@
.account-icon {
display: inline-block;
width: 50px;
padding-top: 4px;
// margin-left: 5px;
margin: 0 0 5px 5px;
&__avatar {
border-radius: 50%;
width: 40px;
}
// & a {
// margin-left: 4px;
// /*margin-top: 4px;*/
// }
// & img {
// width: 40px;
// border-radius: 50%;
// }
}

View File

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

View File

@ -0,0 +1,29 @@
import { Component, OnInit, Input } from '@angular/core';
import { AccountInfo } from '../../../../states/accounts.state';
import { AccountsService } from '../../../../services/accounts.service';
import { AccountWrapper } from '../../../../models/account.models';
import { Account } from "../../../../services/models/mastodon.interfaces";
@Component({
selector: 'app-account-icon',
templateUrl: './account-icon.component.html',
styleUrls: ['./account-icon.component.scss']
})
export class AccountIconComponent implements OnInit {
@Input() account: AccountWrapper;
constructor() { }
ngOnInit() {
}
toogleAccount(): boolean {
console.warn(`click`);
return false;
}
openMenu(event): boolean {
console.warn(`openMenu`);
return false;
}
}

View File

@ -5,7 +5,7 @@ import { AccountWrapper } from "../../models/account.models";
@Component({
selector: "app-stream",
templateUrl: "./stream.component.html",
styleUrls: ["./stream.component.css"]
styleUrls: ["./stream.component.scss"]
})
export class StreamComponent implements OnInit {
private _stream: Stream;

View File

@ -3,7 +3,7 @@ import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-streams-selection-footer',
templateUrl: './streams-selection-footer.component.html',
styleUrls: ['./streams-selection-footer.component.css']
styleUrls: ['./streams-selection-footer.component.scss']
})
export class StreamsSelectionFooterComponent implements OnInit {

View File

@ -4,7 +4,7 @@ import { TootWrapper } from "../../models/stream.models";
@Component({
selector: "app-toot",
templateUrl: "./toot.component.html",
styleUrls: ["./toot.component.css"]
styleUrls: ["./toot.component.scss"]
})
export class TootComponent implements OnInit {
@Input() toot: TootWrapper;

View File

@ -2,7 +2,7 @@ import { Http, Headers, Response } from "@angular/http";
import { BehaviorSubject } from "rxjs";
import { AccountWrapper } from "./account.models";
import { LocalAccount } from "../services/accounts.service";
// import { LocalAccount } from "../services/accounts.service";
import { ApiRoutes } from "../services/models/api.settings";
import { Account, Status } from "../services/models/mastodon.interfaces";
import { StreamingService, StreamingWrapper } from "../services/streaming.service";
@ -15,8 +15,7 @@ export class Stream {
constructor(
private readonly httpService: Http,
public streamName: string,
private readonly type: StreamTypeEnum,
private readonly account: LocalAccount) {
private readonly type: StreamTypeEnum) {
this.retrieveToots(); //TODO change this for WebSockets
}
@ -31,17 +30,17 @@ export class Stream {
const route = this.getTimelineRoute();
const header = new Headers();
header.append("Authorization", `Bearer ${this.account.tokenData.access_token}`);
// header.append("Authorization", `Bearer ${this.account.tokenData.access_token}`);
this.httpService.get(this.account.mastodonInstance + route, { headers: header }).toPromise()
.then((res: Response) => {
const statuses = (res.json() as Status[])
.map((status: Status) => {
return new TootWrapper(status);
});
// this.httpService.get(this.account.mastodonInstance + route, { headers: header }).toPromise()
// .then((res: Response) => {
// const statuses = (res.json() as Status[])
// .map((status: Status) => {
// return new TootWrapper(status);
// });
this.statuses.next(statuses);
});
// this.statuses.next(statuses);
// });
}

View File

@ -1,88 +1,131 @@
import { Component, OnInit, Input } from "@angular/core";
import { Store, Select } from '@ngxs/store';
import { AuthService } from "../../services/auth.service";
import { TokenData } from "../../services/models/mastodon.interfaces";
import { AccountsService } from "../../services/accounts.service";
import { AddRegisteredApp, RegisteredAppsState, RegisteredAppsStateModel } from "../../states/registered-apps.state";
import { ActivatedRoute } from "@angular/router";
import { Observable } from "rxjs";
import { AuthService } from "../../services/auth.service";
import { TokenData, AppData } from "../../services/models/mastodon.interfaces";
import { AccountsService } from "../../services/accounts.service";
import { AddRegisteredApp, RegisteredAppsState, RegisteredAppsStateModel, AppInfo } from "../../states/registered-apps.state";
import { AccountInfo, AddAccount } from "../../states/accounts.state";
@Component({
selector: "app-register-new-account",
templateUrl: "./register-new-account.component.html",
styleUrls: ["./register-new-account.component.css"]
selector: "app-register-new-account",
templateUrl: "./register-new-account.component.html",
styleUrls: ["./register-new-account.component.scss"]
})
export class RegisterNewAccountComponent implements OnInit {
@Input() mastodonFullHandle: string;
// @Input() email: string;
// @Input() password: string;
result: string;
//@Select() registeredApps$: Observable<RegisteredAppsStateModel>;
registeredApps$: Observable<RegisteredAppsStateModel>;
@Input() mastodonFullHandle: string;
result: string;
// registeredApps$: Observable<RegisteredAppsStateModel>;
constructor(
private readonly authService: AuthService,
private readonly accountsService: AccountsService,
private readonly store: Store) {
private authStorageKey: string = 'tempAuth';
this.registeredApps$ = this.store.select(state => state.registeredapps.registeredApps);
constructor(
private readonly authService: AuthService,
private readonly accountsService: AccountsService,
private readonly store: Store,
private readonly activatedRoute: ActivatedRoute) {
// this.registeredApps$ = this.store.select(state => state.registeredapps.registeredApps);
this.activatedRoute.queryParams.subscribe(params => {
const code = params['code'];
if (!code) return;
const appDataWrapper = <CurrentAuthProcess>JSON.parse(localStorage.getItem(this.authStorageKey));
if (!appDataWrapper) return;
const appInfo = this.getAllSavedApps().filter(x => x.instance === appDataWrapper.instance)[0];
console.warn('appInfo');
console.warn(appInfo);
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.token = tokenData;
this.store.dispatch([new AddAccount(accountInfo)])
.subscribe(() => {
localStorage.removeItem(this.authStorageKey);
});
});
});
}
ngOnInit() {
this.registeredApps$.subscribe(x => {
console.error('registeredApps$')
console.warn(x);
});
ngOnInit() {
// this.registeredApps$.subscribe(x => {
// console.error('registeredApps$')
// console.warn(x);
// });
}
}
onSubmit(): boolean {
let fullHandle = this.mastodonFullHandle.split('@').filter(x => x != null && x !== '');
onSubmit(): boolean {
const username = fullHandle[0];
const instance = fullHandle[1];
this.store
.dispatch(new AddRegisteredApp({ name: 'test', id: 15, client_id: 'dsqdqs', client_secret: 'dsqdqs', redirect_uri: 'dsqdqs' }))
.subscribe(res => {
console.error('dispatch');
console.warn(res);
});
this.checkAndCreateApplication(instance)
.then((appData: AppData) => {
this.redirectToInstanceAuthPage(username, instance, appData);
});
return false;
}
private checkAndCreateApplication(instance: string): Promise<AppData> {
const alreadyRegisteredApps = this.getAllSavedApps();
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) => {
return this.saveNewApp(instance, appData)
.then(() => { return appData; });
})
}
}
// let fullHandle = this.mastodonFullHandle.split('@').filter(x => x != null && x !== '');
// console.log(fullHandle[0]);
// console.log(fullHandle[1]);
private getLocalHostname(): string {
let localHostname = location.protocol + '//' + location.hostname + (location.port ? ':' + location.port : '');
return localHostname;
}
// this.result = fullHandle[0] + '*' + fullHandle[1];
private getAllSavedApps(): AppInfo[] {
const snapshot = <RegisteredAppsStateModel>this.store.snapshot().registeredapps;
return snapshot.apps;
}
// window.location.href = "https://google.com";
private saveNewApp(instance: string, app: AppData): Promise<any> {
const appInfo = new AppInfo();
appInfo.instance = instance;
appInfo.app = app;
return this.store.dispatch([
new AddRegisteredApp(appInfo)
]).toPromise();
}
private redirectToInstanceAuthPage(username: string, instance: string, app: AppData) {
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}`;
//register app
//ask for getting token
// this.authService.getToken(this.mastodonNode, this.email, this.password)
// .then((res: TokenData) => {
// this.result = res.access_token;
// this.accountsService.addNewAccount(this.mastodonNode, this.email, res);
// })
// .catch(err => {
// this.result = err;
// });
return false;
}
window.location.href = instanceUrl;
}
}
class CurrentAuthProcess {
constructor(public username: string, public instance: string) { }
}

View File

@ -7,7 +7,7 @@ import { StreamsService } from "../../services/streams.service";
@Component({
selector: "app-streams-main-display",
templateUrl: "./streams-main-display.component.html",
styleUrls: ["./streams-main-display.component.css"]
styleUrls: ["./streams-main-display.component.scss"]
})
export class StreamsMainDisplayComponent implements OnInit {
streams: Stream[] = [];

View File

@ -4,97 +4,23 @@ import { Subject, BehaviorSubject } from "rxjs";
import { TokenData, Account } from "./models/mastodon.interfaces";
import { ApiRoutes } from "./models/api.settings";
import { AccountInfo } from "../states/accounts.state";
import { HttpClient, HttpHeaders } from "@angular/common/http";
@Injectable()
export class AccountsService {
private localAccountKey = "localAccounts";
private apiRoutes = new ApiRoutes();
accountsSubject: BehaviorSubject<LocalAccount[]>;
constructor(private readonly httpClient: HttpClient) {}
constructor(private readonly httpService: Http) {}
load(): Promise<boolean> {
return this.getAllLocalAccount()
.then((accounts) => {
this.accountsSubject = new BehaviorSubject<LocalAccount[]>(accounts);
return true;
});
retrieveAccountDetails(account: AccountInfo): Promise<Account> {
const headers = new HttpHeaders({'Authorization':`Bearer ${account.token.access_token}`});
// const headers = new HttpHeaders({'Bearer':`${account.token}`});
return this.httpClient.get<Account>('https://' + account.instance + this.apiRoutes.getCurrentAccount, {headers: headers}).toPromise();
}
addNewAccount(mastodonInstance: string, email: string, token: TokenData) {
const newAccount = new LocalAccount();
newAccount.mastodonInstance = mastodonInstance;
newAccount.email = email;
newAccount.tokenData = token;
this.getAllLocalAccount().then((allAccounts) => {
allAccounts.push(newAccount);
this.saveAccounts(allAccounts);
this.accountsSubject.next(allAccounts);
});
load(): any {
}
private getAllLocalAccount(): Promise<LocalAccount[]> {
const allSavedAccounts = this.retrieveSavedAccounts();
const allAccounts: LocalAccount[] = [];
const allTasks: Promise<any>[] = [];
for (let savedAcc of allSavedAccounts) {
const promise = this.retrieveMastodonDetails(savedAcc)
.then((acc) => {
allAccounts.push(acc);
})
.catch(err => console.error(err));
allTasks.push(promise);
}
return Promise.all(allTasks).then(() => {
return allAccounts;
});
}
private retrieveMastodonDetails(account: SavedLocalAccount): Promise<LocalAccount> {
const localAccount = new LocalAccount();
localAccount.mastodonInstance = account.mastodonInstance;
localAccount.email = account.email;
localAccount.tokenData = account.tokenData;
const header = new Headers();
header.append("Authorization", `Bearer ${localAccount.tokenData.access_token}`);
return this.httpService.get(localAccount.mastodonInstance + this.apiRoutes.getCurrentAccount, { headers: header }).toPromise()
.then((res: Response) => {
const mastodonAccount = res.json() as Account;
localAccount.mastodonAccount = mastodonAccount;
return localAccount;
});
}
private retrieveSavedAccounts(): SavedLocalAccount[] {
const savedData = <SavedLocalAccount[]>JSON.parse(localStorage.getItem(this.localAccountKey));
if (savedData) {
return savedData;
}
return [];
}
private saveAccounts(accounts: SavedLocalAccount[]) {
localStorage.setItem(this.localAccountKey, JSON.stringify(accounts));
}
}
class SavedLocalAccount {
mastodonInstance: string;
email: string;
tokenData: TokenData;
}
export class LocalAccount implements SavedLocalAccount {
mastodonAccount: Account;
mastodonInstance: string;
email: string;
tokenData: TokenData;
}
}

View File

@ -1,37 +0,0 @@
import { Injectable } from '@angular/core';
import { Http, Response, RequestOptions } from '@angular/http';
import { ApiRoutes } from './models/api.settings';
@Injectable()
export class AppService {
private apiRoutes = new ApiRoutes();
constructor(private readonly httpService: Http) {
}
createNewApplication(mastodonUrl: string): Promise<> {
const url = mastodonUrl + this.apiRoutes.createApp;
const options = new RequestOptions();
const formData = new FormData();
formData.append('client_name', 'Sengi');
formData.append('redirect_uris', '');
formData.append('scopes', 'read write follow');
formData.append('website', 'https://github.com/NicolasConstant/sengi');
return this.httpService.post(url, formData, options)
.pipe(
map((res: Response) => {
const result = res.json();
return result as TokenData;
}))
.toPromise()
.then((res: Response) => {
const result = res.json();
return result as TokenData;
});
}
}

View File

@ -1,48 +1,31 @@
import { Injectable } from "@angular/core";
import { Http, Response, RequestOptions } from "@angular/http";
import { ApiRoutes } from "./models/api.settings";
import { TokenData } from "./models/mastodon.interfaces";
import { ApiRoutes } from './models/api.settings';
import { AppData, TokenData } from "./models/mastodon.interfaces";
import { HttpClient } from "@angular/common/http";
@Injectable()
export class AuthService {
private apiRoutes = new ApiRoutes();
private apiRoutes = new ApiRoutes();
constructor(
private readonly httpService: Http) {
}
getToken(
mastodonNode: string, email: string, password: string): Promise<TokenData> {
//TODO retrieve those via API
const clientId = localStorage.getItem("client_id");
const clientSecret = localStorage.getItem("client_secret");
//Retrieve Token
const url = this.getHostUrl(mastodonNode) + this.apiRoutes.getToken;
const options = new RequestOptions();
const formData = new FormData();
formData.append("client_id", clientId);
formData.append("client_secret", clientSecret);
formData.append("grant_type", "password");
formData.append("username", email);
formData.append("password", password);
formData.append("scope", "read write follow");
return this.httpService.post(url, formData, options).toPromise()
.then((res: Response) => {
const result = res.json();
return result as TokenData;
});
}
private getHostUrl(url: string): string {
url = url.replace("http://", "");
if (!url.startsWith("https://")) {
url = "https://" + url;
constructor(
private readonly httpClient: HttpClient) {
}
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)}`;
return this.httpClient.post<TokenData>(url, null).toPromise();
}
createNewApplication(instance: string, appName: string, redirectUrl: string, scopes: string, website: string): Promise<AppData> {
const url = 'https://' + instance + this.apiRoutes.createApp;
const formData = new FormData();
formData.append('client_name', appName);
formData.append('redirect_uris', redirectUrl);
formData.append('scopes', scopes);
formData.append('website', website);
return this.httpClient.post<AppData>(url, formData).toPromise();
}
return url;
}
}

View File

@ -1,3 +1,12 @@
export interface AppData {
client_id: string;
client_secret: string;
id: string;
name: string;
redirect_uri: string;
website: string;
}
export interface TokenData {
access_token: string;
token_type: string;

View File

@ -3,7 +3,7 @@ import { Http } from "@angular/http";
import { BehaviorSubject } from "rxjs";
import { Stream, StreamTypeEnum } from "../models/stream.models";
import { AccountsService, LocalAccount } from "./accounts.service";
import { AccountsService } from "./accounts.service";
@Injectable()
export class StreamsService {
@ -11,23 +11,24 @@ export class StreamsService {
constructor(
private readonly httpService: Http,
private readonly accountsService: AccountsService) {
// 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);
// 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);
});
// streams.push(homeStream);
// streams.push(localStream);
// streams.push(publicStream);
// }
// this.streamsSubject.next(streams);
// });
}

View File

@ -0,0 +1,34 @@
import { State, Action, StateContext } from '@ngxs/store';
import { TokenData } from '../services/models/mastodon.interfaces';
export class AddAccount {
static readonly type = '[Accounts] Add account';
constructor(public account: AccountInfo) {}
}
export interface AccountsStateModel {
accounts: AccountInfo[];
}
@State<AccountsStateModel>({
name: 'registeredaccounts',
defaults: {
accounts: []
}
})
export class AccountsState {
@Action(AddAccount)
AddRegisteredApp(ctx: StateContext<AccountsStateModel>, action: AddAccount) {
const state = ctx.getState();
ctx.patchState({
accounts: [...state.accounts, action.account]
});
}
}
export class AccountInfo {
username: string;
instance: string;
token: TokenData;
}

View File

@ -1,26 +1,27 @@
import { State, Action, StateContext } from '@ngxs/store';
import { AppData } from '../services/models/mastodon.interfaces';
export class AddRegisteredApp {
static readonly type = '[RegisteredApps] Add app';
constructor(public app: AppInfo) { }
}
export interface RegisteredAppsStateModel {
registeredApps: AppInfo[];
apps: AppInfo[];
}
@State<RegisteredAppsStateModel>({
name: 'registeredapps',
defaults: {
registeredApps: []
}
name: 'registeredapps',
defaults: {
apps: []
}
})
export class RegisteredAppsState {
export class RegisteredAppsState {
@Action(AddRegisteredApp)
AddRegisteredApp(ctx: StateContext<RegisteredAppsStateModel>, action: AddRegisteredApp) {
const state = ctx.getState();
ctx.patchState({
registeredApps: [...state.registeredApps, action.app]
apps: [...state.apps, action.app]
});
// ctx.setState({
@ -30,9 +31,6 @@ export class RegisteredAppsState {
}
export class AppInfo {
id: number;
name: string;
redirect_uri: string;
client_id: string;
client_secret: string;
instance: string;
app: AppData;
}

0
src/sass/_mixins.scss Normal file
View File

5
src/sass/_variables.scss Normal file
View File

@ -0,0 +1,5 @@
$font-color-primary: #e8eaf3;
$color-primary: #141824;
$default-font-size: 15px;

43
src/sass/styles.scss Normal file
View File

@ -0,0 +1,43 @@
@import './variables';
@import './mixins';
@import "bootstrap";
*,
*::after,
*::before {
margin: 0;
padding: 0;
box-sizing: inherit;
}
body {
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
// padding: 0;
// margin: 0;
font-family: 'Roboto', sans-serif;
font-size: $default-font-size;
color: $font-color-primary;
background-color: $color-primary;
}
.invisible {
display: none;
}
/* .ellipsis {
} */
#toot-content p {
margin-bottom: 0 !important;
}
#toot-content a {
color: #bec3d8;
}

View File

@ -1,35 +0,0 @@
/* You can add global styles to this file, and also import other style files */
html, body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
font-family: 'Roboto', sans-serif;
/* font-family: "mastodon-font-sans-serif",sans-serif; */
font-size: 15px;
/* color: whitesmoke; */
color: #c9cbd4;
color: #a8acbc;
color: #e8eaf3;
background-color: #0f111a;
background-color: #0f111a;
background-color: #0a0c12;
background-color: #141824;
}
.invisible {
display: none;
}
/* .ellipsis {
} */
#toot-content p {
margin-bottom: 0 !important;
}
#toot-content a {
color: #bec3d8;
}