|
@ -44,4 +44,4 @@ before_script:
|
|||
- sleep 3
|
||||
|
||||
script:
|
||||
- npm run dist
|
||||
- npm run travis
|
272
angular.json
|
@ -1,145 +1,149 @@
|
|||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"sengi": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico"
|
||||
],
|
||||
"styles": [
|
||||
"src/sass/styles.scss",
|
||||
"node_modules/@ctrl/ngx-emoji-mart/picker.css"
|
||||
],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": [
|
||||
"./src/sass",
|
||||
"./node_modules/bootstrap/scss"
|
||||
]
|
||||
},
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "sengi:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "sengi:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "sengi:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"karmaConfig": "./karma.conf.js",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"scripts": [],
|
||||
"styles": [
|
||||
"src/sass/styles.scss"
|
||||
],
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico"
|
||||
],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": [
|
||||
"./src/sass",
|
||||
"./node_modules/bootstrap/scss"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json",
|
||||
"src/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"sengi": {
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:browser",
|
||||
"options": {
|
||||
"outputPath": "dist",
|
||||
"index": "src/index.html",
|
||||
"main": "src/main.ts",
|
||||
"tsConfig": "src/tsconfig.app.json",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico",
|
||||
"src/manifest.json"
|
||||
],
|
||||
"styles": [
|
||||
"src/sass/styles.scss",
|
||||
"node_modules/@ctrl/ngx-emoji-mart/picker.css"
|
||||
],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": [
|
||||
"./src/sass",
|
||||
"./node_modules/bootstrap/scss"
|
||||
]
|
||||
},
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"optimization": true,
|
||||
"outputHashing": "all",
|
||||
"sourceMap": false,
|
||||
"extractCss": true,
|
||||
"namedChunks": false,
|
||||
"aot": true,
|
||||
"extractLicenses": true,
|
||||
"vendorChunk": false,
|
||||
"buildOptimizer": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.prod.ts"
|
||||
}
|
||||
],
|
||||
"serviceWorker": true,
|
||||
"ngswConfigPath": "src/ngsw-config.json"
|
||||
}
|
||||
}
|
||||
},
|
||||
"sengi-e2e": {
|
||||
"root": "e2e",
|
||||
"sourceRoot": "e2e",
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "./protractor.conf.js",
|
||||
"devServerTarget": "sengi:serve"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"e2e/tsconfig.e2e.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"options": {
|
||||
"browserTarget": "sengi:build"
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"browserTarget": "sengi:build:production"
|
||||
}
|
||||
}
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||
"options": {
|
||||
"browserTarget": "sengi:build"
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"main": "src/test.ts",
|
||||
"karmaConfig": "./karma.conf.js",
|
||||
"polyfills": "src/polyfills.ts",
|
||||
"tsConfig": "src/tsconfig.spec.json",
|
||||
"scripts": [],
|
||||
"styles": [
|
||||
"src/sass/styles.scss"
|
||||
],
|
||||
"assets": [
|
||||
"src/assets",
|
||||
"src/favicon.ico",
|
||||
"src/manifest.json"
|
||||
],
|
||||
"stylePreprocessorOptions": {
|
||||
"includePaths": [
|
||||
"./src/sass",
|
||||
"./node_modules/bootstrap/scss"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"src/tsconfig.app.json",
|
||||
"src/tsconfig.spec.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "sengi",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"prefix": "app",
|
||||
"styleext": "scss"
|
||||
"sengi-e2e": {
|
||||
"root": "e2e",
|
||||
"sourceRoot": "e2e",
|
||||
"projectType": "application",
|
||||
"architect": {
|
||||
"e2e": {
|
||||
"builder": "@angular-devkit/build-angular:protractor",
|
||||
"options": {
|
||||
"protractorConfig": "./protractor.conf.js",
|
||||
"devServerTarget": "sengi:serve"
|
||||
}
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"prefix": "app"
|
||||
"lint": {
|
||||
"builder": "@angular-devkit/build-angular:tslint",
|
||||
"options": {
|
||||
"tsConfig": [
|
||||
"e2e/tsconfig.e2e.json"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules/**"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"defaultProject": "sengi",
|
||||
"schematics": {
|
||||
"@schematics/angular:component": {
|
||||
"prefix": "app",
|
||||
"styleext": "scss"
|
||||
},
|
||||
"@schematics/angular:directive": {
|
||||
"prefix": "app"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,4 @@
|
|||
const { app, Menu, server, BrowserWindow, shell } = require("electron");
|
||||
const path = require("path");
|
||||
const url = require("url");
|
||||
const http = require("http");
|
||||
const fs = require("fs");
|
||||
const { app, Menu, BrowserWindow, shell } = require("electron");
|
||||
|
||||
// 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.
|
||||
|
@ -14,15 +10,19 @@ function createWindow() {
|
|||
width: 377,
|
||||
height: 800,
|
||||
title: "Sengi",
|
||||
backgroundColor: "#FFF",
|
||||
useContentSize: true
|
||||
backgroundColor: "#131925",
|
||||
useContentSize: true,
|
||||
// webPreferences: {
|
||||
// contextIsolation: true,
|
||||
// nodeIntegration: false,
|
||||
// nodeIntegrationInWorker: false
|
||||
// }
|
||||
});
|
||||
|
||||
win.setAutoHideMenuBar(true);
|
||||
win.setMenuBarVisibility(false);
|
||||
|
||||
var server = http.createServer(requestHandler).listen(9527);
|
||||
const sengiUrl = "http://localhost:9527";
|
||||
|
||||
const sengiUrl = "https://sengi.nicolas-constant.com";
|
||||
win.loadURL(sengiUrl);
|
||||
|
||||
const template = [
|
||||
|
@ -139,9 +139,6 @@ function createWindow() {
|
|||
);
|
||||
}
|
||||
|
||||
// Open the DevTools.
|
||||
// win.webContents.openDevTools()
|
||||
|
||||
//open external links to browser
|
||||
win.webContents.on("new-window", function (event, url) {
|
||||
event.preventDefault();
|
||||
|
@ -157,40 +154,6 @@ function createWindow() {
|
|||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app.commandLine.appendSwitch("force-color-profile", "srgb");
|
||||
|
||||
|
||||
|
@ -228,7 +191,4 @@ app.on("activate", () => {
|
|||
if (win === null) {
|
||||
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.
|
||||
});
|
15
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "sengi",
|
||||
"version": "0.20.1",
|
||||
"version": "0.21.0",
|
||||
"license": "AGPL-3.0-or-later",
|
||||
"main": "main-electron.js",
|
||||
"description": "A multi-account desktop client for Mastodon and Pleroma",
|
||||
|
@ -12,7 +12,7 @@
|
|||
"type": "git",
|
||||
"url": "https://github.com/NicolasConstant/sengi.git"
|
||||
},
|
||||
"scripts": {
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"start-mem": "node --max_old_space_size=5048 ./node_modules/@angular/cli/bin/ng serve",
|
||||
|
@ -21,9 +21,11 @@
|
|||
"test-nowatch": "ng test --watch=false",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e",
|
||||
"electron": "ng build --prod && electron .",
|
||||
"electron": "electron .",
|
||||
"electron-prod": "ng build --prod && electron .",
|
||||
"electron-debug": "ng build && electron .",
|
||||
"dist": "npm run build && electron-builder --publish onTagOrDraft"
|
||||
"dist": "npm run build && electron-builder --publish onTagOrDraft",
|
||||
"travis": "electron-builder --publish onTagOrDraft"
|
||||
},
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
|
@ -36,7 +38,9 @@
|
|||
"@angular/http": "^7.2.7",
|
||||
"@angular/platform-browser": "^7.2.7",
|
||||
"@angular/platform-browser-dynamic": "^7.2.7",
|
||||
"@angular/pwa": "^0.12.4",
|
||||
"@angular/router": "^7.2.7",
|
||||
"@angular/service-worker": "^7.2.7",
|
||||
"@ctrl/ngx-emoji-mart": "^0.17.0",
|
||||
"@fortawesome/angular-fontawesome": "^0.3.0",
|
||||
"@fortawesome/fontawesome-svg-core": "^1.2.13",
|
||||
|
@ -66,7 +70,7 @@
|
|||
"@types/jasminewd2": "~2.0.3",
|
||||
"@types/node": "~8.9.4",
|
||||
"codelyzer": "~4.2.1",
|
||||
"electron": "^4.0.6",
|
||||
"electron": "^8.0.2",
|
||||
"electron-builder": "^20.39.0",
|
||||
"jasmine-core": "~2.99.1",
|
||||
"jasmine-spec-reporter": "~4.2.1",
|
||||
|
@ -89,6 +93,7 @@
|
|||
"productName": "Sengi",
|
||||
"appId": "org.sengi.desktop",
|
||||
"artifactName": "${productName}-${version}-${os}.${ext}",
|
||||
"npmRebuild": false,
|
||||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<div class="drag-and-drop" *ngIf="drag" (dragover)="dragover($event)" (drop)="drop($event)"
|
||||
<div class="drag-and-drop" *ngIf="drag" (dragover)="dragover($event)" (drop)="drop($event)"
|
||||
[ngClass]="{'drag-and-drop__on-drag': drag2 === true }">
|
||||
<!-- (dragleave)="dragleave($event)" -->
|
||||
<div class="drag-and-drop__card">
|
||||
|
@ -10,6 +10,12 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="auto-update" [class.auto-update__activated]="updateAvailable">
|
||||
<div class="auto-update__display">
|
||||
<div class="auto-update__display--text">A new version is available!</div> <a href class="auto-update__display--reload" (click)="loadNewVersion()">reload</a> <a href class="auto-update__display--close" (click)="closeAutoUpdate()"><fa-icon [icon]="faTimes"></fa-icon></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<app-media-viewer id="media-viewer" *ngIf="openedMediaEvent" [openedMediaEvent]="openedMediaEvent"
|
||||
(closeSubject)="closeMedia()" (dragenter)="dragenter($event)"></app-media-viewer>
|
||||
|
||||
|
|
|
@ -97,4 +97,75 @@ app-streams-selection-footer {
|
|||
right: 0;
|
||||
bottom: 0;
|
||||
left: 50px;
|
||||
}
|
||||
|
||||
.auto-update {
|
||||
transition: all .2s;
|
||||
transition-timing-function: ease-in;
|
||||
|
||||
position: absolute;
|
||||
height: 70px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: -80px;
|
||||
z-index: 999999999;
|
||||
|
||||
&__activated {
|
||||
// opacity: 1;
|
||||
transition: all .25s;
|
||||
transition-timing-function: ease-out;
|
||||
bottom: 0px;
|
||||
}
|
||||
|
||||
&__display {
|
||||
position: relative;
|
||||
// height: 30px;
|
||||
width: 300px;
|
||||
// margin: 0 auto 30px auto;
|
||||
margin: auto;
|
||||
border-radius: 2px;
|
||||
color: rgba(rgb(0, 4, 24), 1);
|
||||
background: #ffffff;
|
||||
|
||||
box-shadow: 0px 0px 10px rgb(0, 0, 0);
|
||||
|
||||
&--text {
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
&--reload {
|
||||
transition: all .2s;
|
||||
position: absolute;
|
||||
right: 30px;
|
||||
|
||||
padding: 5px 10px;
|
||||
text-decoration: none;
|
||||
color: #ffffff;
|
||||
background-color: #3e455f;
|
||||
|
||||
&:hover {
|
||||
background-color: #1d202c;
|
||||
}
|
||||
}
|
||||
|
||||
&--close {
|
||||
transition: all .2s;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
|
||||
display: inline-block;
|
||||
padding: 5px 10px;
|
||||
text-decoration: none;
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
|
||||
color: #ffffff;
|
||||
background-color: #3e455f;
|
||||
|
||||
&:hover {
|
||||
background-color: #1d202c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,25 @@
|
|||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Subscription, Observable, Subject } from 'rxjs';
|
||||
import { debounceTime, map } from 'rxjs/operators';
|
||||
import { Select } from '@ngxs/store';
|
||||
// import { ElectronService } from 'ngx-electron';
|
||||
import { Select, Store } from '@ngxs/store';
|
||||
|
||||
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
import { NavigationService, LeftPanelType, OpenLeftPanelEvent } from './services/navigation.service';
|
||||
import { StreamElement } from './states/streams.state';
|
||||
import { AccountInfo, AddAccount } from "./states/accounts.state";
|
||||
import { OpenMediaEvent } from './models/common.model';
|
||||
import { ToolsService } from './services/tools.service';
|
||||
import { MediaService } from './services/media.service';
|
||||
import { ServiceWorkerService } from './services/service-worker.service';
|
||||
import { AuthService, CurrentAuthProcess } from './services/auth.service';
|
||||
|
||||
import { MastodonWrapperService } from './services/mastodon-wrapper.service';
|
||||
import { TokenData, Account } from './services/models/mastodon.interfaces';
|
||||
import { NotificationService } from './services/notification.service';
|
||||
import { AppInfo, RegisteredAppsStateModel } from './states/registered-apps.state';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
|
@ -16,26 +27,96 @@ import { MediaService } from './services/media.service';
|
|||
styleUrls: ['./app.component.scss']
|
||||
})
|
||||
export class AppComponent implements OnInit, OnDestroy {
|
||||
faTimes = faTimes;
|
||||
title = 'Sengi';
|
||||
floatingColumnActive: boolean;
|
||||
tutorialActive: boolean;
|
||||
// mediaViewerActive: boolean = false;
|
||||
openedMediaEvent: OpenMediaEvent
|
||||
updateAvailable: boolean;
|
||||
|
||||
private authStorageKey: string = 'tempAuth';
|
||||
|
||||
private columnEditorSub: Subscription;
|
||||
private openMediaSub: Subscription;
|
||||
private streamSub: Subscription;
|
||||
private dragoverSub: Subscription;
|
||||
|
||||
private updateAvailableSub: Subscription;
|
||||
private paramsSub: Subscription;
|
||||
|
||||
@Select(state => state.streamsstatemodel.streams) streamElements$: Observable<StreamElement[]>;
|
||||
|
||||
constructor(
|
||||
private readonly router: Router,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly store: Store,
|
||||
private readonly mastodonService: MastodonWrapperService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly activatedRoute: ActivatedRoute,
|
||||
private readonly serviceWorkerService: ServiceWorkerService,
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly mediaService: MediaService,
|
||||
private readonly navigationService: NavigationService) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.paramsSub = this.activatedRoute.queryParams.subscribe(params => {
|
||||
const code = params['code'];
|
||||
if (!code) {
|
||||
return;
|
||||
}
|
||||
|
||||
const appDataWrapper = <CurrentAuthProcess>JSON.parse(localStorage.getItem(this.authStorageKey));
|
||||
if (!appDataWrapper) {
|
||||
this.notificationService.notify('', 400, 'Something when wrong in the authentication process. Please retry.', true);
|
||||
this.router.navigate(['/']);
|
||||
return;
|
||||
}
|
||||
|
||||
const appInfo = this.getAllSavedApps().filter(x => x.instance === appDataWrapper.instance)[0];
|
||||
let usedTokenData: TokenData;
|
||||
this.authService.getToken(appDataWrapper.instance, appInfo.app.client_id, appInfo.app.client_secret, code, appInfo.app.redirect_uri)
|
||||
.then((tokenData: TokenData) => {
|
||||
|
||||
if(tokenData.refresh_token && !tokenData.created_at){
|
||||
const nowEpoch = Date.now() / 1000 | 0;
|
||||
tokenData.created_at = nowEpoch;
|
||||
}
|
||||
|
||||
usedTokenData = tokenData;
|
||||
|
||||
return this.mastodonService.retrieveAccountDetails({ 'instance': appDataWrapper.instance, 'id': '', 'username': '', 'order': 0, 'isSelected': true, 'token': tokenData });
|
||||
})
|
||||
.then((account: Account) => {
|
||||
var username = account.username.toLowerCase();
|
||||
var instance = appDataWrapper.instance.toLowerCase();
|
||||
|
||||
if(this.isAccountAlreadyPresent(username, instance)){
|
||||
this.notificationService.notify(null, null, `Account @${username}@${instance} is already registered`, true);
|
||||
this.router.navigate(['/']);
|
||||
return;
|
||||
}
|
||||
|
||||
const accountInfo = new AccountInfo();
|
||||
accountInfo.username = username;
|
||||
accountInfo.instance = instance;
|
||||
accountInfo.token = usedTokenData;
|
||||
|
||||
this.store.dispatch([new AddAccount(accountInfo)])
|
||||
.subscribe(() => {
|
||||
localStorage.removeItem(this.authStorageKey);
|
||||
this.router.navigate(['/']);
|
||||
});
|
||||
})
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err, null);
|
||||
this.router.navigate(['/']);
|
||||
});
|
||||
});
|
||||
|
||||
this.updateAvailableSub = this.serviceWorkerService.newAppVersionIsAvailable.subscribe((updateAvailable) => {
|
||||
this.updateAvailable = updateAvailable;
|
||||
});
|
||||
|
||||
this.streamSub = this.streamElements$.subscribe((streams: StreamElement[]) => {
|
||||
if (streams && streams.length === 0) {
|
||||
this.tutorialActive = true;
|
||||
|
@ -60,12 +141,11 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
this.dragoverSub = this.dragoverSubject
|
||||
.pipe(
|
||||
debounceTime(1500)
|
||||
)
|
||||
.subscribe(() => {
|
||||
.subscribe(() => {
|
||||
this.drag = false;
|
||||
})
|
||||
}
|
||||
|
@ -75,6 +155,8 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
this.columnEditorSub.unsubscribe();
|
||||
this.openMediaSub.unsubscribe();
|
||||
this.dragoverSub.unsubscribe();
|
||||
this.updateAvailableSub.unsubscribe();
|
||||
this.paramsSub.unsubscribe();
|
||||
}
|
||||
|
||||
closeMedia() {
|
||||
|
@ -96,7 +178,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
return false;
|
||||
}
|
||||
dragover(event): boolean {
|
||||
// console.warn('dragover');
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this.dragoverSubject.next(true);
|
||||
|
@ -112,4 +193,29 @@ export class AppComponent implements OnInit, OnDestroy {
|
|||
this.mediaService.uploadMedia(selectedAccount, files);
|
||||
return false;
|
||||
}
|
||||
|
||||
loadNewVersion(): boolean {
|
||||
this.serviceWorkerService.loadNewAppVersion();
|
||||
return false;
|
||||
}
|
||||
|
||||
closeAutoUpdate(): boolean {
|
||||
this.updateAvailable = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
private isAccountAlreadyPresent(username: string, instance: string): boolean{
|
||||
const accounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts;
|
||||
for (let acc of accounts) {
|
||||
if(acc.instance === instance && acc.username == username){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private getAllSavedApps(): AppInfo[] {
|
||||
const snapshot = <RegisteredAppsStateModel>this.store.snapshot().registeredapps;
|
||||
return snapshot.apps;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import { LeftSideBarComponent } from "./components/left-side-bar/left-side-bar.c
|
|||
import { StreamsMainDisplayComponent } from "./pages/streams-main-display/streams-main-display.component";
|
||||
import { StreamComponent } from "./components/stream/stream.component";
|
||||
import { StreamsSelectionFooterComponent } from "./components/streams-selection-footer/streams-selection-footer.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 { StreamingService } from "./services/streaming.service";
|
||||
import { RegisteredAppsState } from "./states/registered-apps.state";
|
||||
|
@ -79,13 +79,15 @@ import { ScheduledStatusesComponent } from './components/floating-column/schedul
|
|||
import { ScheduledStatusComponent } from './components/floating-column/scheduled-statuses/scheduled-status/scheduled-status.component';
|
||||
import { StreamNotificationsComponent } from './components/stream/stream-notifications/stream-notifications.component';
|
||||
import { NotificationComponent } from './components/floating-column/manage-account/notifications/notification/notification.component';
|
||||
import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
import { environment } from '../environments/environment';
|
||||
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: "", redirectTo: "home", pathMatch: "full" },
|
||||
{ path: "home", component: StreamsMainDisplayComponent },
|
||||
{ path: "register", component: RegisterNewAccountComponent },
|
||||
{ path: "**", redirectTo: "home" }
|
||||
{ path: "", component: StreamsMainDisplayComponent },
|
||||
// { path: "home", component: StreamsMainDisplayComponent },
|
||||
// { path: "register", component: RegisterNewAccountComponent },
|
||||
{ path: "**", redirectTo: "" }
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
@ -96,7 +98,7 @@ const routes: Routes = [
|
|||
StreamComponent,
|
||||
StreamsSelectionFooterComponent,
|
||||
StatusComponent,
|
||||
RegisterNewAccountComponent,
|
||||
// RegisterNewAccountComponent,
|
||||
AccountIconComponent,
|
||||
FloatingColumnComponent,
|
||||
ManageAccountComponent,
|
||||
|
@ -167,7 +169,8 @@ const routes: Routes = [
|
|||
]),
|
||||
NgxsStoragePluginModule.forRoot(),
|
||||
ContextMenuModule.forRoot(),
|
||||
HotkeyModule.forRoot()
|
||||
HotkeyModule.forRoot(),
|
||||
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })
|
||||
],
|
||||
providers: [AuthService, NavigationService, NotificationService, MastodonWrapperService, MastodonService, StreamingService],
|
||||
bootstrap: [AppComponent],
|
||||
|
|
|
@ -104,12 +104,12 @@ export class AddNewAccountComponent implements OnInit {
|
|||
} else {
|
||||
let redirect_uri = this.getLocalHostname();
|
||||
|
||||
let userAgent = navigator.userAgent.toLowerCase();
|
||||
console.log(`userAgent ${userAgent}`);
|
||||
// let userAgent = navigator.userAgent.toLowerCase();
|
||||
// console.log(`userAgent ${userAgent}`);
|
||||
|
||||
if (userAgent.includes(' electron/')) {
|
||||
redirect_uri += '/register';
|
||||
}
|
||||
// if (userAgent.includes(' electron/')) {
|
||||
// redirect_uri += '/register';
|
||||
// }
|
||||
|
||||
return this.authService.createNewApplication(instance, 'Sengi', redirect_uri, 'read write follow', 'https://nicolasconstant.github.io/sengi/')
|
||||
.then((appData: AppData) => {
|
||||
|
|
|
@ -55,7 +55,12 @@
|
|||
</div>
|
||||
|
||||
<h4 class="panel__subtitle">About</h4>
|
||||
<p class="version">Sengi version: {{version}}</p>
|
||||
<p class="version">
|
||||
Sengi version: {{version}}<br/>
|
||||
<a href class="version__link" (click)="checkForUpdates()">check for updates</a>
|
||||
<app-waiting-animation *ngIf="isCheckingUpdates" class="waiting-icon"></app-waiting-animation>
|
||||
</p>
|
||||
|
||||
|
||||
<h4 class="panel__subtitle">RESET</h4>
|
||||
<div class="sub-section">
|
||||
|
|
|
@ -17,6 +17,14 @@
|
|||
.version {
|
||||
display: block;
|
||||
padding: 0 5px;
|
||||
|
||||
&__link {
|
||||
color: rgb(161, 161, 161);
|
||||
|
||||
&:hover {
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sub-section {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { Howl } from 'howler';
|
|||
import { environment } from '../../../../environments/environment';
|
||||
import { ToolsService } from '../../../services/tools.service';
|
||||
import { UserNotificationService, NotificationSoundDefinition } from '../../../services/user-notification.service';
|
||||
import { ServiceWorkerService } from '../../../services/service-worker.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings',
|
||||
|
@ -13,7 +14,7 @@ import { UserNotificationService, NotificationSoundDefinition } from '../../../s
|
|||
})
|
||||
|
||||
export class SettingsComponent implements OnInit {
|
||||
|
||||
|
||||
notificationSounds: NotificationSoundDefinition[];
|
||||
notificationSoundId: string;
|
||||
notificationForm: FormGroup;
|
||||
|
@ -28,13 +29,14 @@ export class SettingsComponent implements OnInit {
|
|||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private serviceWorkersService: ServiceWorkerService,
|
||||
private readonly toolsService: ToolsService,
|
||||
private readonly userNotificationsService: UserNotificationService) { }
|
||||
|
||||
ngOnInit() {
|
||||
this.version = environment.VERSION;
|
||||
|
||||
const settings = this.toolsService.getSettings();
|
||||
const settings = this.toolsService.getSettings();
|
||||
|
||||
this.notificationSounds = this.userNotificationsService.getAllNotificationSounds();
|
||||
this.notificationSoundId = settings.notificationSoundFileId;
|
||||
|
@ -46,18 +48,18 @@ export class SettingsComponent implements OnInit {
|
|||
this.disableAvatarNotificationsEnabled = settings.disableAvatarNotifications;
|
||||
this.disableSoundsEnabled = settings.disableSounds;
|
||||
|
||||
if(!settings.columnSwitchingWinAlt){
|
||||
if (!settings.columnSwitchingWinAlt) {
|
||||
this.columnShortcutEnabled = ColumnShortcut.Ctrl;
|
||||
} else {
|
||||
this.columnShortcutEnabled = ColumnShortcut.Win;
|
||||
}
|
||||
}
|
||||
|
||||
onShortcutChange(id: ColumnShortcut){
|
||||
onShortcutChange(id: ColumnShortcut) {
|
||||
this.columnShortcutEnabled = id;
|
||||
this.columnShortcutChanged = true;
|
||||
|
||||
let settings = this.toolsService.getSettings()
|
||||
let settings = this.toolsService.getSettings()
|
||||
settings.columnSwitchingWinAlt = id === ColumnShortcut.Win;
|
||||
this.toolsService.saveSettings(settings);
|
||||
}
|
||||
|
@ -85,19 +87,19 @@ export class SettingsComponent implements OnInit {
|
|||
return false;
|
||||
}
|
||||
|
||||
onDisableAutofocusChanged(){
|
||||
onDisableAutofocusChanged() {
|
||||
let settings = this.toolsService.getSettings();
|
||||
settings.disableAutofocus = this.disableAutofocusEnabled;
|
||||
this.toolsService.saveSettings(settings);
|
||||
this.toolsService.saveSettings(settings);
|
||||
}
|
||||
|
||||
onDisableAvatarNotificationsChanged(){
|
||||
onDisableAvatarNotificationsChanged() {
|
||||
let settings = this.toolsService.getSettings();
|
||||
settings.disableAvatarNotifications = this.disableAvatarNotificationsEnabled;
|
||||
this.toolsService.saveSettings(settings);
|
||||
}
|
||||
|
||||
onDisableSoundsEnabledChanged(){
|
||||
onDisableSoundsEnabledChanged() {
|
||||
let settings = this.toolsService.getSettings();
|
||||
settings.disableSounds = this.disableSoundsEnabled;
|
||||
this.toolsService.saveSettings(settings);
|
||||
|
@ -109,7 +111,7 @@ export class SettingsComponent implements OnInit {
|
|||
return false;
|
||||
}
|
||||
|
||||
confirmClearAll(): boolean{
|
||||
confirmClearAll(): boolean {
|
||||
localStorage.clear();
|
||||
location.reload();
|
||||
return false;
|
||||
|
@ -119,10 +121,23 @@ export class SettingsComponent implements OnInit {
|
|||
this.isCleanningAll = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
isCheckingUpdates = false;
|
||||
checkForUpdates(): boolean {
|
||||
this.isCheckingUpdates = true;
|
||||
this.serviceWorkersService.checkForUpdates()
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
})
|
||||
.then(() => {
|
||||
this.isCheckingUpdates = false;
|
||||
});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
enum ColumnShortcut {
|
||||
Ctrl = 1,
|
||||
Ctrl = 1,
|
||||
Win = 2
|
||||
}
|
|
@ -1,124 +1,124 @@
|
|||
import { Component, OnInit, Input } from "@angular/core";
|
||||
import { Store, Select } from '@ngxs/store';
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { HttpErrorResponse } from "@angular/common/http";
|
||||
// import { Component, OnInit, Input } from "@angular/core";
|
||||
// import { Store, Select } from '@ngxs/store';
|
||||
// import { ActivatedRoute, Router } from "@angular/router";
|
||||
// import { HttpErrorResponse } from "@angular/common/http";
|
||||
|
||||
import { AuthService, CurrentAuthProcess } from "../../services/auth.service";
|
||||
import { TokenData, Account } from "../../services/models/mastodon.interfaces";
|
||||
import { RegisteredAppsStateModel, AppInfo } from "../../states/registered-apps.state";
|
||||
import { AccountInfo, AddAccount, AccountsStateModel } from "../../states/accounts.state";
|
||||
import { NotificationService } from "../../services/notification.service";
|
||||
import { MastodonWrapperService } from '../../services/mastodon-wrapper.service';
|
||||
// import { AuthService, CurrentAuthProcess } from "../../services/auth.service";
|
||||
// import { TokenData, Account } from "../../services/models/mastodon.interfaces";
|
||||
// import { RegisteredAppsStateModel, AppInfo } from "../../states/registered-apps.state";
|
||||
// import { AccountInfo, AddAccount, AccountsStateModel } from "../../states/accounts.state";
|
||||
// import { NotificationService } from "../../services/notification.service";
|
||||
// import { MastodonWrapperService } from '../../services/mastodon-wrapper.service';
|
||||
|
||||
@Component({
|
||||
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;
|
||||
// @Component({
|
||||
// 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;
|
||||
|
||||
hasError: boolean;
|
||||
errorMessage: string;
|
||||
// hasError: boolean;
|
||||
// errorMessage: string;
|
||||
|
||||
private authStorageKey: string = 'tempAuth';
|
||||
// private authStorageKey: string = 'tempAuth';
|
||||
|
||||
constructor(
|
||||
private readonly mastodonService: MastodonWrapperService,
|
||||
private readonly notificationService: NotificationService,
|
||||
private readonly authService: AuthService,
|
||||
private readonly store: Store,
|
||||
private readonly activatedRoute: ActivatedRoute,
|
||||
private readonly router: Router) {
|
||||
// constructor(
|
||||
// private readonly mastodonService: MastodonWrapperService,
|
||||
// private readonly notificationService: NotificationService,
|
||||
// private readonly authService: AuthService,
|
||||
// private readonly store: Store,
|
||||
// private readonly activatedRoute: ActivatedRoute,
|
||||
// private readonly router: Router) {
|
||||
|
||||
this.activatedRoute.queryParams.subscribe(params => {
|
||||
this.hasError = false;
|
||||
// this.activatedRoute.queryParams.subscribe(params => {
|
||||
// this.hasError = false;
|
||||
|
||||
const code = params['code'];
|
||||
if (!code) {
|
||||
this.displayError(RegistrationErrorTypes.CodeNotFound);
|
||||
return;
|
||||
}
|
||||
// const code = params['code'];
|
||||
// if (!code) {
|
||||
// this.displayError(RegistrationErrorTypes.CodeNotFound);
|
||||
// return;
|
||||
// }
|
||||
|
||||
const appDataWrapper = <CurrentAuthProcess>JSON.parse(localStorage.getItem(this.authStorageKey));
|
||||
if (!appDataWrapper) {
|
||||
this.displayError(RegistrationErrorTypes.AuthProcessNotFound);
|
||||
return;
|
||||
}
|
||||
// const appDataWrapper = <CurrentAuthProcess>JSON.parse(localStorage.getItem(this.authStorageKey));
|
||||
// if (!appDataWrapper) {
|
||||
// this.displayError(RegistrationErrorTypes.AuthProcessNotFound);
|
||||
// return;
|
||||
// }
|
||||
|
||||
const appInfo = this.getAllSavedApps().filter(x => x.instance === appDataWrapper.instance)[0];
|
||||
let usedTokenData: TokenData;
|
||||
this.authService.getToken(appDataWrapper.instance, appInfo.app.client_id, appInfo.app.client_secret, code, appInfo.app.redirect_uri)
|
||||
.then((tokenData: TokenData) => {
|
||||
// const appInfo = this.getAllSavedApps().filter(x => x.instance === appDataWrapper.instance)[0];
|
||||
// let usedTokenData: TokenData;
|
||||
// this.authService.getToken(appDataWrapper.instance, appInfo.app.client_id, appInfo.app.client_secret, code, appInfo.app.redirect_uri)
|
||||
// .then((tokenData: TokenData) => {
|
||||
|
||||
if(tokenData.refresh_token && !tokenData.created_at){
|
||||
const nowEpoch = Date.now() / 1000 | 0;
|
||||
tokenData.created_at = nowEpoch;
|
||||
}
|
||||
// if(tokenData.refresh_token && !tokenData.created_at){
|
||||
// const nowEpoch = Date.now() / 1000 | 0;
|
||||
// tokenData.created_at = nowEpoch;
|
||||
// }
|
||||
|
||||
usedTokenData = tokenData;
|
||||
// usedTokenData = tokenData;
|
||||
|
||||
return this.mastodonService.retrieveAccountDetails({ 'instance': appDataWrapper.instance, 'id': '', 'username': '', 'order': 0, 'isSelected': true, 'token': tokenData });
|
||||
})
|
||||
.then((account: Account) => {
|
||||
var username = account.username.toLowerCase();
|
||||
var instance = appDataWrapper.instance.toLowerCase();
|
||||
// return this.mastodonService.retrieveAccountDetails({ 'instance': appDataWrapper.instance, 'id': '', 'username': '', 'order': 0, 'isSelected': true, 'token': tokenData });
|
||||
// })
|
||||
// .then((account: Account) => {
|
||||
// var username = account.username.toLowerCase();
|
||||
// var instance = appDataWrapper.instance.toLowerCase();
|
||||
|
||||
if(this.isAccountAlreadyPresent(username, instance)){
|
||||
this.notificationService.notify(null, null, `Account @${username}@${instance} is already registered`, true);
|
||||
this.router.navigate(['/home']);
|
||||
return;
|
||||
}
|
||||
// if(this.isAccountAlreadyPresent(username, instance)){
|
||||
// this.notificationService.notify(null, null, `Account @${username}@${instance} is already registered`, true);
|
||||
// this.router.navigate(['/home']);
|
||||
// return;
|
||||
// }
|
||||
|
||||
const accountInfo = new AccountInfo();
|
||||
accountInfo.username = username;
|
||||
accountInfo.instance = instance;
|
||||
accountInfo.token = usedTokenData;
|
||||
// const accountInfo = new AccountInfo();
|
||||
// accountInfo.username = username;
|
||||
// accountInfo.instance = instance;
|
||||
// accountInfo.token = usedTokenData;
|
||||
|
||||
this.store.dispatch([new AddAccount(accountInfo)])
|
||||
.subscribe(() => {
|
||||
localStorage.removeItem(this.authStorageKey);
|
||||
this.router.navigate(['/home']);
|
||||
});
|
||||
})
|
||||
.catch((err: HttpErrorResponse) => {
|
||||
this.notificationService.notifyHttpError(err, null);
|
||||
});
|
||||
});
|
||||
}
|
||||
// this.store.dispatch([new AddAccount(accountInfo)])
|
||||
// .subscribe(() => {
|
||||
// localStorage.removeItem(this.authStorageKey);
|
||||
// this.router.navigate(['/home']);
|
||||
// });
|
||||
// })
|
||||
// .catch((err: HttpErrorResponse) => {
|
||||
// this.notificationService.notifyHttpError(err, null);
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
// ngOnInit() {
|
||||
// }
|
||||
|
||||
private isAccountAlreadyPresent(username: string, instance: string): boolean{
|
||||
const accounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts;
|
||||
for (let acc of accounts) {
|
||||
if(acc.instance === instance && acc.username == username){
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
// private isAccountAlreadyPresent(username: string, instance: string): boolean{
|
||||
// const accounts = <AccountInfo[]>this.store.snapshot().registeredaccounts.accounts;
|
||||
// for (let acc of accounts) {
|
||||
// if(acc.instance === instance && acc.username == username){
|
||||
// return true;
|
||||
// }
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
|
||||
private displayError(type: RegistrationErrorTypes) {
|
||||
this.hasError = true;
|
||||
switch (type) {
|
||||
case RegistrationErrorTypes.AuthProcessNotFound:
|
||||
this.errorMessage = 'Something when wrong in the authentication process. Please retry.'
|
||||
break;
|
||||
case RegistrationErrorTypes.CodeNotFound:
|
||||
this.errorMessage = 'No authentication code returned. Please retry.'
|
||||
break;
|
||||
}
|
||||
}
|
||||
// private displayError(type: RegistrationErrorTypes) {
|
||||
// this.hasError = true;
|
||||
// switch (type) {
|
||||
// case RegistrationErrorTypes.AuthProcessNotFound:
|
||||
// this.errorMessage = 'Something when wrong in the authentication process. Please retry.'
|
||||
// break;
|
||||
// case RegistrationErrorTypes.CodeNotFound:
|
||||
// this.errorMessage = 'No authentication code returned. Please retry.'
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
private getAllSavedApps(): AppInfo[] {
|
||||
const snapshot = <RegisteredAppsStateModel>this.store.snapshot().registeredapps;
|
||||
return snapshot.apps;
|
||||
}
|
||||
}
|
||||
// private getAllSavedApps(): AppInfo[] {
|
||||
// const snapshot = <RegisteredAppsStateModel>this.store.snapshot().registeredapps;
|
||||
// return snapshot.apps;
|
||||
// }
|
||||
// }
|
||||
|
||||
enum RegistrationErrorTypes {
|
||||
CodeNotFound,
|
||||
AuthProcessNotFound
|
||||
}
|
||||
// enum RegistrationErrorTypes {
|
||||
// CodeNotFound,
|
||||
// AuthProcessNotFound
|
||||
// }
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Component, OnInit, OnDestroy, QueryList, ViewChildren, ElementRef } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { Observable, Subscription } from "rxjs";
|
||||
import { Select } from "@ngxs/store";
|
||||
import scrollIntoView from "smooth-scroll-into-view-if-needed";
|
||||
|
@ -20,20 +19,10 @@ export class StreamsMainDisplayComponent implements OnInit, OnDestroy {
|
|||
private columnSelectedSub: Subscription;
|
||||
|
||||
constructor(
|
||||
private readonly router: Router,
|
||||
private readonly activatedRoute: ActivatedRoute,
|
||||
private readonly navigationService: NavigationService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this.activatedRoute.queryParams.subscribe(params => {
|
||||
const code = params['code'];
|
||||
if (code) {
|
||||
this.router.navigate(['/register'], { queryParams: { code: code} });
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
this.columnSelectedSub = this.navigationService.columnSelectedSubject.subscribe((columnIndex: number) => {
|
||||
this.focusOnColumn(columnIndex);
|
||||
});
|
||||
|
@ -55,10 +44,6 @@ export class StreamsMainDisplayComponent implements OnInit, OnDestroy {
|
|||
.then(() => {
|
||||
this.streamComponents.toArray()[columnIndex].focus();
|
||||
});
|
||||
|
||||
// setTimeout(() => {
|
||||
// this.streamComponents.toArray()[columnIndex].focus();
|
||||
// }, 500);
|
||||
}, 0);
|
||||
|
||||
|
||||
|
|
|
@ -22,6 +22,9 @@ export class MastodonWrapperService {
|
|||
let isExpired = false;
|
||||
let storedAccountInfo = this.getStoreAccountInfo(accountInfo.id);
|
||||
|
||||
if(!storedAccountInfo || !(storedAccountInfo.token))
|
||||
return Promise.resolve(accountInfo);
|
||||
|
||||
try {
|
||||
if (storedAccountInfo.token.refresh_token) {
|
||||
if (!storedAccountInfo.token.created_at || !storedAccountInfo.token.expires_in) {
|
||||
|
@ -31,14 +34,12 @@ export class MastodonWrapperService {
|
|||
|
||||
//Pleroma workaround
|
||||
let expire_in = storedAccountInfo.token.expires_in;
|
||||
if(expire_in < 3600) {
|
||||
if (expire_in < 3600) {
|
||||
expire_in = 3600;
|
||||
}
|
||||
|
||||
let expire_on = expire_in + storedAccountInfo.token.created_at;
|
||||
isExpired = expire_on - nowEpoch <= 60 * 2;
|
||||
|
||||
//console.warn(`expiring in ${Math.round((expire_on - nowEpoch)/24/60/60)}days ${Math.round((expire_on - nowEpoch)/60/60)}h ${Math.round((expire_on - nowEpoch)/60)} mins`);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
|
@ -46,10 +47,8 @@ export class MastodonWrapperService {
|
|||
}
|
||||
|
||||
if (storedAccountInfo.token.refresh_token && isExpired) {
|
||||
console.warn('--------------------------');
|
||||
console.warn('-------->> MARTY!! -------');
|
||||
console.warn('-------->> RENEW TOKEN FFS');
|
||||
console.warn('--------------------------');
|
||||
console.log('>>> MARTY!! ------------');
|
||||
console.log('>>> RENEW TOKEN FFS ----');
|
||||
|
||||
const app = this.getAllSavedApps().find(x => x.instance === storedAccountInfo.instance);
|
||||
return this.authService.refreshToken(storedAccountInfo.instance, app.app.client_id, app.app.client_secret, storedAccountInfo.token.refresh_token)
|
||||
|
@ -87,7 +86,10 @@ export class MastodonWrapperService {
|
|||
}
|
||||
|
||||
retrieveAccountDetails(account: AccountInfo): Promise<Account> {
|
||||
return this.mastodonService.retrieveAccountDetails(account);
|
||||
return this.refreshAccountIfNeeded(account)
|
||||
.then((refreshedAccount: AccountInfo) => {
|
||||
return this.mastodonService.retrieveAccountDetails(refreshedAccount);
|
||||
});
|
||||
}
|
||||
|
||||
getTimeline(account: AccountInfo, type: StreamTypeEnum, max_id: string = null, since_id: string = null, limit: number = 20, tag: string = null, listId: string = null): Promise<Status[]> {
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ServiceWorkerService } from './service-worker.service';
|
||||
|
||||
xdescribe('ServiceWorkerService', () => {
|
||||
beforeEach(() => TestBed.configureTestingModule({}));
|
||||
|
||||
it('should be created', () => {
|
||||
const service: ServiceWorkerService = TestBed.get(ServiceWorkerService);
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,52 @@
|
|||
import { Injectable, ApplicationRef } from '@angular/core';
|
||||
import { SwUpdate } from '@angular/service-worker';
|
||||
import { first } from 'rxjs/operators';
|
||||
import { interval, concat, BehaviorSubject } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ServiceWorkerService {
|
||||
newAppVersionIsAvailable = new BehaviorSubject<boolean>(false);
|
||||
|
||||
private isListening = false;
|
||||
|
||||
constructor(appRef: ApplicationRef, private updates: SwUpdate) {
|
||||
|
||||
//https://angular.io/guide/service-worker-communications
|
||||
|
||||
updates.available.subscribe(event => {
|
||||
console.log('current version is', event.current);
|
||||
console.log('available version is', event.available);
|
||||
|
||||
this.newAppVersionIsAvailable.next(true);
|
||||
});
|
||||
|
||||
// Allow the app to stabilize first, before starting polling for updates with `interval()`.
|
||||
// const updateCheckTimer$ = interval(10 * 1000);
|
||||
// const appIsStable$ = appRef.isStable; //.pipe(first(isStable => isStable === true));
|
||||
// const everySixHoursOnceAppIsStable$ = concat(appIsStable$, updateCheckTimer$);
|
||||
// everySixHoursOnceAppIsStable$.subscribe(() => {
|
||||
// updates.checkForUpdate();
|
||||
// });
|
||||
|
||||
const updateCheckTimer$ = interval(6 * 60 * 60 * 1000);
|
||||
|
||||
appRef.isStable.subscribe(() => {
|
||||
if (this.isListening) return;
|
||||
this.isListening = true;
|
||||
|
||||
updateCheckTimer$.subscribe(() => {
|
||||
updates.checkForUpdate();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
loadNewAppVersion() {
|
||||
document.location.reload();
|
||||
}
|
||||
|
||||
checkForUpdates(): Promise<void> {
|
||||
return this.updates.checkForUpdate();
|
||||
}
|
||||
}
|
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 792 B |
After Width: | Height: | Size: 958 B |
|
@ -0,0 +1,48 @@
|
|||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
src: url('assets/fonts/Regular/Roboto-Regular.woff2') format('woff2'),
|
||||
url('assets/fonts/Regular/Roboto-Regular.woff') format('woff');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
src: url('assets/fonts/Medium/Roboto-Medium.woff2') format('woff2'),
|
||||
url('assets/fonts/Medium/Roboto-Medium.woff') format('woff');
|
||||
font-weight: 500;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
src: url('assets/fonts/Black/Roboto-Black.woff2') format('woff2'),
|
||||
url('assets/fonts/Black/Roboto-Black.woff') format('woff');
|
||||
font-weight: 900;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
src: url('assets/fonts/Thin/Roboto-Thin.woff2') format('woff2'),
|
||||
url('assets/fonts/Thin/Roboto-Thin.woff') format('woff');
|
||||
font-weight: 100;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
src: url('assets/fonts/Light/Roboto-Light.woff2') format('woff2'),
|
||||
url('assets/fonts/Light/Roboto-Light.woff') format('woff');
|
||||
font-weight: 300;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Roboto';
|
||||
src: url('assets/fonts/Bold/Roboto-Bold.woff2') format('woff2'),
|
||||
url('assets/fonts/Bold/Roboto-Bold.woff') format('woff');
|
||||
font-weight: bold;
|
||||
font-style: normal;
|
||||
}
|
||||
|
|
@ -8,8 +8,6 @@
|
|||
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
|
||||
<!-- <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> -->
|
||||
|
||||
<style>
|
||||
.lds-ripple {
|
||||
|
@ -50,6 +48,8 @@
|
|||
}
|
||||
}
|
||||
</style>
|
||||
<link rel="manifest" href="manifest.json">
|
||||
<meta name="theme-color" content="#1976d2">
|
||||
</head>
|
||||
|
||||
<body ondragstart="return false;" ondrop="return false;">
|
||||
|
@ -59,5 +59,6 @@
|
|||
<div></div>
|
||||
</div>
|
||||
</app-root>
|
||||
<noscript>Please enable JavaScript to continue using this application.</noscript>
|
||||
</body>
|
||||
</html>
|
|
@ -5,8 +5,13 @@ import { AppModule } from './app/app.module';
|
|||
import { environment } from './environments/environment';
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
enableProdMode();
|
||||
}
|
||||
|
||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||
.catch(err => console.log(err));
|
||||
.then(() => {
|
||||
if ('serviceWorker' in navigator && environment.production) {
|
||||
navigator.serviceWorker.register('./ngsw-worker.js');
|
||||
}
|
||||
})
|
||||
.catch(err => console.log(err));
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"name": "sengi",
|
||||
"short_name": "sengi",
|
||||
"theme_color": "#1976d2",
|
||||
"background_color": "#fafafa",
|
||||
"display": "standalone",
|
||||
"scope": "/",
|
||||
"start_url": "/",
|
||||
"icons": [
|
||||
{
|
||||
"src": "assets/icons/icon-72x72.png",
|
||||
"sizes": "72x72",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "assets/icons/icon-96x96.png",
|
||||
"sizes": "96x96",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "assets/icons/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "assets/icons/icon-144x144.png",
|
||||
"sizes": "144x144",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "assets/icons/icon-152x152.png",
|
||||
"sizes": "152x152",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "assets/icons/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "assets/icons/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "assets/icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"index": "/index.html",
|
||||
"assetGroups": [
|
||||
{
|
||||
"name": "app",
|
||||
"installMode": "prefetch",
|
||||
"resources": {
|
||||
"files": [
|
||||
"/favicon.ico",
|
||||
"/index.html",
|
||||
"/*.css",
|
||||
"/*.js"
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"name": "assets",
|
||||
"installMode": "lazy",
|
||||
"updateMode": "prefetch",
|
||||
"resources": {
|
||||
"files": [
|
||||
"/assets/**",
|
||||
"/*.(eot|svg|cur|jpg|png|webp|gif|otf|ttf|woff|woff2|ani)"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -4,6 +4,8 @@
|
|||
@import "~bootstrap/scss/bootstrap.scss";
|
||||
@import "~ng-pick-datetime/assets/style/picker.min.css";
|
||||
|
||||
@import 'assets/scss/modules/_fonts.scss';
|
||||
|
||||
*,
|
||||
*::after,
|
||||
*::before {
|
||||
|
|