Re-wire the UI to support the new backend

This still doesn't allow editing, but it supports showing the widgets at least.
This commit is contained in:
Travis Ralston 2017-12-20 21:28:43 -07:00
parent 599fb80112
commit 4965b61f2d
40 changed files with 373 additions and 274 deletions

View File

@ -1,4 +1,4 @@
import { GET, Path, QueryParam } from "typescript-rest"; import { GET, Path, PathParam, QueryParam } from "typescript-rest";
import * as Promise from "bluebird"; import * as Promise from "bluebird";
import { ScalarService } from "../scalar/ScalarService"; import { ScalarService } from "../scalar/ScalarService";
import { DimensionStore } from "../../db/DimensionStore"; import { DimensionStore } from "../../db/DimensionStore";
@ -35,6 +35,13 @@ export class DimensionIntegrationsService {
}); });
} }
@GET
@Path("room/:roomId")
public getIntegrationsInRoom(@QueryParam("scalar_token") scalarToken: string, @PathParam("roomId") roomId: string) :Promise<IntegrationsResponse>{
console.log(roomId);
return this.getEnabledIntegrations(scalarToken);
}
private getIntegrations(isEnabledCheck?: boolean): Promise<IntegrationsResponse> { private getIntegrations(isEnabledCheck?: boolean): Promise<IntegrationsResponse> {
const cachedResponse = DimensionIntegrationsService.integrationCache.get("integrations_" + isEnabledCheck); const cachedResponse = DimensionIntegrationsService.integrationCache.get("integrations_" + isEnabledCheck);
if (cachedResponse) { if (cachedResponse) {

View File

@ -12,6 +12,7 @@ export default {
"avatarUrl": {type: DataType.STRING, allowNull: false}, "avatarUrl": {type: DataType.STRING, allowNull: false},
"description": {type: DataType.STRING, allowNull: false}, "description": {type: DataType.STRING, allowNull: false},
"isEnabled": {type: DataType.BOOLEAN, allowNull: false}, "isEnabled": {type: DataType.BOOLEAN, allowNull: false},
"isPublic": {type: DataType.BOOLEAN, allowNull: false},
"optionsJson": {type: DataType.STRING, allowNull: true}, "optionsJson": {type: DataType.STRING, allowNull: true},
})) }))
.then(() => queryInterface.bulkInsert("dimension_widgets", [ .then(() => queryInterface.bulkInsert("dimension_widgets", [
@ -64,6 +65,15 @@ export default {
avatarUrl: "/img/avatars/twitch.png", avatarUrl: "/img/avatars/twitch.png",
description: "Embed a Twitch livestream into your room.", description: "Embed a Twitch livestream into your room.",
}, },
{
type: "jitsi",
name: "Jitsi Conference",
isEnabled: true,
isPublic: true,
avatarUrl: "/img/avatars/jitsi.png",
description: "Hold a video conference with Jitsi Meet",
optionsJson: '{"jitsiDomain":"jitsi.riot.im", "scriptUrl":"https://jitsi.riot.im/libs/external_api.min.js"}',
},
])); ]));
}, },
down: (queryInterface: QueryInterface) => { down: (queryInterface: QueryInterface) => {

View File

@ -5,6 +5,7 @@ export class Integration {
public category: "bot" | "complex-bot" | "bridge" | "widget"; public category: "bot" | "complex-bot" | "bridge" | "widget";
public type: string; public type: string;
public requirements: IntegrationRequirement[]; public requirements: IntegrationRequirement[];
public isEncryptionSupported = false;
// These are meant to be set by us // These are meant to be set by us
public displayName: string; public displayName: string;
@ -26,5 +27,9 @@ export class Integration {
export interface IntegrationRequirement { export interface IntegrationRequirement {
condition: "publicRoom" | "canSendEventTypes"; condition: "publicRoom" | "canSendEventTypes";
argument: any; argument: any;
// For publicRoom this is true or false (boolean)
// For canSendEventTypes this is an array of {isState: boolean, type: string}
// For userInRoom this is the user ID
expectedValue: any; expectedValue: any;
} }

View File

@ -5,6 +5,11 @@ export interface EtherpadWidgetOptions {
defaultUrl: string; defaultUrl: string;
} }
export interface JitsiWidgetOptions {
jitsiDomain: string;
scriptUrl: string;
}
export class Widget extends Integration { export class Widget extends Integration {
public options: any; public options: any;
@ -14,8 +19,11 @@ export class Widget extends Integration {
this.options = widgetRecord.optionsJson ? JSON.parse(widgetRecord.optionsJson) : {}; this.options = widgetRecord.optionsJson ? JSON.parse(widgetRecord.optionsJson) : {};
this.requirements = [{ this.requirements = [{
condition: "canSendEventTypes", condition: "canSendEventTypes",
argument: ["im.vector.widget"], argument: [{isState: true, type: "im.vector.widget"}],
expectedValue: true, expectedValue: true,
}]; }];
// Technically widgets are supported in encrypted rooms, although at risk.
this.isEncryptionSupported = true;
} }
} }

View File

@ -14,6 +14,8 @@ export class MatrixOpenIdClient {
"/_matrix/federation/v1/openid/userinfo", "/_matrix/federation/v1/openid/userinfo",
{access_token: this.openId.access_token} {access_token: this.openId.access_token}
).then(response => { ).then(response => {
// Annoyingly, the response isn't JSON for this
response = JSON.parse(response);
return response['sub']; return response['sub'];
}); });
} }

View File

@ -8,16 +8,14 @@ import { routing } from "./app.routing";
import { createNewHosts, removeNgStyles } from "@angularclass/hmr"; import { createNewHosts, removeNgStyles } from "@angularclass/hmr";
import { NgbModule } from "@ng-bootstrap/ng-bootstrap"; import { NgbModule } from "@ng-bootstrap/ng-bootstrap";
import { RiotComponent } from "./riot/riot.component"; import { RiotComponent } from "./riot/riot.component";
import { ApiService } from "./shared/services/api.service";
import { UiSwitchModule } from "angular2-ui-switch"; import { UiSwitchModule } from "angular2-ui-switch";
import { ScalarService } from "./shared/services/scalar.service"; import { ScalarClientApiService } from "./shared/services/scalar-client-api.service";
import { ToasterModule } from "angular2-toaster"; import { ToasterModule } from "angular2-toaster";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { ScalarCloseComponent } from "./riot/scalar-close/scalar-close.component"; import { ScalarCloseComponent } from "./riot/scalar-close/scalar-close.component";
import { IntegrationService } from "./shared/services/integration.service"; import { LegacyIntegrationService } from "./shared/services/legacy/integration.service";
import { BootstrapModalModule } from "ngx-modialog/plugins/bootstrap"; import { BootstrapModalModule } from "ngx-modialog/plugins/bootstrap";
import { ModalModule } from "ngx-modialog"; import { ModalModule } from "ngx-modialog";
import { IrcApiService } from "./shared/services/irc-api.service";
import { GenericWidgetWrapperComponent } from "./widget_wrappers/generic/generic.component"; import { GenericWidgetWrapperComponent } from "./widget_wrappers/generic/generic.component";
import { ToggleFullscreenDirective } from "./shared/directives/toggle-fullscreen.directive"; import { ToggleFullscreenDirective } from "./shared/directives/toggle-fullscreen.directive";
import { FullscreenButtonComponent } from "./fullscreen-button/fullscreen-button.component"; import { FullscreenButtonComponent } from "./fullscreen-button/fullscreen-button.component";
@ -30,8 +28,11 @@ import { BreadcrumbsModule } from "ng2-breadcrumbs";
import { RiotHomeComponent } from "./riot/riot-home/home.component"; import { RiotHomeComponent } from "./riot/riot-home/home.component";
import { IntegrationBagComponent } from "./integration-bag/integration-bag.component"; import { IntegrationBagComponent } from "./integration-bag/integration-bag.component";
import { IntegrationComponent } from "./integration/integration.component"; import { IntegrationComponent } from "./integration/integration.component";
import { ScalarServerApiService } from "./shared/services/scalar-server-api.service";
import { DimensionApiService } from "./shared/services/dimension-api.service";
import { AdminApiService } from "./shared/services/admin-api.service";
const WIDGET_CONFIGURATION_COMPONENTS: any[] = IntegrationService.getAllConfigComponents(); const WIDGET_CONFIGURATION_COMPONENTS: any[] = LegacyIntegrationService.getAllConfigComponents();
@NgModule({ @NgModule({
imports: [ imports: [
@ -68,10 +69,10 @@ const WIDGET_CONFIGURATION_COMPONENTS: any[] = IntegrationService.getAllConfigCo
// Vendor // Vendor
], ],
providers: [ providers: [
ApiService, ScalarClientApiService,
ScalarService, ScalarServerApiService,
IntegrationService, DimensionApiService,
IrcApiService, AdminApiService,
{provide: Window, useValue: window}, {provide: Window, useValue: window},
// Vendor // Vendor

View File

@ -1,9 +1,9 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { CircleCiIntegration } from "../../shared/models/integration"; import { CircleCiIntegration } from "../../shared/models/legacyintegration";
import { ModalComponent, DialogRef } from "ngx-modialog"; import { ModalComponent, DialogRef } from "ngx-modialog";
import { ConfigModalContext } from "../../integration/integration.component"; import { ConfigModalContext } from "../../integration/integration.component";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { ApiService } from "../../shared/services/api.service"; import { ApiService } from "../../shared/services/legacy/api.service";
@Component({ @Component({
selector: "my-circleci-config", selector: "my-circleci-config",

View File

@ -1,11 +1,11 @@
import { Component, OnDestroy } from "@angular/core"; import { Component, OnDestroy } from "@angular/core";
import { IRCIntegration } from "../../shared/models/integration"; import { IRCIntegration } from "../../shared/models/legacyintegration";
import { ModalComponent, DialogRef } from "ngx-modialog"; import { ModalComponent, DialogRef } from "ngx-modialog";
import { ConfigModalContext } from "../../integration/integration.component"; import { ConfigModalContext } from "../../integration/integration.component";
import { IrcApiService } from "../../shared/services/irc-api.service"; import { IrcApiService } from "../../shared/services/legacy/irc-api.service";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { IntervalObservable } from "rxjs/observable/IntervalObservable"; import { IntervalObservable } from "rxjs/observable/IntervalObservable";
import { ApiService } from "../../shared/services/api.service"; import { ApiService } from "../../shared/services/legacy/api.service";
import { Subscription } from "rxjs"; import { Subscription } from "rxjs";
@Component({ @Component({

View File

@ -1,9 +1,9 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { RSSIntegration } from "../../shared/models/integration"; import { RSSIntegration } from "../../shared/models/legacyintegration";
import { ModalComponent, DialogRef } from "ngx-modialog"; import { ModalComponent, DialogRef } from "ngx-modialog";
import { ConfigModalContext } from "../../integration/integration.component"; import { ConfigModalContext } from "../../integration/integration.component";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { ApiService } from "../../shared/services/api.service"; import { ApiService } from "../../shared/services/legacy/api.service";
@Component({ @Component({
selector: "my-rss-config", selector: "my-rss-config",

View File

@ -1,9 +1,9 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { TravisCiIntegration } from "../../shared/models/integration"; import { TravisCiIntegration } from "../../shared/models/legacyintegration";
import { ModalComponent, DialogRef } from "ngx-modialog"; import { ModalComponent, DialogRef } from "ngx-modialog";
import { ConfigModalContext } from "../../integration/integration.component"; import { ConfigModalContext } from "../../integration/integration.component";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { ApiService } from "../../shared/services/api.service"; import { ApiService } from "../../shared/services/legacy/api.service";
@Component({ @Component({
selector: "my-travisci-config", selector: "my-travisci-config",

View File

@ -1,7 +1,7 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { DialogRef, ModalComponent } from "ngx-modialog"; import { DialogRef, ModalComponent } from "ngx-modialog";
import { WidgetComponent } from "../widget.component"; import { WidgetComponent } from "../widget.component";
import { ScalarService } from "../../../shared/services/scalar.service"; import { ScalarClientApiService } from "../../../shared/services/scalar-client-api.service";
import { ConfigModalContext } from "../../../integration/integration.component"; import { ConfigModalContext } from "../../../integration/integration.component";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { WIDGET_CUSTOM } from "../../../shared/models/widget"; import { WIDGET_CUSTOM } from "../../../shared/models/widget";
@ -15,7 +15,7 @@ export class CustomWidgetConfigComponent extends WidgetComponent implements Moda
constructor(public dialog: DialogRef<ConfigModalContext>, constructor(public dialog: DialogRef<ConfigModalContext>,
toaster: ToasterService, toaster: ToasterService,
scalarService: ScalarService, scalarService: ScalarClientApiService,
window: Window) { window: Window) {
super( super(
window, window,

View File

@ -1,11 +1,11 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { DialogRef, ModalComponent } from "ngx-modialog"; import { DialogRef, ModalComponent } from "ngx-modialog";
import { WidgetComponent } from "../widget.component"; import { WidgetComponent } from "../widget.component";
import { ScalarService } from "../../../shared/services/scalar.service"; import { ScalarClientApiService } from "../../../shared/services/scalar-client-api.service";
import { ConfigModalContext } from "../../../integration/integration.component"; import { ConfigModalContext } from "../../../integration/integration.component";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { WIDGET_ETHERPAD } from "../../../shared/models/widget"; import { WIDGET_ETHERPAD } from "../../../shared/models/widget";
import { EtherpadWidgetIntegration } from "../../../shared/models/integration"; import { EtherpadWidgetIntegration } from "../../../shared/models/legacyintegration";
@Component({ @Component({
selector: "my-etherpadwidget-config", selector: "my-etherpadwidget-config",
@ -18,7 +18,7 @@ export class EtherpadWidgetConfigComponent extends WidgetComponent implements Mo
constructor(public dialog: DialogRef<ConfigModalContext>, constructor(public dialog: DialogRef<ConfigModalContext>,
toaster: ToasterService, toaster: ToasterService,
scalarService: ScalarService, scalarService: ScalarClientApiService,
window: Window) { window: Window) {
super( super(
window, window,

View File

@ -1,7 +1,7 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { DialogRef, ModalComponent } from "ngx-modialog"; import { DialogRef, ModalComponent } from "ngx-modialog";
import { WidgetComponent } from "../widget.component"; import { WidgetComponent } from "../widget.component";
import { ScalarService } from "../../../shared/services/scalar.service"; import { ScalarClientApiService } from "../../../shared/services/scalar-client-api.service";
import { ConfigModalContext } from "../../../integration/integration.component"; import { ConfigModalContext } from "../../../integration/integration.component";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { EditableWidget, WIDGET_GOOGLE_CALENDAR } from "../../../shared/models/widget"; import { EditableWidget, WIDGET_GOOGLE_CALENDAR } from "../../../shared/models/widget";
@ -15,7 +15,7 @@ export class GoogleCalendarWidgetConfigComponent extends WidgetComponent impleme
constructor(public dialog: DialogRef<ConfigModalContext>, constructor(public dialog: DialogRef<ConfigModalContext>,
toaster: ToasterService, toaster: ToasterService,
scalarService: ScalarService, scalarService: ScalarClientApiService,
window: Window) { window: Window) {
super( super(
window, window,

View File

@ -1,7 +1,7 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { DialogRef, ModalComponent } from "ngx-modialog"; import { DialogRef, ModalComponent } from "ngx-modialog";
import { WidgetComponent } from "../widget.component"; import { WidgetComponent } from "../widget.component";
import { ScalarService } from "../../../shared/services/scalar.service"; import { ScalarClientApiService } from "../../../shared/services/scalar-client-api.service";
import { ConfigModalContext } from "../../../integration/integration.component"; import { ConfigModalContext } from "../../../integration/integration.component";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { WIDGET_GOOGLE_DOCS } from "../../../shared/models/widget"; import { WIDGET_GOOGLE_DOCS } from "../../../shared/models/widget";
@ -15,7 +15,7 @@ export class GoogleDocsWidgetConfigComponent extends WidgetComponent implements
constructor(public dialog: DialogRef<ConfigModalContext>, constructor(public dialog: DialogRef<ConfigModalContext>,
toaster: ToasterService, toaster: ToasterService,
scalarService: ScalarService, scalarService: ScalarClientApiService,
window: Window) { window: Window) {
super( super(
window, window,

View File

@ -1,11 +1,11 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { DialogRef, ModalComponent } from "ngx-modialog"; import { DialogRef, ModalComponent } from "ngx-modialog";
import { WidgetComponent } from "../widget.component"; import { WidgetComponent } from "../widget.component";
import { ScalarService } from "../../../shared/services/scalar.service"; import { ScalarClientApiService } from "../../../shared/services/scalar-client-api.service";
import { ConfigModalContext } from "../../../integration/integration.component"; import { ConfigModalContext } from "../../../integration/integration.component";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { EditableWidget, WIDGET_JITSI } from "../../../shared/models/widget"; import { EditableWidget, WIDGET_JITSI } from "../../../shared/models/widget";
import { JitsiWidgetIntegration } from "../../../shared/models/integration"; import { JitsiWidgetIntegration } from "../../../shared/models/legacyintegration";
import * as gobyInit from "goby"; import * as gobyInit from "goby";
import * as url from "url"; import * as url from "url";
@ -26,7 +26,7 @@ export class JitsiWidgetConfigComponent extends WidgetComponent implements Modal
constructor(public dialog: DialogRef<ConfigModalContext>, constructor(public dialog: DialogRef<ConfigModalContext>,
toaster: ToasterService, toaster: ToasterService,
scalarService: ScalarService, scalarService: ScalarClientApiService,
window: Window) { window: Window) {
super( super(
window, window,

View File

@ -1,7 +1,7 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { DialogRef, ModalComponent } from "ngx-modialog"; import { DialogRef, ModalComponent } from "ngx-modialog";
import { WidgetComponent } from "../widget.component"; import { WidgetComponent } from "../widget.component";
import { ScalarService } from "../../../shared/services/scalar.service"; import { ScalarClientApiService } from "../../../shared/services/scalar-client-api.service";
import { ConfigModalContext } from "../../../integration/integration.component"; import { ConfigModalContext } from "../../../integration/integration.component";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { EditableWidget, WIDGET_TWITCH } from "../../../shared/models/widget"; import { EditableWidget, WIDGET_TWITCH } from "../../../shared/models/widget";
@ -15,7 +15,7 @@ export class TwitchWidgetConfigComponent extends WidgetComponent implements Moda
constructor(public dialog: DialogRef<ConfigModalContext>, constructor(public dialog: DialogRef<ConfigModalContext>,
toaster: ToasterService, toaster: ToasterService,
scalarService: ScalarService, scalarService: ScalarClientApiService,
window: Window) { window: Window) {
super( super(
window, window,

View File

@ -1,7 +1,7 @@
import { ScalarService } from "../../shared/services/scalar.service"; import { ScalarClientApiService } from "../../shared/services/scalar-client-api.service";
import { convertScalarWidgetsToDtos, EditableWidget } from "../../shared/models/widget"; import { convertScalarWidgetsToDtos, EditableWidget } from "../../shared/models/widget";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { Integration } from "../../shared/models/integration"; import { LegacyIntegration } from "../../shared/models/legacyintegration";
const SCALAR_WIDGET_LINKS = [ const SCALAR_WIDGET_LINKS = [
"https://scalar-staging.riot.im/scalar/api/widgets/__TYPE__.html?url=", "https://scalar-staging.riot.im/scalar/api/widgets/__TYPE__.html?url=",
@ -23,9 +23,9 @@ export class WidgetComponent {
constructor(window: Window, constructor(window: Window,
protected toaster: ToasterService, protected toaster: ToasterService,
protected scalarApi: ScalarService, protected scalarApi: ScalarClientApiService,
public roomId: string, public roomId: string,
public integration: Integration, public integration: LegacyIntegration,
editWidgetId: string, editWidgetId: string,
private widgetIds: string[], private widgetIds: string[],
private defaultName: string, private defaultName: string,

View File

@ -1,7 +1,7 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { DialogRef, ModalComponent } from "ngx-modialog"; import { DialogRef, ModalComponent } from "ngx-modialog";
import { WidgetComponent } from "../widget.component"; import { WidgetComponent } from "../widget.component";
import { ScalarService } from "../../../shared/services/scalar.service"; import { ScalarClientApiService } from "../../../shared/services/scalar-client-api.service";
import { ConfigModalContext } from "../../../integration/integration.component"; import { ConfigModalContext } from "../../../integration/integration.component";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { EditableWidget, WIDGET_YOUTUBE } from "../../../shared/models/widget"; import { EditableWidget, WIDGET_YOUTUBE } from "../../../shared/models/widget";
@ -17,7 +17,7 @@ export class YoutubeWidgetConfigComponent extends WidgetComponent implements Mod
constructor(public dialog: DialogRef<ConfigModalContext>, constructor(public dialog: DialogRef<ConfigModalContext>,
toaster: ToasterService, toaster: ToasterService,
scalarService: ScalarService, scalarService: ScalarClientApiService,
window: Window) { window: Window) {
super( super(
window, window,

View File

@ -1,10 +1,8 @@
<div class="integration-bag"> <div class="integration-bag">
<!--<my-integration *ngFor="let integration of integrations"-->
<!--[integration]="integration" (selected)="onClick(integration)"></my-integration>-->
<div class="integration" *ngFor="let integration of integrations"> <div class="integration" *ngFor="let integration of integrations">
<img class="integration-avatar" [src]="getSafeUrl(integration.avatar)"/> <img class="integration-avatar" [src]="getSafeUrl(integration.avatarUrl)"/>
<div class="integration-name">{{ integration.name }}</div> <div class="integration-name">{{ integration.displayName }}</div>
<div class="integration-description">{{ integration.about }}</div> <div class="integration-description">{{ integration.description }}</div>
<div class="integration-arrow"><i class="fa fa-chevron-right"></i></div> <div class="integration-arrow"><i class="fa fa-chevron-right"></i></div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,7 @@
import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Component, EventEmitter, Input, Output } from "@angular/core";
import { Integration } from "../shared/models/integration"; import { LegacyIntegration } from "../shared/models/legacyintegration";
import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser"; import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser";
import { Integration } from "../shared/models/integration";
@Component({ @Component({
selector: "my-integration-bag", selector: "my-integration-bag",
@ -9,7 +10,7 @@ import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser";
}) })
export class IntegrationBagComponent { export class IntegrationBagComponent {
@Input() integrations: Integration[]; @Input() integrations: LegacyIntegration[];
@Output() integrationClicked: EventEmitter<Integration> = new EventEmitter<Integration>(); @Output() integrationClicked: EventEmitter<Integration> = new EventEmitter<Integration>();
constructor(private sanitizer: DomSanitizer) { constructor(private sanitizer: DomSanitizer) {

View File

@ -1,10 +1,10 @@
import { Component, EventEmitter, Input, Output } from "@angular/core"; import { Component, EventEmitter, Input, Output } from "@angular/core";
import { Integration } from "../shared/models/integration"; import { LegacyIntegration } from "../shared/models/legacyintegration";
import { BSModalContext } from "ngx-modialog/plugins/bootstrap"; import { BSModalContext } from "ngx-modialog/plugins/bootstrap";
import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser"; import { DomSanitizer, SafeResourceUrl } from "@angular/platform-browser";
export class ConfigModalContext extends BSModalContext { export class ConfigModalContext extends BSModalContext {
public integration: Integration; public integration: LegacyIntegration;
public roomId: string; public roomId: string;
public userId: string; public userId: string;
public scalarToken: string; public scalarToken: string;
@ -18,7 +18,7 @@ export class ConfigModalContext extends BSModalContext {
}) })
export class IntegrationComponent { export class IntegrationComponent {
@Input() integration: Integration; @Input() integration: LegacyIntegration;
@Output() selected: EventEmitter<any> = new EventEmitter<any>(); @Output() selected: EventEmitter<any> = new EventEmitter<any>();
constructor(private sanitizer: DomSanitizer) { constructor(private sanitizer: DomSanitizer) {

View File

@ -1,12 +1,13 @@
import { Component, ViewChildren } from "@angular/core"; import { Component } from "@angular/core";
import { IntegrationService } from "../../shared/services/integration.service";
import { IntegrationComponent } from "../../integration/integration.component";
import { ToasterService } from "angular2-toaster"; import { ToasterService } from "angular2-toaster";
import { Integration } from "../../shared/models/integration";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { ApiService } from "../../shared/services/api.service"; import { ScalarClientApiService } from "../../shared/services/scalar-client-api.service";
import { ScalarService } from "../../shared/services/scalar.service";
import * as _ from "lodash"; import * as _ from "lodash";
import { ScalarServerApiService } from "../../shared/services/scalar-server-api.service";
import { AuthedApi } from "../../shared/services/AuthedApi";
import { DimensionApiService } from "../../shared/services/dimension-api.service";
import { Integration, IntegrationRequirement } from "../../shared/models/integration";
import { IntegrationService } from "../../shared/services/integration.service";
const CATEGORY_MAP = { const CATEGORY_MAP = {
"Widgets": ["widget"], "Widgets": ["widget"],
@ -20,14 +21,11 @@ const CATEGORY_MAP = {
styleUrls: ["./home.component.scss"], styleUrls: ["./home.component.scss"],
}) })
export class RiotHomeComponent { export class RiotHomeComponent {
@ViewChildren(IntegrationComponent) integrationComponents: Array<IntegrationComponent>;
public isLoading = true; public isLoading = true;
public isError = false; public isError = false;
public errorMessage: string; public errorMessage: string;
public isRoomEncrypted: boolean; public isRoomEncrypted: boolean;
private scalarToken: string;
private roomId: string; private roomId: string;
private userId: string; private userId: string;
private requestedScreen: string = null; private requestedScreen: string = null;
@ -36,8 +34,9 @@ export class RiotHomeComponent {
private categoryMap: { [categoryName: string]: string[] } = CATEGORY_MAP; private categoryMap: { [categoryName: string]: string[] } = CATEGORY_MAP;
constructor(private activatedRoute: ActivatedRoute, constructor(private activatedRoute: ActivatedRoute,
private api: ApiService, private scalarApi: ScalarServerApiService,
private scalar: ScalarService, private scalar: ScalarClientApiService,
private dimensionApi: DimensionApiService,
private toaster: ToasterService) { private toaster: ToasterService) {
let params: any = this.activatedRoute.snapshot.queryParams; let params: any = this.activatedRoute.snapshot.queryParams;
@ -51,9 +50,10 @@ export class RiotHomeComponent {
this.errorMessage = "Unable to load Dimension - missing room ID or token."; this.errorMessage = "Unable to load Dimension - missing room ID or token.";
} else { } else {
this.roomId = params.room_id; this.roomId = params.room_id;
this.scalarToken = params.scalar_token; AuthedApi.SCALAR_TOKEN = params.scalar_token;
this.api.getTokenOwner(params.scalar_token).then(userId => { this.scalarApi.getAccount().then(response => {
const userId = response.user_id;
if (!userId) { if (!userId) {
console.error("No user returned for token. Is the token registered in Dimension?"); console.error("No user returned for token. Is the token registered in Dimension?");
this.isError = true; this.isError = true;
@ -89,31 +89,42 @@ export class RiotHomeComponent {
return this.integrationsForCategory[category]; return this.integrationsForCategory[category];
} }
public modifyIntegration(integration: Integration) { private getIntegrations(): Integration[] {
console.log(this.userId + " is trying to modify " + integration.name); const result: Integration[] = [];
if (integration.hasAdditionalConfig) { for (const category of this.getCategories()) {
// TODO: Navigate to edit screen for (const integration of this.getIntegrationsIn(category)) {
console.log("EDIT SCREEN FOR " + integration.name); result.push(integration);
} else { }
// It's a flip-a-bit (simple bot) }
return result;
}
public modifyIntegration(integration: Integration) {
console.log(this.userId + " is trying to modify " + integration.displayName);
if (integration.category === "bot") {
// It's a bot
// TODO: "Are you sure?" dialog // TODO: "Are you sure?" dialog
let promise = null; // let promise = null;
if (!integration.isEnabled) { const promise = Promise.resolve();
promise = this.scalar.inviteUser(this.roomId, integration.userId); // if (!integration._inRoom) {
} else promise = this.api.removeIntegration(this.roomId, integration.type, integration.integrationType, this.scalarToken); // promise = this.scalar.inviteUser(this.roomId, integration.userId);
// } else promise = this.api.removeIntegration(this.roomId, integration.type, integration.integrationType, this.scalarToken);
// We set this ahead of the promise for debouncing // We set this ahead of the promise for debouncing
integration.isEnabled = !integration.isEnabled;
integration.isUpdating = true; integration._inRoom = !integration._inRoom;
integration._isUpdating = true;
promise.then(() => { promise.then(() => {
integration.isUpdating = false; integration._isUpdating = false;
if (integration.isEnabled) this.toaster.pop("success", integration.name + " was invited to the room"); if (integration._inRoom) this.toaster.pop("success", integration.displayName + " was invited to the room");
else this.toaster.pop("success", integration.name + " was removed from the room"); else this.toaster.pop("success", integration.displayName + " was removed from the room");
}).catch(err => { }).catch(err => {
integration.isEnabled = !integration.isEnabled; // revert the status change integration._inRoom = !integration._inRoom; // revert the status change
integration.isUpdating = false; integration._isUpdating = false;
console.error(err); console.error(err);
let errorMessage = null; let errorMessage = null;
@ -122,31 +133,27 @@ export class RiotHomeComponent {
if (!errorMessage) errorMessage = "Could not update integration status"; if (!errorMessage) errorMessage = "Could not update integration status";
this.toaster.pop("error", errorMessage); this.toaster.pop("error", errorMessage);
}) });
} else {
// TODO: Navigate to edit screen
console.log("EDIT SCREEN FOR " + integration.displayName);
} }
} }
private prepareIntegrations() { private prepareIntegrations() {
this.scalar.isRoomEncrypted(this.roomId).then(payload => { this.scalar.isRoomEncrypted(this.roomId).then(payload => {
this.isRoomEncrypted = payload.response; this.isRoomEncrypted = payload.response;
return this.api.getIntegrations(this.roomId, this.scalarToken); return this.dimensionApi.getIntegrations(this.roomId);
}).then(integrations => { }).then(response => {
const integrations: Integration[] = _.flatten(Object.keys(response).map(k => response[k]));
const supportedIntegrations: Integration[] = _.filter(integrations, i => IntegrationService.isSupported(i)); const supportedIntegrations: Integration[] = _.filter(integrations, i => IntegrationService.isSupported(i));
for (const integration of supportedIntegrations) {
// Widgets technically support encrypted rooms, so unless they explicitly declare that
// they don't, we'll assume they do. A warning about adding widgets in encrypted rooms
// is displayed to users elsewhere.
if (integration.type === "widget" && integration.supportsEncryptedRooms !== false)
integration.supportsEncryptedRooms = true;
}
// Flag integrations that aren't supported in encrypted rooms // Flag integrations that aren't supported in encrypted rooms
if (this.isRoomEncrypted) { if (this.isRoomEncrypted) {
for (const integration of supportedIntegrations) { for (const integration of supportedIntegrations) {
if (!integration.supportsEncryptedRooms) { if (!integration.isEncryptionSupported) {
integration.isSupported = false; integration._isSupported = false;
integration.notSupportedReason = "This integration is not supported in encrypted rooms"; integration._notSupportedReason = "This integration is not supported in encrypted rooms";
} }
} }
} }
@ -154,7 +161,7 @@ export class RiotHomeComponent {
// Set up the categories // Set up the categories
for (const category of Object.keys(this.categoryMap)) { for (const category of Object.keys(this.categoryMap)) {
const supportedTypes = this.categoryMap[category]; const supportedTypes = this.categoryMap[category];
this.integrationsForCategory[category] = _.filter(supportedIntegrations, i => supportedTypes.indexOf(i.type) !== -1); this.integrationsForCategory[category] = _.filter(supportedIntegrations, i => supportedTypes.indexOf(i.category) !== -1);
} }
let promises = supportedIntegrations.map(i => this.updateIntegrationState(i)); let promises = supportedIntegrations.map(i => this.updateIntegrationState(i));
@ -173,92 +180,57 @@ export class RiotHomeComponent {
} }
private tryOpenConfigScreen() { private tryOpenConfigScreen() {
let category = null;
let type = null; let type = null;
let integrationType = null;
if (!this.requestedScreen) return; if (!this.requestedScreen) return;
const targetIntegration = IntegrationService.getIntegrationForScreen(this.requestedScreen); const targetIntegration = IntegrationService.getIntegrationForScreen(this.requestedScreen);
if (targetIntegration) { if (targetIntegration) {
category = targetIntegration.category;
type = targetIntegration.type; type = targetIntegration.type;
integrationType = targetIntegration.integrationType;
} else { } else {
console.log("Unknown screen requested: " + this.requestedScreen); console.log("Unknown screen requested: " + this.requestedScreen);
} }
let opened = false; for (const integration of this.getIntegrations()) {
this.integrationComponents.forEach(component => { if (integration.category === category && integration.type === type) {
if (opened) return; console.log("Configuring integration " + this.requestedIntegration + " category=" + category + " type=" + type);
if (component.integration.type !== type || component.integration.integrationType !== integrationType) return; // TODO: Actually edit integration
console.log("Configuring integration " + this.requestedIntegration + " type=" + type + " integrationType=" + integrationType); return;
//component.configureIntegration(this.requestedIntegration); }
// TODO: Support editing integrations
opened = true;
});
if (!opened) {
console.log("Failed to find integration component for type=" + type + " integrationType=" + integrationType);
} }
console.log("Failed to find integration component for category=" + category + " type=" + type);
} }
private updateIntegrationState(integration: Integration) { private updateIntegrationState(integration: Integration) {
integration.hasAdditionalConfig = IntegrationService.hasConfig(integration); if (!integration.requirements) return;
if (integration.type === "widget") { let promises = integration.requirements.map(r => this.checkRequirement(r));
if (!integration.requirements) integration.requirements = {};
integration.requirements["canSetWidget"] = true;
}
// If the integration has requirements, then we'll check those instead of anything else return Promise.all(promises).then(() => {
if (integration.requirements) { integration._isSupported = true;
let keys = _.keys(integration.requirements); integration._notSupportedReason = null;
let promises = []; }, error => {
for (let key of keys) {
let requirement = this.checkRequirement(integration, key);
promises.push(requirement);
}
return Promise.all(promises).then(() => {
integration.isSupported = true;
integration.notSupportedReason = null;
}, error => {
console.error(error);
integration.isSupported = false;
integration.notSupportedReason = error;
});
}
// The integration doesn't have requirements, so we'll just make sure the bot user can be retrieved.
return this.scalar.getMembershipState(this.roomId, integration.userId).then(payload => {
if (payload.response) {
integration.isSupported = true;
integration.notSupportedReason = null;
integration.isEnabled = (payload.response.membership === "join" || payload.response.membership === "invite");
} else {
console.error("No response received to membership query of " + integration.userId);
integration.isSupported = false;
integration.notSupportedReason = "Unable to query membership state for this bot";
}
}, (error) => {
console.error(error); console.error(error);
integration.isSupported = false; integration._isSupported = false;
integration.notSupportedReason = "Unable to query membership state for this bot"; integration._notSupportedReason = error;
}); });
} }
private checkRequirement(integration: Integration, key: string) { private checkRequirement(requirement: IntegrationRequirement) {
let requirement = integration.requirements[key]; switch (requirement.condition) {
case "publicRoom":
switch (key) {
case "joinRule":
return this.scalar.getJoinRule(this.roomId).then(payload => { return this.scalar.getJoinRule(this.roomId).then(payload => {
if (!payload.response) { if (!payload.response) {
return Promise.reject("Could not communicate with Riot"); return Promise.reject("Could not communicate with Riot");
} }
return payload.response.join_rule === requirement const isPublic = payload.response.join_rule === "public";
? Promise.resolve() if (isPublic !== requirement.expectedValue) {
: Promise.reject("The room must be " + requirement + " to use this integration."); return Promise.reject("The room must be " + (isPublic ? "non-public" : "public") + " to use this integration");
} else return Promise.resolve();
}); });
case "canSetWidget": case "canSendEventTypes":
const processPayload = payload => { const processPayload = payload => {
const response = <any>payload.response; const response = <any>payload.response;
if (response === true) return Promise.resolve(); if (response === true) return Promise.resolve();
@ -266,9 +238,17 @@ export class RiotHomeComponent {
return Promise.reject("You cannot modify widgets in this room"); return Promise.reject("You cannot modify widgets in this room");
return Promise.reject("Error communicating with Riot"); return Promise.reject("Error communicating with Riot");
}; };
return this.scalar.canSendEvent(this.roomId, "im.vector.modular.widgets", true).then(processPayload).catch(processPayload);
let promiseChain = Promise.resolve();
requirement.argument.forEach(e => promiseChain = promiseChain.then(() => this.scalar.canSendEvent(this.roomId, e.type, e.isState).then(processPayload).catch(processPayload)));
return promiseChain.then(() => {
if (!requirement.expectedValue) return Promise.reject("Expected to not be able to send specific event types");
}).catch(err => {
console.error(err);
if (requirement.expectedValue) return Promise.reject("Expected to be able to send specific event types");
});
default: default:
return Promise.reject("Requirement '" + key + "' not found"); return Promise.reject("Requirement '" + requirement.condition + "' not found");
} }
} }
} }

View File

@ -1,5 +1,5 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ScalarService } from "../../shared/services/scalar.service"; import { ScalarClientApiService } from "../../shared/services/scalar-client-api.service";
@Component({ @Component({
selector: "my-scalar-close", selector: "my-scalar-close",
@ -8,7 +8,7 @@ import { ScalarService } from "../../shared/services/scalar.service";
}) })
export class ScalarCloseComponent { export class ScalarCloseComponent {
constructor(private scalar: ScalarService) { constructor(private scalar: ScalarClientApiService) {
} }
public closeScalar() { public closeScalar() {

View File

@ -0,0 +1,5 @@
import { Widget } from "./integration";
export interface DimensionIntegrationsResponse {
widgets: Widget[];
}

View File

@ -1,49 +1,40 @@
export interface Integration { export interface Integration {
// These are from the server category: "bot" | "complex-bot" | "bridge" | "widget";
type: string; type: string;
integrationType: string; requirements: IntegrationRequirement[];
userId: string; isEncryptionSupported: boolean;
name: string; displayName: string;
avatar: string; avatarUrl: string;
about: string; // nullable description: string;
supportsEncryptedRooms: boolean; isEnabled: boolean;
requirements: any; // nullable isPublic: boolean;
// These are set in the UI // Used by us
isSupported: boolean; _inRoom: boolean;
notSupportedReason: string; _isUpdating: boolean;
hasAdditionalConfig: boolean; _isSupported: boolean;
isEnabled: boolean; // for the flip-a-bit integrations _notSupportedReason: string;
isUpdating: boolean;
} }
export interface RSSIntegration extends Integration { export interface Widget extends Integration {
feeds: string[]; options: any;
immutableFeeds: { url: string, ownerId: string }[];
} }
export interface TravisCiIntegration extends Integration { export interface EtherpadWidget extends Widget {
repoTemplates: { repoKey: string, template: string, newTemplate: string }[]; // newTemplate is local options: {
immutableRepoTemplates: { repoKey: string, template: string, ownerId: string }[]; defaultUrl: string;
webhookUrl: string; // immutable };
} }
export interface CircleCiIntegration extends Integration { export interface JitsiWidget extends Widget {
repoTemplates: { repoKey: string, template: string, newTemplate: string }[]; // newTemplate is local options: {
immutableRepoTemplates: { repoKey: string, template: string, ownerId: string }[]; jitsiDomain: string;
webhookUrl: string; // immutable scriptUrl: string;
};
} }
export interface IRCIntegration extends Integration { export interface IntegrationRequirement {
availableNetworks: { name: string, id: string }[]; condition: "publicRoom" | "canSendEventTypes";
channels: { [networkId: string]: string[] }; argument: any;
} expectedValue: any;
export interface EtherpadWidgetIntegration extends Integration {
defaultUrl: string;
}
export interface JitsiWidgetIntegration extends Integration {
jitsiDomain: string;
scriptUrl: string
} }

View File

@ -0,0 +1,49 @@
export interface LegacyIntegration {
// These are from the server
type: string;
integrationType: string;
userId: string;
name: string;
avatar: string;
about: string; // nullable
supportsEncryptedRooms: boolean;
requirements: any; // nullable
// These are set in the UI
isSupported: boolean;
notSupportedReason: string;
hasAdditionalConfig: boolean;
isEnabled: boolean; // for the flip-a-bit integrations
isUpdating: boolean;
}
export interface RSSIntegration extends LegacyIntegration {
feeds: string[];
immutableFeeds: { url: string, ownerId: string }[];
}
export interface TravisCiIntegration extends LegacyIntegration {
repoTemplates: { repoKey: string, template: string, newTemplate: string }[]; // newTemplate is local
immutableRepoTemplates: { repoKey: string, template: string, ownerId: string }[];
webhookUrl: string; // immutable
}
export interface CircleCiIntegration extends LegacyIntegration {
repoTemplates: { repoKey: string, template: string, newTemplate: string }[]; // newTemplate is local
immutableRepoTemplates: { repoKey: string, template: string, ownerId: string }[];
webhookUrl: string; // immutable
}
export interface IRCIntegration extends LegacyIntegration {
availableNetworks: { name: string, id: string }[];
channels: { [networkId: string]: string[] };
}
export interface EtherpadWidgetIntegration extends LegacyIntegration {
defaultUrl: string;
}
export interface JitsiWidgetIntegration extends LegacyIntegration {
jitsiDomain: string;
scriptUrl: string
}

View File

@ -0,0 +1,3 @@
export interface ScalarAccountResponse {
user_id: string;
}

View File

@ -1,4 +1,4 @@
import { WidgetsResponse } from "./scalar_responses"; import { WidgetsResponse } from "./scalar_client_responses";
export const WIDGET_CUSTOM = ["customwidget", "dimension-customwidget"]; export const WIDGET_CUSTOM = ["customwidget", "dimension-customwidget"];
export const WIDGET_ETHERPAD = ["etherpad", "dimension-etherpad"]; export const WIDGET_ETHERPAD = ["etherpad", "dimension-etherpad"];

View File

@ -0,0 +1,15 @@
import { Http, Response } from "@angular/http";
import { Observable } from "rxjs/Observable";
export class AuthedApi {
public static SCALAR_TOKEN: string = null;
constructor(protected http: Http) {
}
protected authedGet(url: string, qs?: any): Observable<Response> {
if (!qs) qs = {};
qs["scalar_token"] = AuthedApi.SCALAR_TOKEN;
return this.http.get(url, {params: qs});
}
}

View File

@ -0,0 +1,10 @@
import { Injectable } from "@angular/core";
import { Http } from "@angular/http";
import { AuthedApi } from "./AuthedApi";
@Injectable()
export class AdminApiService extends AuthedApi {
constructor(http: Http) {
super(http);
}
}

View File

@ -0,0 +1,15 @@
import { Injectable } from "@angular/core";
import { Http } from "@angular/http";
import { AuthedApi } from "./AuthedApi";
import { DimensionIntegrationsResponse } from "../models/dimension_responses";
@Injectable()
export class DimensionApiService extends AuthedApi {
constructor(http: Http) {
super(http);
}
public getIntegrations(roomId: string): Promise<DimensionIntegrationsResponse> {
return this.authedGet("/api/v1/dimension/integrations/room/" + roomId).map(r => r.json()).toPromise();
}
}

View File

@ -1,21 +1,9 @@
import { Injectable } from "@angular/core"; import { Component, Injectable } from "@angular/core";
import { Integration } from "../models/integration";
import { RssConfigComponent } from "../../configs/rss/rss-config.component";
import { ContainerContent } from "ngx-modialog";
import { IrcConfigComponent } from "../../configs/irc/irc-config.component";
import { TravisCiConfigComponent } from "../../configs/travisci/travisci-config.component";
import { CustomWidgetConfigComponent } from "../../configs/widget/custom_widget/custom_widget-config.component";
import { YoutubeWidgetConfigComponent } from "../../configs/widget/youtube/youtube-config.component";
import { TwitchWidgetConfigComponent } from "../../configs/widget/twitch/twitch-config.component";
import { EtherpadWidgetConfigComponent } from "../../configs/widget/etherpad/etherpad-config.component";
import { JitsiWidgetConfigComponent } from "../../configs/widget/jitsi/jitsi-config.component";
import { import {
WIDGET_CUSTOM, WIDGET_ETHERPAD, WIDGET_GOOGLE_CALENDAR, WIDGET_GOOGLE_DOCS, WIDGET_JITSI, WIDGET_TWITCH, WIDGET_CUSTOM, WIDGET_ETHERPAD, WIDGET_GOOGLE_CALENDAR, WIDGET_GOOGLE_DOCS, WIDGET_JITSI, WIDGET_TWITCH,
WIDGET_YOUTUBE WIDGET_YOUTUBE
} from "../models/widget"; } from "../models/widget";
import { GoogleDocsWidgetConfigComponent } from "../../configs/widget/googledocs/googledocs-config.component"; import { Integration } from "../models/integration";
import { GoogleCalendarWidgetConfigComponent } from "../../configs/widget/googlecalendar/googlecalendar-config.component";
import { CircleCiConfigComponent } from "../../configs/circleci/circleci-config.component";
@Injectable() @Injectable()
export class IntegrationService { export class IntegrationService {
@ -24,53 +12,53 @@ export class IntegrationService {
"bot": {}, // empty == supported "bot": {}, // empty == supported
"complex-bot": { "complex-bot": {
"rss": { "rss": {
component: RssConfigComponent, //component: RssConfigComponent,
}, },
"travisci": { "travisci": {
component: TravisCiConfigComponent, //component: TravisCiConfigComponent,
}, },
"circleci": { "circleci": {
component: CircleCiConfigComponent, //component: CircleCiConfigComponent,
}, },
}, },
"bridge": { "bridge": {
"irc": { "irc": {
component: IrcConfigComponent, //component: IrcConfigComponent,
}, },
}, },
"widget": { "widget": {
"customwidget": { "customwidget": {
component: CustomWidgetConfigComponent, //component: CustomWidgetConfigComponent,
types: WIDGET_CUSTOM, types: WIDGET_CUSTOM,
}, },
"youtube": { "youtube": {
component: YoutubeWidgetConfigComponent, //component: YoutubeWidgetConfigComponent,
types: WIDGET_YOUTUBE types: WIDGET_YOUTUBE
}, },
"etherpad": { "etherpad": {
component: EtherpadWidgetConfigComponent, //component: EtherpadWidgetConfigComponent,
types: WIDGET_ETHERPAD, types: WIDGET_ETHERPAD,
}, },
"twitch": { "twitch": {
component: TwitchWidgetConfigComponent, //component: TwitchWidgetConfigComponent,
types: WIDGET_TWITCH, types: WIDGET_TWITCH,
}, },
"jitsi": { "jitsi": {
component: JitsiWidgetConfigComponent, //component: JitsiWidgetConfigComponent,
types: WIDGET_JITSI, types: WIDGET_JITSI,
}, },
"googledocs": { "googledocs": {
component: GoogleDocsWidgetConfigComponent, //component: GoogleDocsWidgetConfigComponent,
types: WIDGET_GOOGLE_DOCS, types: WIDGET_GOOGLE_DOCS,
}, },
"googlecalendar": { "googlecalendar": {
component: GoogleCalendarWidgetConfigComponent, //component: GoogleCalendarWidgetConfigComponent,
types: WIDGET_GOOGLE_CALENDAR, types: WIDGET_GOOGLE_CALENDAR,
}, },
}, },
}; };
static getAllConfigComponents(): ContainerContent[] { static getAllConfigComponents(): Component[] {
const components = []; const components = [];
for (const iType of Object.keys(IntegrationService.supportedIntegrationsMap)) { for (const iType of Object.keys(IntegrationService.supportedIntegrationsMap)) {
@ -84,34 +72,30 @@ export class IntegrationService {
} }
static isSupported(integration: Integration): boolean { static isSupported(integration: Integration): boolean {
const forType = IntegrationService.supportedIntegrationsMap[integration.type]; const forType = IntegrationService.supportedIntegrationsMap[integration.category];
if (!forType) return false; if (!forType) return false;
if (Object.keys(forType).length === 0) return true; if (Object.keys(forType).length === 0) return true;
return forType[integration.integrationType]; // has sub type return forType[integration.type]; // has sub type
} }
static hasConfig(integration: Integration): boolean { static getConfigComponent(integration: Integration): Component {
return integration.type !== "bot"; return IntegrationService.supportedIntegrationsMap[integration.category][integration.type].component;
} }
static getConfigComponent(integration: Integration): ContainerContent { static getIntegrationForScreen(screen: string): { category: string, type: string } {
return IntegrationService.supportedIntegrationsMap[integration.type][integration.integrationType].component;
}
static getIntegrationForScreen(screen: string): { type: string, integrationType: string } {
for (const iType of Object.keys(IntegrationService.supportedIntegrationsMap)) { for (const iType of Object.keys(IntegrationService.supportedIntegrationsMap)) {
for (const iiType of Object.keys(IntegrationService.supportedIntegrationsMap[iType])) { for (const iiType of Object.keys(IntegrationService.supportedIntegrationsMap[iType])) {
const integrationTypes = IntegrationService.supportedIntegrationsMap[iType][iiType].types; const integrationTypes = IntegrationService.supportedIntegrationsMap[iType][iiType].types;
const integrationScreens = integrationTypes.map(t => "type_" + t); const integrationScreens = integrationTypes.map(t => "type_" + t);
if (integrationScreens.includes(screen)) return {type: iType, integrationType: iiType}; if (integrationScreens.includes(screen)) return {category: iType, type: iiType};
} }
} }
return null; return null;
} }
constructor() { private constructor() {
} }
} }

View File

@ -1,6 +1,6 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { Http } from "@angular/http"; import { Http } from "@angular/http";
import { Integration } from "../models/integration"; import { LegacyIntegration } from "../../models/legacyintegration";
@Injectable() @Injectable()
export class ApiService { export class ApiService {
@ -17,7 +17,7 @@ export class ApiService {
.map(res => res.status === 200 ? res.json()["userId"] : null).toPromise(); .map(res => res.status === 200 ? res.json()["userId"] : null).toPromise();
} }
getIntegrations(roomId: string, scalarToken: string): Promise<Integration[]> { getIntegrations(roomId: string, scalarToken: string): Promise<LegacyIntegration[]> {
return this.http.get("/api/v1/dimension/integrations/" + roomId, {params: {scalar_token: scalarToken}}) return this.http.get("/api/v1/dimension/integrations/" + roomId, {params: {scalar_token: scalarToken}})
.map(res => res.json()).toPromise(); .map(res => res.json()).toPromise();
} }
@ -46,7 +46,7 @@ export class ApiService {
.map(res => res.json()).toPromise(); .map(res => res.json()).toPromise();
} }
getIntegration(type: string, integrationType: string): Promise<Integration> { getIntegration(type: string, integrationType: string): Promise<LegacyIntegration> {
const url = "/api/v1/dimension/integration/" + type + "/" + integrationType; const url = "/api/v1/dimension/integration/" + type + "/" + integrationType;
return this.http.get(url).map(res => res.json()).toPromise(); return this.http.get(url).map(res => res.json()).toPromise();
} }

View File

@ -1,24 +1,24 @@
import { Injectable } from "@angular/core"; import { Injectable } from "@angular/core";
import { Integration } from "./models/integration"; import { LegacyIntegration } from "../../models/legacyintegration";
import { RssConfigComponent } from "../configs/rss/rss-config.component"; import { RssConfigComponent } from "../../../configs/rss/rss-config.component";
import { ContainerContent } from "ngx-modialog"; import { ContainerContent } from "ngx-modialog";
import { IrcConfigComponent } from "../configs/irc/irc-config.component"; import { IrcConfigComponent } from "../../../configs/irc/irc-config.component";
import { TravisCiConfigComponent } from "../configs/travisci/travisci-config.component"; import { TravisCiConfigComponent } from "../../../configs/travisci/travisci-config.component";
import { CircleCiConfigComponent } from "../configs/circleci/circleci-config.component"; import { CustomWidgetConfigComponent } from "../../../configs/widget/custom_widget/custom_widget-config.component";
import { CustomWidgetConfigComponent } from "../configs/widget/custom_widget/custom_widget-config.component"; import { YoutubeWidgetConfigComponent } from "../../../configs/widget/youtube/youtube-config.component";
import { YoutubeWidgetConfigComponent } from "../configs/widget/youtube/youtube-config.component"; import { TwitchWidgetConfigComponent } from "../../../configs/widget/twitch/twitch-config.component";
import { TwitchWidgetConfigComponent } from "../configs/widget/twitch/twitch-config.component"; import { EtherpadWidgetConfigComponent } from "../../../configs/widget/etherpad/etherpad-config.component";
import { EtherpadWidgetConfigComponent } from "../configs/widget/etherpad/etherpad-config.component"; import { JitsiWidgetConfigComponent } from "../../../configs/widget/jitsi/jitsi-config.component";
import { JitsiWidgetConfigComponent } from "../configs/widget/jitsi/jitsi-config.component";
import { import {
WIDGET_CUSTOM, WIDGET_ETHERPAD, WIDGET_GOOGLE_CALENDAR, WIDGET_GOOGLE_DOCS, WIDGET_JITSI, WIDGET_TWITCH, WIDGET_CUSTOM, WIDGET_ETHERPAD, WIDGET_GOOGLE_CALENDAR, WIDGET_GOOGLE_DOCS, WIDGET_JITSI, WIDGET_TWITCH,
WIDGET_YOUTUBE WIDGET_YOUTUBE
} from "./models/widget"; } from "../../models/widget";
import { GoogleDocsWidgetConfigComponent } from "../configs/widget/googledocs/googledocs-config.component"; import { GoogleDocsWidgetConfigComponent } from "../../../configs/widget/googledocs/googledocs-config.component";
import { GoogleCalendarWidgetConfigComponent } from "../configs/widget/googlecalendar/googlecalendar-config.component"; import { GoogleCalendarWidgetConfigComponent } from "../../../configs/widget/googlecalendar/googlecalendar-config.component";
import { CircleCiConfigComponent } from "../../../configs/circleci/circleci-config.component";
@Injectable() @Injectable()
export class IntegrationService { export class LegacyIntegrationService {
private static supportedIntegrationsMap = { private static supportedIntegrationsMap = {
"bot": {}, // empty == supported "bot": {}, // empty == supported
@ -73,9 +73,9 @@ export class IntegrationService {
static getAllConfigComponents(): ContainerContent[] { static getAllConfigComponents(): ContainerContent[] {
const components = []; const components = [];
for (const iType of Object.keys(IntegrationService.supportedIntegrationsMap)) { for (const iType of Object.keys(LegacyIntegrationService.supportedIntegrationsMap)) {
for (const iiType of Object.keys(IntegrationService.supportedIntegrationsMap[iType])) { for (const iiType of Object.keys(LegacyIntegrationService.supportedIntegrationsMap[iType])) {
const component = IntegrationService.supportedIntegrationsMap[iType][iiType].component; const component = LegacyIntegrationService.supportedIntegrationsMap[iType][iiType].component;
if (component) components.push(component); if (component) components.push(component);
} }
} }
@ -83,8 +83,8 @@ export class IntegrationService {
return components; return components;
} }
static isSupported(integration: Integration): boolean { static isSupported(integration: LegacyIntegration): boolean {
const forType = IntegrationService.supportedIntegrationsMap[integration.type]; const forType = LegacyIntegrationService.supportedIntegrationsMap[integration.type];
if (!forType) return false; if (!forType) return false;
if (Object.keys(forType).length === 0) return true; if (Object.keys(forType).length === 0) return true;
@ -92,18 +92,18 @@ export class IntegrationService {
return forType[integration.integrationType]; // has sub type return forType[integration.integrationType]; // has sub type
} }
static hasConfig(integration: Integration): boolean { static hasConfig(integration: LegacyIntegration): boolean {
return integration.type !== "bot"; return integration.type !== "bot";
} }
static getConfigComponent(integration: Integration): ContainerContent { static getConfigComponent(integration: LegacyIntegration): ContainerContent {
return IntegrationService.supportedIntegrationsMap[integration.type][integration.integrationType].component; return LegacyIntegrationService.supportedIntegrationsMap[integration.type][integration.integrationType].component;
} }
static getIntegrationForScreen(screen: string): { type: string, integrationType: string } { static getIntegrationForScreen(screen: string): { type: string, integrationType: string } {
for (const iType of Object.keys(IntegrationService.supportedIntegrationsMap)) { for (const iType of Object.keys(LegacyIntegrationService.supportedIntegrationsMap)) {
for (const iiType of Object.keys(IntegrationService.supportedIntegrationsMap[iType])) { for (const iiType of Object.keys(LegacyIntegrationService.supportedIntegrationsMap[iType])) {
const integrationTypes = IntegrationService.supportedIntegrationsMap[iType][iiType].types; const integrationTypes = LegacyIntegrationService.supportedIntegrationsMap[iType][iiType].types;
const integrationScreens = integrationTypes.map(t => "type_" + t); const integrationScreens = integrationTypes.map(t => "type_" + t);
if (integrationScreens.includes(screen)) return {type: iType, integrationType: iiType}; if (integrationScreens.includes(screen)) return {type: iType, integrationType: iiType};
} }

View File

@ -6,17 +6,17 @@ import {
MembershipStateResponse, RoomEncryptionStatusResponse, MembershipStateResponse, RoomEncryptionStatusResponse,
ScalarSuccessResponse, ScalarSuccessResponse,
WidgetsResponse WidgetsResponse
} from "../models/scalar_responses"; } from "../models/scalar_client_responses";
import { EditableWidget } from "../models/widget"; import { EditableWidget } from "../models/widget";
@Injectable() @Injectable()
export class ScalarService { export class ScalarClientApiService {
private static actionMap: { [key: string]: { resolve: (obj: any) => void, reject: (obj: any) => void } } = {}; private static actionMap: { [key: string]: { resolve: (obj: any) => void, reject: (obj: any) => void } } = {};
public static getAndRemoveActionHandler(requestKey: string): { resolve: (obj: any) => void, reject: (obj: any) => void } { public static getAndRemoveActionHandler(requestKey: string): { resolve: (obj: any) => void, reject: (obj: any) => void } {
let handler = ScalarService.actionMap[requestKey]; let handler = ScalarClientApiService.actionMap[requestKey];
ScalarService.actionMap[requestKey] = null; ScalarClientApiService.actionMap[requestKey] = null;
return handler; return handler;
} }
@ -96,7 +96,7 @@ export class ScalarService {
return; return;
} }
ScalarService.actionMap[requestKey] = { ScalarClientApiService.actionMap[requestKey] = {
resolve: resolve, resolve: resolve,
reject: reject reject: reject
}; };
@ -117,7 +117,7 @@ window.addEventListener("message", event => {
let requestKey = event.data["request_id"]; let requestKey = event.data["request_id"];
if (!requestKey) return; if (!requestKey) return;
let action = ScalarService.getAndRemoveActionHandler(requestKey); let action = ScalarClientApiService.getAndRemoveActionHandler(requestKey);
if (!action) return; if (!action) return;
if (event.data.response && event.data.response.error) action.reject(event.data); if (event.data.response && event.data.response.error) action.reject(event.data);

View File

@ -0,0 +1,15 @@
import { Injectable } from "@angular/core";
import { Http } from "@angular/http";
import { ScalarAccountResponse } from "../models/scalar_server_responses";
import { AuthedApi } from "./AuthedApi";
@Injectable()
export class ScalarServerApiService extends AuthedApi {
constructor(http: Http) {
super(http)
}
public getAccount(): Promise<ScalarAccountResponse> {
return this.authedGet("/api/v1/scalar/account").map(res => res.json()).toPromise();
}
}

View File

@ -1,5 +1,5 @@
import { Component } from "@angular/core"; import { Component } from "@angular/core";
import { ApiService } from "../../shared/services/api.service"; import { ApiService } from "../../shared/services/legacy/api.service";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import { DomSanitizer, SafeUrl } from "@angular/platform-browser"; import { DomSanitizer, SafeUrl } from "@angular/platform-browser";

View File

@ -1,8 +1,8 @@
import { Component, OnInit } from "@angular/core"; import { Component, OnInit } from "@angular/core";
import { ActivatedRoute } from "@angular/router"; import { ActivatedRoute } from "@angular/router";
import * as $ from "jquery"; import * as $ from "jquery";
import { ApiService } from "../../shared/services/api.service"; import { ApiService } from "../../shared/services/legacy/api.service";
import { JitsiWidgetIntegration } from "../../shared/models/integration"; import { JitsiWidgetIntegration } from "../../shared/models/legacyintegration";
declare var JitsiMeetExternalAPI: any; declare var JitsiMeetExternalAPI: any;