Skip to content

Commit

Permalink
quick dialog now provides specs to app installer
Browse files Browse the repository at this point in the history
  • Loading branch information
iJungleboy committed Dec 5, 2022
1 parent 8049bb2 commit 7582894
Show file tree
Hide file tree
Showing 10 changed files with 173 additions and 54 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "2sxc-ui",
"version": "14.12.04",
"version": "15.00.00",
"description": "2sxc UI - the JS UI of 2sxc",
"scripts": {
"release-all": "npm run js2sxc && npm run inpage && npm run snippets && npm run quickdialog && npm run turn-on",
Expand Down
3 changes: 2 additions & 1 deletion projects/quick-dialog/src/app/core/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ export const Constants = {
//#region WebApi Endpoints used: 2sxc
webApiDialogContext: 'admin/dialog/settings',
webApiInstallPackage: 'sys/install/RemotePackage',
webApiRemoteInstaller: 'sys/install/RemoteWizardUrl',
// webApiRemoteInstaller: 'sys/install/RemoteWizardUrl',
webApiInstallSettings: 'sys/install/InstallSettings',
webApiGetTemplates: 'cms/block/Templates',
webApiGetTypes: 'cms/block/ContentTypes',
webApiGetApps: 'cms/block/Apps',
Expand Down
4 changes: 2 additions & 2 deletions projects/quick-dialog/src/app/core/core.module.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { HttpClientModule } from '@angular/common/http';
import { GettingStartedService } from 'app/installer/getting-started.service';
import { AppInstallSettingsService } from 'app/installer/getting-started.service';
import { PickerService } from 'app/template-picker/picker.service';
import { CurrentDataService } from 'app/template-picker/current-data.service';

Expand All @@ -12,7 +12,7 @@ import { CurrentDataService } from 'app/template-picker/current-data.service';
],
declarations: [],
providers: [
GettingStartedService,
AppInstallSettingsService,
PickerService,
CurrentDataService,
]
Expand Down
18 changes: 8 additions & 10 deletions projects/quick-dialog/src/app/installer/getting-started.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,24 @@ import { HttpClient } from '@angular/common/http';
import { Subject, Observable } from 'rxjs';
import { log } from 'app/core/log';
import { Constants } from 'app/core/constants';
import { InstallSettings } from './installer-models';

@Injectable()
export class GettingStartedService {
gettingStarted$: Observable<string>;
ready$ = new Observable<boolean>();
export class AppInstallSettingsService {

private gettingStartedSubject: Subject<string> = new Subject<string>();
private installSettingsSubject: Subject<InstallSettings> = new Subject<InstallSettings>();
settings$: Observable<InstallSettings> = this.installSettingsSubject.asObservable();

constructor(private http: HttpClient) {
this.gettingStarted$ = this.gettingStartedSubject.asObservable();
this.ready$ = this.gettingStarted$.pipe(
const ready$ = this.settings$.pipe(
map(() => true),
startWith(false));

this.ready$.pipe(tap(r => log.add(`ready getting started:${r}`))).subscribe();
ready$.pipe(tap(r => log.add(`ready getting started:${r}`))).subscribe();
}

public loadGettingStarted(isContentApp: boolean): void {
this.http.get<string>(`${Constants.webApiRemoteInstaller}?isContentApp=${isContentApp}`)
.subscribe(json => this.gettingStartedSubject.next(json));
this.http.get<InstallSettings>(`${Constants.webApiInstallSettings}?isContentApp=${isContentApp}`)
.subscribe(json => this.installSettingsSubject.next(json));
}

}
23 changes: 23 additions & 0 deletions projects/quick-dialog/src/app/installer/installer-models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export interface InstallSpecs {
installedApps?: InstalledApp[];
rules?: InstallRule[];
}

export interface InstallSettings extends InstallSpecs {
remoteUrl: string;
// installedApps?: InstalledApp[];
// rules?: InstallRule[];
}

export interface InstalledApp {
name: string;
version: string;
guid: string;
}

export interface InstallRule {
target: string;
mode: string;
appGuid: string;
url: string;
}
11 changes: 10 additions & 1 deletion projects/quick-dialog/src/app/installer/installer.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,14 @@
<span>Installing {{ currentPackage?.displayName }}..</span>
</div>
<div *ngIf="ready">
<iframe class="fr-getting-started" id="frGettingStarted" [src]="remoteInstallerUrl" width="100%" height="300px"></iframe>
<iframe #installerWindow class="fr-getting-started" id="frGettingStarted" [src]="remoteInstallerUrl" width="100%" height="300px"></iframe>
</div>
<div *ngIf="showDebug">
Debug:
<button type="button" (click)="sendMessage('test')">Send Test message</button>
-
Simulate Install: {{devSimulateInstall}}
<button type="button" (click)="toggleSimulate()">toggle</button>
-
<button type="button" (click)="sendMessage('specs')">Send 'specs' message</button>
</div>
133 changes: 101 additions & 32 deletions projects/quick-dialog/src/app/installer/installer.component.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@

import { tap, switchMap, map, filter, debounceTime, catchError } from 'rxjs/operators';
import { Component, OnInit, Input } from '@angular/core';
import { tap, switchMap, map, filter, debounceTime, catchError, take } from 'rxjs/operators';
import { Component, OnInit, Input, ViewChild, ElementRef } from '@angular/core';
import { InstallerService } from 'app/installer/installer.service';
import { DomSanitizer } from '@angular/platform-browser';
import { fromEvent, of, Subscription } from 'rxjs';
import { GettingStartedService } from './getting-started.service';
import { AppInstallSettingsService } from './getting-started.service';
import { Config } from '../config';

declare const $2sxc: any;
import { CrossWindowMessage, InstallPackage, SpecsForInstaller } from './messages';
import { DebugConfig } from 'app/debug-config';
import { IDialogFrameElement } from '../../../../connect-parts/inpage-quick-dialog';
import { InstallSettings } from './installer-models';

@Component({
selector: 'app-installer',
Expand All @@ -17,65 +19,113 @@ declare const $2sxc: any;
export class InstallerComponent implements OnInit {
@Input() isContentApp: boolean;

@ViewChild('installerWindow') installerWindow: ElementRef;

showProgress: boolean;
currentPackage: any;
currentPackage: InstallPackage;
remoteInstallerUrl = '';
settings: InstallSettings;
ready = false;

private subscriptions: Subscription[] = [];

showDebug = DebugConfig.picker.showDebugPanel;

devSimulateInstall = false;

constructor(
private installer: InstallerService,
private api: GettingStartedService,
private api: AppInstallSettingsService,
private sanitizer: DomSanitizer,
) {
this.subscriptions.push(
this.api.gettingStarted$.subscribe(url => {
this.remoteInstallerUrl = <string>this.sanitizer.bypassSecurityTrustResourceUrl(url);
this.api.settings$.subscribe(settings => {
this.settings = settings;
this.remoteInstallerUrl = <string>this.sanitizer.bypassSecurityTrustResourceUrl(settings.remoteUrl);
this.ready = true;
}));

// get configuration from iframe-bridge and set everything
const bridge = (<IDialogFrameElement>window.frameElement).bridge;
const dashInfo = bridge.getAdditionalDashboardConfig();
this.showDebug = dashInfo.debug;

window.bootController.rebootRequest$.pipe(
debounceTime(1000))
.subscribe(() => this.destroy());
}

destroy(): void {
this.subscriptions
.forEach(sub => sub.unsubscribe());
this.subscriptions.forEach(sub => sub.unsubscribe());
console.log('destroy subs', this.subscriptions);
}

private alreadyProcessing = false;

// Initial Observable to monitor messages
private messages$ = fromEvent(window, 'message').pipe(

// Ensure only one installation is processed.
filter(() => !this.alreadyProcessing),

// Get data from event.
map((evt: MessageEvent) => {
try {
return JSON.parse(evt.data) as CrossWindowMessage;
} catch (e) {
console.error('error procesing message. Message was ' + evt.data, e);
return void 0;
}
}),

// Check if data is valid and the moduleID matches
filter(data => data && Number(data.moduleId) === Config.moduleId()),
);

ngOnInit() {
let alreadyProcessing = false;
this.api.loadGettingStarted(this.isContentApp);

this.subscriptions.push(fromEvent(window, 'message').pipe(
// Subscription to listen to 'test' messages
this.subscriptions.push(this.messages$.pipe(
tap((data) => {
console.log('debug data', data);
}),
filter(data => data.action === 'test'),
tap(() => { console.log('test message received'); }),
).subscribe());

// Ensure only one installation is processed.
filter(() => !alreadyProcessing),
// Subscription to listen to 'specs' messages
this.subscriptions.push(this.messages$.pipe(
// Verify it's for this action
filter(data => data.action === 'specs'),

// Get data from event.
map((evt: MessageEvent) => {
try {
return JSON.parse(evt.data);
} catch (e) {
return void 0;
}
// Send message to iframe
tap(() => {
const winFrame = this.installerWindow.nativeElement as HTMLIFrameElement;
const specsMsg: SpecsForInstaller = {
action: 'specs',
data: {
installedApps: this.settings.installedApps,
rules: this.settings.rules,
},
};
const specsJson = JSON.stringify(specsMsg);
winFrame.contentWindow.postMessage(specsJson);
console.log('debug: just sent specs message:' + specsJson, specsMsg, winFrame);
}),
).subscribe());

// Check if data is correct.
filter(data => data
&& Number(data.moduleId) === Config.moduleId()
&& data.action === 'install'),
// Subscription to listen to 'install' messages
this.subscriptions.push(this.messages$.pipe(
filter(data => data.action === 'install'),

// Get packages from data.
map(data => Object.values(data.packages)),

// Show confirm dialog.
filter(packages => {
const packagesDisplayNames = packages
.reduce((t, c) => `${t} - ${(c as any).displayName}\n`, '');
.reduce((t, c) => `${t} - ${c.displayName}\n`, '');

const msg = `Do you want to install these packages?
Expand All @@ -84,23 +134,34 @@ This takes about 10 seconds per package. Don't reload the page while it's instal
return confirm(msg);
}),

// Install the packages one at a time
switchMap(packages => {
alreadyProcessing = true;
this.alreadyProcessing = true;
this.showProgress = true;
return this.installer.installPackages(packages, p => this.currentPackage = p);
if (this.devSimulateInstall) {
alert('would install packages now, see list in console');
console.log('packages', packages);
return of(true);
} else
return this.installer.installPackages(packages, p => this.currentPackage = p);
}),

tap(() => {
this.showProgress = false;
alert('Installation complete 👍');
window.top.location.reload();
if (this.devSimulateInstall)
console.log(`would reload now, but won't, as we're just simulating.`);
else
window.top.location.reload();
}),

catchError(error => {
console.error('Error: ', error);
this.showProgress = false;
alreadyProcessing = false;
var errorMsg = `An error occurred: ${error.error?.Message ?? error.error?.message ?? ''}
this.alreadyProcessing = false;
const errorMsg = `An error occurred: Package ${this.currentPackage.displayName}
${error.error?.Message ?? error.error?.message ?? ''}
${error.message}
Expand All @@ -110,4 +171,12 @@ Please try again later or check how to manually install content-templates: https
}),
).subscribe());
}

sendMessage(message: string) {
window.postMessage(JSON.stringify({ action: message, moduleId: Config.moduleId() } as CrossWindowMessage));
}

toggleSimulate() {
this.devSimulateInstall = !this.devSimulateInstall;
}
}
15 changes: 9 additions & 6 deletions projects/quick-dialog/src/app/installer/installer.service.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@

import {of as observableOf } from 'rxjs';
import { of } from 'rxjs';

import {switchMap} from 'rxjs/operators';
import { switchMap } from 'rxjs/operators';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { Constants } from 'app/core/constants';
import { InstallPackage } from './messages';

@Injectable()
export class InstallerService {
Expand All @@ -14,12 +15,14 @@ export class InstallerService {
private http: HttpClient
) { }

installPackages(packages: any[], step: Function): Observable<any> {
return packages.reduce((t: Observable<Response>, c) => t.pipe(
installPackages(packages: InstallPackage[], step: (p: InstallPackage) => void): Observable<any> {
return packages.reduce(
(t: Observable<Response>, c) => t.pipe(
switchMap(() => {
if (!c.url) return observableOf(true);
if (!c.url) return of(true);
step(c);
return <Observable<any>>this.http.post(`${Constants.webApiInstallPackage}?packageUrl=${c.url}`, {});
})), observableOf(true));
})),
of(true));
}
}
16 changes: 16 additions & 0 deletions projects/quick-dialog/src/app/installer/messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { InstallSpecs } from './installer-models';
export interface CrossWindowMessage {
action: string;
moduleId: string | number; // probably string, must safely convert to Number()
packages: InstallPackage[];
}

export interface InstallPackage {
displayName: string;
url: string;
}

export interface SpecsForInstaller {
action: 'specs';
data: InstallSpecs;
}
2 changes: 1 addition & 1 deletion projects/quick-dialog/src/app/interfaces/shared.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export * from '../../../../connect-parts/inpage-quick-dialog';
export * from '../../../../shared'
export * from '../../../../shared';

0 comments on commit 7582894

Please sign in to comment.