mirror of
https://github.com/turt2live/matrix-dimension.git
synced 2024-08-31 06:02:09 +00:00
Add a sticker picker
The useful bit for sending stickers. Implements the rest of #156
This commit is contained in:
parent
d2c672cf00
commit
6c4e8f75d4
@ -77,6 +77,7 @@ import { AdminStickerPackPreviewComponent } from "./admin/sticker-packs/preview/
|
||||
import { MediaService } from "./shared/services/media.service";
|
||||
import { StickerApiService } from "./shared/services/integrations/sticker-api.service";
|
||||
import { StickerpickerComponent } from "./configs/stickerpicker/stickerpicker.component";
|
||||
import { StickerPickerWidgetWrapperComponent } from "./widget-wrappers/sticker-picker/sticker-picker.component";
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@ -143,6 +144,7 @@ import { StickerpickerComponent } from "./configs/stickerpicker/stickerpicker.co
|
||||
AdminStickerPacksComponent,
|
||||
AdminStickerPackPreviewComponent,
|
||||
StickerpickerComponent,
|
||||
StickerPickerWidgetWrapperComponent,
|
||||
|
||||
// Vendor
|
||||
],
|
||||
|
@ -26,6 +26,7 @@ import { AdminIrcBridgeComponent } from "./admin/bridges/irc/irc.component";
|
||||
import { IrcBridgeConfigComponent } from "./configs/bridge/irc/irc.bridge.component";
|
||||
import { AdminStickerPacksComponent } from "./admin/sticker-packs/sticker-packs.component";
|
||||
import { StickerpickerComponent } from "./configs/stickerpicker/stickerpicker.component";
|
||||
import { StickerPickerWidgetWrapperComponent } from "./widget-wrappers/sticker-picker/sticker-picker.component";
|
||||
|
||||
const routes: Routes = [
|
||||
{path: "", component: HomeComponent},
|
||||
@ -179,6 +180,7 @@ const routes: Routes = [
|
||||
{path: "video", component: VideoWidgetWrapperComponent},
|
||||
{path: "jitsi", component: JitsiWidgetWrapperComponent},
|
||||
{path: "gcal", component: GCalWidgetWrapperComponent},
|
||||
{path: "stickerpicker", component: StickerPickerWidgetWrapperComponent},
|
||||
]
|
||||
},
|
||||
];
|
||||
|
@ -3,6 +3,8 @@ import { FE_UserStickerPack } from "../../shared/models/integration";
|
||||
import { StickerApiService } from "../../shared/services/integrations/sticker-api.service";
|
||||
import { ToasterService } from "angular2-toaster";
|
||||
import { MediaService } from "../../shared/services/media.service";
|
||||
import { ScalarClientApiService } from "../../shared/services/scalar/scalar-client-api.service";
|
||||
import { WIDGET_STICKER_PICKER } from "../../shared/models/widget";
|
||||
|
||||
@Component({
|
||||
templateUrl: "stickerpicker.component.html",
|
||||
@ -16,7 +18,9 @@ export class StickerpickerComponent implements OnInit {
|
||||
|
||||
constructor(private stickerApi: StickerApiService,
|
||||
private media: MediaService,
|
||||
private toaster: ToasterService) {
|
||||
private scalarClient: ScalarClientApiService,
|
||||
private toaster: ToasterService,
|
||||
private window: Window) {
|
||||
this.isLoading = true;
|
||||
this.isUpdating = false;
|
||||
}
|
||||
@ -41,7 +45,8 @@ export class StickerpickerComponent implements OnInit {
|
||||
this.stickerApi.togglePackSelection(pack.id, pack.isSelected).then(() => {
|
||||
this.isUpdating = false;
|
||||
this.toaster.pop("success", "Stickers updated");
|
||||
// TODO: Add the user widget when we have >1 sticker pack selected
|
||||
|
||||
if (this.packs.filter(p => p.isSelected).length > 0) this.addWidget();
|
||||
}).catch(err => {
|
||||
console.error(err);
|
||||
pack.isSelected = !pack.isSelected; // revert change
|
||||
@ -49,4 +54,32 @@ export class StickerpickerComponent implements OnInit {
|
||||
this.toaster.pop("error", "Error updating stickers");
|
||||
});
|
||||
}
|
||||
|
||||
private async addWidget() {
|
||||
try {
|
||||
const widgets = await this.scalarClient.getWidgets();
|
||||
const stickerPicker = widgets.response.find(w => w.content && w.content.type === "m.stickerpicker");
|
||||
if (stickerPicker && !stickerPicker.content.data.dimension) {
|
||||
console.log("Deleting non-Dimension sticker picker");
|
||||
await this.scalarClient.deleteUserWidget(stickerPicker);
|
||||
}
|
||||
|
||||
if (stickerPicker) return; // already have a widget
|
||||
|
||||
console.log("Adding Dimension sticker picker");
|
||||
await this.scalarClient.setUserWidget({
|
||||
id: "dimension-stickerpicker-" + (new Date().getTime()),
|
||||
type: WIDGET_STICKER_PICKER[0],
|
||||
url: this.window.location.origin + "/widgets/stickerpicker",
|
||||
data: {
|
||||
dimension: {
|
||||
wrapperId: "stickerpicker",
|
||||
},
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("Failed to check for Dimension sticker picker");
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
export interface ScalarToWidgetRequest {
|
||||
api: "to_widget";
|
||||
action: "supported_api_versions" | "screenshot" | "capabilities" | "send_event" | "visibility_change" | string;
|
||||
action: "supported_api_versions" | "screenshot" | "capabilities" | "send_event" | "visibility" | string;
|
||||
requestId: string;
|
||||
widgetId: string;
|
||||
data?: any;
|
||||
|
@ -33,18 +33,20 @@ export interface JoinRuleStateResponse extends ScalarRoomResponse {
|
||||
}
|
||||
|
||||
export interface WidgetsResponse extends ScalarRoomResponse {
|
||||
response: {
|
||||
type: "im.vector.modular.widgets";
|
||||
state_key: string;
|
||||
sender: string;
|
||||
room_id: string;
|
||||
content: {
|
||||
type: string;
|
||||
url: string;
|
||||
name?: string;
|
||||
data?: any;
|
||||
}
|
||||
}[];
|
||||
response: ScalarWidget[];
|
||||
}
|
||||
|
||||
export interface ScalarWidget {
|
||||
type: "im.vector.modular.widgets";
|
||||
state_key: string;
|
||||
sender: string;
|
||||
room_id: string;
|
||||
content: {
|
||||
type: string;
|
||||
url: string;
|
||||
name?: string;
|
||||
data?: any;
|
||||
}
|
||||
}
|
||||
|
||||
export interface CanSendEventResponse extends ScalarRoomResponse {
|
||||
|
@ -8,6 +8,7 @@ export const WIDGET_JITSI = ["jitsi", "dimension-jitsi"];
|
||||
export const WIDGET_YOUTUBE = ["youtube", "dimension-youtube"];
|
||||
export const WIDGET_GRAFANA = ["grafana", "dimension-grafana"];
|
||||
export const WIDGET_TWITCH = ["twitch", "dimension-twitch"];
|
||||
export const WIDGET_STICKER_PICKER = ["m.stickerpicker"];
|
||||
|
||||
export interface EditableWidget {
|
||||
/**
|
||||
|
@ -5,7 +5,7 @@ import {
|
||||
JoinRuleStateResponse,
|
||||
MembershipStateResponse,
|
||||
RoomEncryptionStatusResponse,
|
||||
ScalarSuccessResponse,
|
||||
ScalarSuccessResponse, ScalarWidget,
|
||||
SetPowerLevelResponse,
|
||||
WidgetsResponse
|
||||
} from "../../models/server-client-responses";
|
||||
@ -45,7 +45,7 @@ export class ScalarClientApiService {
|
||||
});
|
||||
}
|
||||
|
||||
public getWidgets(roomId: string): Promise<WidgetsResponse> {
|
||||
public getWidgets(roomId?: string): Promise<WidgetsResponse> {
|
||||
return this.callAction("get_widgets", {
|
||||
room_id: roomId
|
||||
});
|
||||
@ -62,10 +62,32 @@ export class ScalarClientApiService {
|
||||
});
|
||||
}
|
||||
|
||||
public deleteWidget(roomId: string, widget: EditableWidget): Promise<ScalarSuccessResponse> {
|
||||
public setUserWidget(widget: EditableWidget): Promise<ScalarSuccessResponse> {
|
||||
return this.callAction("set_widget", {
|
||||
userWidget: true,
|
||||
widget_id: widget.id,
|
||||
type: widget.type,
|
||||
url: widget.url,
|
||||
name: widget.name,
|
||||
data: widget.data
|
||||
});
|
||||
}
|
||||
|
||||
public deleteWidget(roomId: string, widget: EditableWidget|ScalarWidget): Promise<ScalarSuccessResponse> {
|
||||
const anyWidget: any = widget;
|
||||
return this.callAction("set_widget", {
|
||||
room_id: roomId,
|
||||
widget_id: widget.id,
|
||||
widget_id: anyWidget.id || anyWidget.state_key,
|
||||
type: widget.type, // required for some reason
|
||||
url: ""
|
||||
});
|
||||
}
|
||||
|
||||
public deleteUserWidget(widget: EditableWidget|ScalarWidget): Promise<ScalarSuccessResponse> {
|
||||
const anyWidget: any = widget;
|
||||
return this.callAction("set_widget", {
|
||||
userWidget: true,
|
||||
widget_id: anyWidget.id || anyWidget.state_key,
|
||||
type: widget.type, // required for some reason
|
||||
url: ""
|
||||
});
|
||||
|
@ -1,23 +1,16 @@
|
||||
import { ScalarToWidgetRequest } from "../../models/scalar-widget-actions";
|
||||
import { ReplaySubject } from "rxjs/ReplaySubject";
|
||||
import { Subject } from "rxjs/Subject";
|
||||
import { FE_Sticker, FE_StickerPack } from "../../models/integration";
|
||||
|
||||
export class ScalarWidgetApi {
|
||||
|
||||
public static requestReceived: Subject<ScalarToWidgetRequest> = new ReplaySubject();
|
||||
private static widgetId: string;
|
||||
public static widgetId: string;
|
||||
|
||||
private constructor() {
|
||||
}
|
||||
|
||||
public static setWidgetId(id: string) {
|
||||
ScalarWidgetApi.widgetId = id;
|
||||
}
|
||||
|
||||
public static test() {
|
||||
ScalarWidgetApi.callAction(null, null);
|
||||
}
|
||||
|
||||
public static replyScreenshot(request: ScalarToWidgetRequest, data: Blob): void {
|
||||
ScalarWidgetApi.replyEvent(request, {screenshot: data});
|
||||
}
|
||||
@ -38,6 +31,38 @@ export class ScalarWidgetApi {
|
||||
ScalarWidgetApi.replyEvent(request, {error: {message: message || error.message, _error: error}});
|
||||
}
|
||||
|
||||
public static replyAcknowledge(request: ScalarToWidgetRequest): void {
|
||||
ScalarWidgetApi.replyEvent(request, {success: true});
|
||||
}
|
||||
|
||||
public static sendSticker(sticker: FE_Sticker, pack: FE_StickerPack): void {
|
||||
ScalarWidgetApi.callAction("m.sticker", {
|
||||
data: {
|
||||
description: sticker.description,
|
||||
content: {
|
||||
url: sticker.thumbnail.mxc,
|
||||
info: {
|
||||
mimetype: sticker.image.mimetype,
|
||||
w: sticker.thumbnail.width / 2,
|
||||
h: sticker.thumbnail.height / 2,
|
||||
thumbnail_url: sticker.thumbnail.mxc,
|
||||
thumbnail_info: {
|
||||
mimetype: sticker.image.mimetype,
|
||||
w: sticker.thumbnail.width / 2,
|
||||
h: sticker.thumbnail.height / 2,
|
||||
},
|
||||
|
||||
// This has to be included in the info object so it makes it to the event
|
||||
dimension: {
|
||||
license: pack.license,
|
||||
author: pack.author,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
private static callAction(action, payload) {
|
||||
if (!window.opener) {
|
||||
return;
|
||||
@ -67,6 +92,7 @@ window.addEventListener("message", event => {
|
||||
if (!event.data) return;
|
||||
|
||||
if (event.data.api === "toWidget" && event.data.action) {
|
||||
if (event.data.widgetId && !ScalarWidgetApi.widgetId) ScalarWidgetApi.widgetId = event.data.widgetId;
|
||||
ScalarWidgetApi.requestReceived.next(event.data);
|
||||
return;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ export abstract class CapableWidget implements OnInit, OnDestroy {
|
||||
|
||||
// The capabilities we support
|
||||
protected supportsScreenshots = false;
|
||||
protected supportsStickers = false;
|
||||
|
||||
public ngOnInit() {
|
||||
this.widgetApiSubscription = ScalarWidgetApi.requestReceived.subscribe(request => {
|
||||
@ -15,6 +16,7 @@ export abstract class CapableWidget implements OnInit, OnDestroy {
|
||||
const capabilities = [];
|
||||
|
||||
if (this.supportsScreenshots) capabilities.push("m.capability.screenshot");
|
||||
if (this.supportsStickers) capabilities.push("m.sticker");
|
||||
|
||||
ScalarWidgetApi.replyCapabilities(request, capabilities);
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import { Component } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { DomSanitizer, SafeUrl } from "@angular/platform-browser";
|
||||
import { WidgetApiService } from "../../shared/services/integrations/widget-api.service";
|
||||
import { ScalarWidgetApi } from "../../shared/services/scalar/scalar-widget.api";
|
||||
|
||||
@Component({
|
||||
selector: "my-generic-widget-wrapper",
|
||||
@ -18,8 +17,6 @@ export class GenericWidgetWrapperComponent {
|
||||
constructor(widgetApi: WidgetApiService, activatedRoute: ActivatedRoute, sanitizer: DomSanitizer) {
|
||||
let params: any = activatedRoute.snapshot.queryParams;
|
||||
|
||||
ScalarWidgetApi.setWidgetId("test");
|
||||
|
||||
widgetApi.isEmbeddable(params.url).then(result => {
|
||||
this.canEmbed = result.canEmbed;
|
||||
this.isLoading = false;
|
||||
|
@ -0,0 +1,26 @@
|
||||
<div class="wrapper">
|
||||
<div class="control-page" *ngIf="isLoading || authError">
|
||||
<div class="loading-badge" *ngIf="isLoading">
|
||||
<my-spinner></my-spinner>
|
||||
</div>
|
||||
<div class="auth-error" *ngIf="!isLoading && authError">
|
||||
There was a problem authenticating your use of this sticker picker. Please make sure you're using a client
|
||||
that has Dimension enabled and correctly set up.
|
||||
</div>
|
||||
</div>
|
||||
<div class="sticker-picker" *ngIf="!isLoading && !authError">
|
||||
<div class="sticker-pack" *ngFor="let pack of packs trackById">
|
||||
<div class="header">
|
||||
<span class="title">{{ pack.displayName }}</span>
|
||||
<span class="license"><a [href]="pack.license.urlPath">{{ pack.license.name }}</a></span>
|
||||
<span class="author" *ngIf="pack.author.type !== 'none'"><a [href]="pack.author.reference">{{ pack.author.name }}</a> | </span>
|
||||
</div>
|
||||
|
||||
<div class="stickers">
|
||||
<div class="sticker" *ngFor="let sticker of pack.stickers trackById" (click)="sendSticker(sticker, pack)">
|
||||
<img [src]="getThumbnailUrl(sticker.thumbnail.mxc, 48, 48)" width="48" height="48" class="image" ngbTooltip="{{ sticker.name }}" placement="bottom" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,69 @@
|
||||
// component styles are encapsulated and only applied to their components
|
||||
.control-page {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background-color: #eee;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.loading-badge {
|
||||
text-align: center;
|
||||
font-size: 20px;
|
||||
position: relative;
|
||||
top: calc(50% - 10px);
|
||||
}
|
||||
|
||||
.auth-error {
|
||||
text-align: center;
|
||||
position: relative;
|
||||
height: 300px;
|
||||
top: calc(50% - 150px);
|
||||
color: #bd362f;
|
||||
}
|
||||
|
||||
.sticker-picker {
|
||||
margin: 15px 15px 30px;
|
||||
|
||||
.sticker-pack {
|
||||
.header {
|
||||
margin-top: 15px;
|
||||
margin-left: 3px;
|
||||
|
||||
.title {
|
||||
font-weight: 700;
|
||||
color: #222222;
|
||||
}
|
||||
|
||||
.author, .license {
|
||||
font-size: 0.6em;
|
||||
font-weight: 300;
|
||||
color: #b5b5b5;
|
||||
margin-top: 3px;
|
||||
float: right;
|
||||
|
||||
a {
|
||||
color: #b5b5b5;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.stickers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.sticker {
|
||||
padding: 5px;
|
||||
margin: 2px;
|
||||
cursor: pointer;
|
||||
border-radius: 3px;
|
||||
background-color: #fff;
|
||||
box-shadow: 0 2px 6px hsla(0, 0%, 0%, 0.2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,98 @@
|
||||
import { Component, OnDestroy, OnInit } from "@angular/core";
|
||||
import { ActivatedRoute } from "@angular/router";
|
||||
import { CapableWidget } from "../capable-widget";
|
||||
import { Subscription } from "rxjs/Subscription";
|
||||
import { ScalarWidgetApi } from "../../shared/services/scalar/scalar-widget.api";
|
||||
import { StickerApiService } from "../../shared/services/integrations/sticker-api.service";
|
||||
import { SessionStorage } from "../../shared/SessionStorage";
|
||||
import { ScalarServerApiService } from "../../shared/services/scalar/scalar-server-api.service";
|
||||
import { FE_Sticker, FE_UserStickerPack } from "../../shared/models/integration";
|
||||
import { MediaService } from "../../shared/services/media.service";
|
||||
|
||||
@Component({
|
||||
selector: "my-generic-widget-wrapper",
|
||||
templateUrl: "sticker-picker.component.html",
|
||||
styleUrls: ["sticker-picker.component.scss"],
|
||||
})
|
||||
export class StickerPickerWidgetWrapperComponent extends CapableWidget implements OnInit, OnDestroy {
|
||||
|
||||
public isLoading = true;
|
||||
public authError = false;
|
||||
public packs: FE_UserStickerPack[];
|
||||
|
||||
private stickerWidgetApiSubscription: Subscription;
|
||||
|
||||
constructor(activatedRoute: ActivatedRoute,
|
||||
private media: MediaService,
|
||||
private scalarApi: ScalarServerApiService,
|
||||
private stickerApi: StickerApiService) {
|
||||
super();
|
||||
this.supportsStickers = true;
|
||||
|
||||
let params: any = activatedRoute.snapshot.queryParams;
|
||||
|
||||
let token = params.scalar_token;
|
||||
if (!token) token = localStorage.getItem("dim-scalar-token");
|
||||
else localStorage.setItem("dim-scalar-token", token);
|
||||
|
||||
if (!params.widgetId) {
|
||||
console.error("No widgetId query parameter");
|
||||
this.authError = true;
|
||||
} else {
|
||||
ScalarWidgetApi.widgetId = params.widgetId;
|
||||
}
|
||||
|
||||
SessionStorage.scalarToken = token;
|
||||
this.authError = !token;
|
||||
}
|
||||
|
||||
public ngOnInit() {
|
||||
super.ngOnInit();
|
||||
this.stickerWidgetApiSubscription = ScalarWidgetApi.requestReceived.subscribe(request => {
|
||||
if (request.action === "visibility") {
|
||||
if ((<any>request).visible) this.loadStickers();
|
||||
ScalarWidgetApi.replyAcknowledge(request);
|
||||
}
|
||||
});
|
||||
this.loadStickers();
|
||||
}
|
||||
|
||||
public ngOnDestroy() {
|
||||
super.ngOnDestroy();
|
||||
if (this.stickerWidgetApiSubscription) this.stickerWidgetApiSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
public getThumbnailUrl(mxc: string, width: number, height: number, method: "crop" | "scale" = "scale"): string {
|
||||
return this.media.getThumbnailUrl(mxc, width, height, method, true);
|
||||
}
|
||||
|
||||
private async loadStickers() {
|
||||
if (this.authError) return; // Don't bother
|
||||
|
||||
if (!SessionStorage.userId) {
|
||||
try {
|
||||
const info = await this.scalarApi.getAccount();
|
||||
SessionStorage.userId = info.user_id;
|
||||
console.log("Dimension scalar_token belongs to " + SessionStorage.userId);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.authError = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("Attempting to load available stickers...");
|
||||
try {
|
||||
const packs = await this.stickerApi.getPacks();
|
||||
this.packs = packs.filter(p => p.isSelected);
|
||||
console.log("User has " + this.packs.length + "/" + packs.length + " sticker packs selected");
|
||||
this.isLoading = false;
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
public sendSticker(sticker: FE_Sticker, pack: FE_UserStickerPack) {
|
||||
ScalarWidgetApi.sendSticker(sticker, pack);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user