Merge pull request #231 from NicolasConstant/topic_auto-update
Topic auto update
10
angular.json
|
@ -18,7 +18,8 @@
|
||||||
"polyfills": "src/polyfills.ts",
|
"polyfills": "src/polyfills.ts",
|
||||||
"assets": [
|
"assets": [
|
||||||
"src/assets",
|
"src/assets",
|
||||||
"src/favicon.ico"
|
"src/favicon.ico",
|
||||||
|
"src/manifest.json"
|
||||||
],
|
],
|
||||||
"styles": [
|
"styles": [
|
||||||
"src/sass/styles.scss",
|
"src/sass/styles.scss",
|
||||||
|
@ -48,7 +49,9 @@
|
||||||
"replace": "src/environments/environment.ts",
|
"replace": "src/environments/environment.ts",
|
||||||
"with": "src/environments/environment.prod.ts"
|
"with": "src/environments/environment.prod.ts"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"serviceWorker": true,
|
||||||
|
"ngswConfigPath": "src/ngsw-config.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -82,7 +85,8 @@
|
||||||
],
|
],
|
||||||
"assets": [
|
"assets": [
|
||||||
"src/assets",
|
"src/assets",
|
||||||
"src/favicon.ico"
|
"src/favicon.ico",
|
||||||
|
"src/manifest.json"
|
||||||
],
|
],
|
||||||
"stylePreprocessorOptions": {
|
"stylePreprocessorOptions": {
|
||||||
"includePaths": [
|
"includePaths": [
|
||||||
|
|
|
@ -36,7 +36,9 @@
|
||||||
"@angular/http": "^7.2.7",
|
"@angular/http": "^7.2.7",
|
||||||
"@angular/platform-browser": "^7.2.7",
|
"@angular/platform-browser": "^7.2.7",
|
||||||
"@angular/platform-browser-dynamic": "^7.2.7",
|
"@angular/platform-browser-dynamic": "^7.2.7",
|
||||||
|
"@angular/pwa": "^0.12.4",
|
||||||
"@angular/router": "^7.2.7",
|
"@angular/router": "^7.2.7",
|
||||||
|
"@angular/service-worker": "^7.2.7",
|
||||||
"@ctrl/ngx-emoji-mart": "^0.17.0",
|
"@ctrl/ngx-emoji-mart": "^0.17.0",
|
||||||
"@fortawesome/angular-fontawesome": "^0.3.0",
|
"@fortawesome/angular-fontawesome": "^0.3.0",
|
||||||
"@fortawesome/fontawesome-svg-core": "^1.2.13",
|
"@fortawesome/fontawesome-svg-core": "^1.2.13",
|
||||||
|
@ -89,6 +91,7 @@
|
||||||
"productName": "Sengi",
|
"productName": "Sengi",
|
||||||
"appId": "org.sengi.desktop",
|
"appId": "org.sengi.desktop",
|
||||||
"artifactName": "${productName}-${version}-${os}.${ext}",
|
"artifactName": "${productName}-${version}-${os}.${ext}",
|
||||||
|
"npmRebuild": false,
|
||||||
"directories": {
|
"directories": {
|
||||||
"output": "release"
|
"output": "release"
|
||||||
},
|
},
|
||||||
|
|
|
@ -10,6 +10,12 @@
|
||||||
</div>
|
</div>
|
||||||
</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"
|
<app-media-viewer id="media-viewer" *ngIf="openedMediaEvent" [openedMediaEvent]="openedMediaEvent"
|
||||||
(closeSubject)="closeMedia()" (dragenter)="dragenter($event)"></app-media-viewer>
|
(closeSubject)="closeMedia()" (dragenter)="dragenter($event)"></app-media-viewer>
|
||||||
|
|
||||||
|
|
|
@ -98,3 +98,74 @@ app-streams-selection-footer {
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
left: 50px;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||||
import { Subscription, Observable, Subject } from 'rxjs';
|
import { Subscription, Observable, Subject } from 'rxjs';
|
||||||
import { debounceTime, map } from 'rxjs/operators';
|
import { debounceTime, map } from 'rxjs/operators';
|
||||||
import { Select } from '@ngxs/store';
|
import { Select } from '@ngxs/store';
|
||||||
|
import { faTimes } from "@fortawesome/free-solid-svg-icons";
|
||||||
// import { ElectronService } from 'ngx-electron';
|
// import { ElectronService } from 'ngx-electron';
|
||||||
|
|
||||||
import { NavigationService, LeftPanelType, OpenLeftPanelEvent } from './services/navigation.service';
|
import { NavigationService, LeftPanelType, OpenLeftPanelEvent } from './services/navigation.service';
|
||||||
|
@ -9,6 +10,7 @@ import { StreamElement } from './states/streams.state';
|
||||||
import { OpenMediaEvent } from './models/common.model';
|
import { OpenMediaEvent } from './models/common.model';
|
||||||
import { ToolsService } from './services/tools.service';
|
import { ToolsService } from './services/tools.service';
|
||||||
import { MediaService } from './services/media.service';
|
import { MediaService } from './services/media.service';
|
||||||
|
import { ServiceWorkerService } from './services/service-worker.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
|
@ -16,26 +18,33 @@ import { MediaService } from './services/media.service';
|
||||||
styleUrls: ['./app.component.scss']
|
styleUrls: ['./app.component.scss']
|
||||||
})
|
})
|
||||||
export class AppComponent implements OnInit, OnDestroy {
|
export class AppComponent implements OnInit, OnDestroy {
|
||||||
|
faTimes = faTimes;
|
||||||
title = 'Sengi';
|
title = 'Sengi';
|
||||||
floatingColumnActive: boolean;
|
floatingColumnActive: boolean;
|
||||||
tutorialActive: boolean;
|
tutorialActive: boolean;
|
||||||
// mediaViewerActive: boolean = false;
|
|
||||||
openedMediaEvent: OpenMediaEvent
|
openedMediaEvent: OpenMediaEvent
|
||||||
|
updateAvailable: boolean;
|
||||||
|
|
||||||
private columnEditorSub: Subscription;
|
private columnEditorSub: Subscription;
|
||||||
private openMediaSub: Subscription;
|
private openMediaSub: Subscription;
|
||||||
private streamSub: Subscription;
|
private streamSub: Subscription;
|
||||||
private dragoverSub: Subscription;
|
private dragoverSub: Subscription;
|
||||||
|
private updateAvailableSub: Subscription;
|
||||||
|
|
||||||
@Select(state => state.streamsstatemodel.streams) streamElements$: Observable<StreamElement[]>;
|
@Select(state => state.streamsstatemodel.streams) streamElements$: Observable<StreamElement[]>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
private readonly serviceWorkerService: ServiceWorkerService,
|
||||||
private readonly toolsService: ToolsService,
|
private readonly toolsService: ToolsService,
|
||||||
private readonly mediaService: MediaService,
|
private readonly mediaService: MediaService,
|
||||||
private readonly navigationService: NavigationService) {
|
private readonly navigationService: NavigationService) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
|
this.updateAvailableSub = this.serviceWorkerService.newAppVersionIsAvailable.subscribe((updateAvailable) => {
|
||||||
|
this.updateAvailable = updateAvailable;
|
||||||
|
});
|
||||||
|
|
||||||
this.streamSub = this.streamElements$.subscribe((streams: StreamElement[]) => {
|
this.streamSub = this.streamElements$.subscribe((streams: StreamElement[]) => {
|
||||||
if (streams && streams.length === 0) {
|
if (streams && streams.length === 0) {
|
||||||
this.tutorialActive = true;
|
this.tutorialActive = true;
|
||||||
|
@ -75,6 +84,7 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||||
this.columnEditorSub.unsubscribe();
|
this.columnEditorSub.unsubscribe();
|
||||||
this.openMediaSub.unsubscribe();
|
this.openMediaSub.unsubscribe();
|
||||||
this.dragoverSub.unsubscribe();
|
this.dragoverSub.unsubscribe();
|
||||||
|
this.updateAvailableSub.unsubscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
closeMedia() {
|
closeMedia() {
|
||||||
|
@ -96,7 +106,6 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
dragover(event): boolean {
|
dragover(event): boolean {
|
||||||
// console.warn('dragover');
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.dragoverSubject.next(true);
|
this.dragoverSubject.next(true);
|
||||||
|
@ -112,4 +121,14 @@ export class AppComponent implements OnInit, OnDestroy {
|
||||||
this.mediaService.uploadMedia(selectedAccount, files);
|
this.mediaService.uploadMedia(selectedAccount, files);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loadNewVersion(): boolean {
|
||||||
|
this.serviceWorkerService.loadNewAppVersion();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeAutoUpdate(): boolean {
|
||||||
|
this.updateAvailable = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,6 +79,8 @@ import { ScheduledStatusesComponent } from './components/floating-column/schedul
|
||||||
import { ScheduledStatusComponent } from './components/floating-column/scheduled-statuses/scheduled-status/scheduled-status.component';
|
import { ScheduledStatusComponent } from './components/floating-column/scheduled-statuses/scheduled-status/scheduled-status.component';
|
||||||
import { StreamNotificationsComponent } from './components/stream/stream-notifications/stream-notifications.component';
|
import { StreamNotificationsComponent } from './components/stream/stream-notifications/stream-notifications.component';
|
||||||
import { NotificationComponent } from './components/floating-column/manage-account/notifications/notification/notification.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 = [
|
const routes: Routes = [
|
||||||
|
@ -167,7 +169,8 @@ const routes: Routes = [
|
||||||
]),
|
]),
|
||||||
NgxsStoragePluginModule.forRoot(),
|
NgxsStoragePluginModule.forRoot(),
|
||||||
ContextMenuModule.forRoot(),
|
ContextMenuModule.forRoot(),
|
||||||
HotkeyModule.forRoot()
|
HotkeyModule.forRoot(),
|
||||||
|
ServiceWorkerModule.register('ngsw-worker.js', { enabled: environment.production })
|
||||||
],
|
],
|
||||||
providers: [AuthService, NavigationService, NotificationService, MastodonWrapperService, MastodonService, StreamingService],
|
providers: [AuthService, NavigationService, NotificationService, MastodonWrapperService, MastodonService, StreamingService],
|
||||||
bootstrap: [AppComponent],
|
bootstrap: [AppComponent],
|
||||||
|
|
|
@ -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,49 @@
|
||||||
|
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, 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();
|
||||||
|
}
|
||||||
|
}
|
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 |
|
@ -50,6 +50,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<link rel="manifest" href="manifest.json">
|
||||||
|
<meta name="theme-color" content="#1976d2">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body ondragstart="return false;" ondrop="return false;">
|
<body ondragstart="return false;" ondrop="return false;">
|
||||||
|
@ -59,5 +61,6 @@
|
||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
</app-root>
|
</app-root>
|
||||||
|
<noscript>Please enable JavaScript to continue using this application.</noscript>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -9,4 +9,9 @@ if (environment.production) {
|
||||||
}
|
}
|
||||||
|
|
||||||
platformBrowserDynamic().bootstrapModule(AppModule)
|
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||||
|
.then(() => {
|
||||||
|
if ('serviceWorker' in navigator && environment.production) {
|
||||||
|
navigator.serviceWorker.register('./ngsw-worker.js');
|
||||||
|
}
|
||||||
|
})
|
||||||
.catch(err => console.log(err));
|
.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)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|