Migrate StickerPicker to TypeScript

pull/21833/head
Germain Souquet 2021-08-25 14:26:21 +01:00
parent 9444995305
commit f712186e64
1 changed files with 88 additions and 78 deletions

View File

@ -15,22 +15,25 @@ limitations under the License.
*/ */
import React from 'react'; import React from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Room } from 'matrix-js-sdk/src/models/room';
import { _t, _td } from '../../../languageHandler'; import { _t, _td } from '../../../languageHandler';
import AppTile from '../elements/AppTile'; import AppTile from '../elements/AppTile';
import { MatrixClientPeg } from '../../../MatrixClientPeg'; import { MatrixClientPeg } from '../../../MatrixClientPeg';
import * as sdk from '../../../index'; import * as sdk from '../../../index';
import dis from '../../../dispatcher/dispatcher'; import dis from '../../../dispatcher/dispatcher';
import AccessibleButton from '../elements/AccessibleButton'; import AccessibleButton from '../elements/AccessibleButton';
import WidgetUtils from '../../../utils/WidgetUtils'; import WidgetUtils, { IWidgetEvent } from '../../../utils/WidgetUtils';
import PersistedElement from "../elements/PersistedElement"; import PersistedElement from "../elements/PersistedElement";
import { IntegrationManagers } from "../../../integrations/IntegrationManagers"; import { IntegrationManagers } from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore from "../../../settings/SettingsStore";
import { ContextMenu } from "../../structures/ContextMenu"; import { ChevronFace, ContextMenu } from "../../structures/ContextMenu";
import { WidgetType } from "../../../widgets/WidgetType"; import { WidgetType } from "../../../widgets/WidgetType";
import AccessibleTooltipButton from "../elements/AccessibleTooltipButton"; import AccessibleTooltipButton from "../elements/AccessibleTooltipButton";
import { Action } from "../../../dispatcher/actions"; import { Action } from "../../../dispatcher/actions";
import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore"; import { WidgetMessagingStore } from "../../../stores/widgets/WidgetMessagingStore";
import { replaceableComponent } from "../../../utils/replaceableComponent"; import { replaceableComponent } from "../../../utils/replaceableComponent";
import { ActionPayload } from '../../../dispatcher/payloads';
import ScalarAuthClient from '../../../ScalarAuthClient';
// This should be below the dialog level (4000), but above the rest of the UI (1000-2000). // This should be below the dialog level (4000), but above the rest of the UI (1000-2000).
// We sit in a context menu, so this should be given to the context menu. // We sit in a context menu, so this should be given to the context menu.
@ -39,27 +42,35 @@ const STICKERPICKER_Z_INDEX = 3500;
// Key to store the widget's AppTile under in PersistedElement // Key to store the widget's AppTile under in PersistedElement
const PERSISTED_ELEMENT_KEY = "stickerPicker"; const PERSISTED_ELEMENT_KEY = "stickerPicker";
interface IProps {
room: Room;
}
interface IState {
showStickers: boolean;
imError: string;
stickerpickerX: number;
stickerpickerY: number;
stickerpickerChevronOffset?: number;
stickerpickerWidget: IWidgetEvent;
widgetId: string;
}
@replaceableComponent("views.rooms.Stickerpicker") @replaceableComponent("views.rooms.Stickerpicker")
export default class Stickerpicker extends React.PureComponent { export default class Stickerpicker extends React.PureComponent<IProps, IState> {
static currentWidget; static currentWidget;
constructor(props) { private dispatcherRef: string;
super(props);
this._onShowStickersClick = this._onShowStickersClick.bind(this);
this._onHideStickersClick = this._onHideStickersClick.bind(this);
this._launchManageIntegrations = this._launchManageIntegrations.bind(this);
this._removeStickerpickerWidgets = this._removeStickerpickerWidgets.bind(this);
this._updateWidget = this._updateWidget.bind(this);
this._onWidgetAction = this._onWidgetAction.bind(this);
this._onResize = this._onResize.bind(this);
this._onFinished = this._onFinished.bind(this);
this.popoverWidth = 300; private prevSentVisibility: boolean;
this.popoverHeight = 300;
private popoverWidth = 300;
private popoverHeight = 300;
// This is loaded by _acquireScalarClient on an as-needed basis. // This is loaded by _acquireScalarClient on an as-needed basis.
this.scalarClient = null; private scalarClient: ScalarAuthClient = null;
constructor(props: IProps) {
super(props);
this.state = { this.state = {
showStickers: false, showStickers: false,
imError: null, imError: null,
@ -70,7 +81,7 @@ export default class Stickerpicker extends React.PureComponent {
}; };
} }
_acquireScalarClient() { private acquireScalarClient(): Promise<void | ScalarAuthClient> {
if (this.scalarClient) return Promise.resolve(this.scalarClient); if (this.scalarClient) return Promise.resolve(this.scalarClient);
// TODO: Pick the right manager for the widget // TODO: Pick the right manager for the widget
if (IntegrationManagers.sharedInstance().hasManager()) { if (IntegrationManagers.sharedInstance().hasManager()) {
@ -79,15 +90,15 @@ export default class Stickerpicker extends React.PureComponent {
this.forceUpdate(); this.forceUpdate();
return this.scalarClient; return this.scalarClient;
}).catch((e) => { }).catch((e) => {
this._imError(_td("Failed to connect to integration manager"), e); this.imError(_td("Failed to connect to integration manager"), e);
}); });
} else { } else {
IntegrationManagers.sharedInstance().openNoManagerDialog(); IntegrationManagers.sharedInstance().openNoManagerDialog();
} }
} }
async _removeStickerpickerWidgets() { private removeStickerpickerWidgets = async (): Promise<void> => {
const scalarClient = await this._acquireScalarClient(); const scalarClient = await this.acquireScalarClient();
console.log('Removing Stickerpicker widgets'); console.log('Removing Stickerpicker widgets');
if (this.state.widgetId) { if (this.state.widgetId) {
if (scalarClient) { if (scalarClient) {
@ -109,36 +120,36 @@ export default class Stickerpicker extends React.PureComponent {
}).catch((e) => { }).catch((e) => {
console.error('Failed to remove sticker picker widget', e); console.error('Failed to remove sticker picker widget', e);
}); });
} };
componentDidMount() { public componentDidMount(): void {
// Close the sticker picker when the window resizes // Close the sticker picker when the window resizes
window.addEventListener('resize', this._onResize); window.addEventListener('resize', this.onResize);
this.dispatcherRef = dis.register(this._onWidgetAction); this.dispatcherRef = dis.register(this.onWidgetAction);
// Track updates to widget state in account data // Track updates to widget state in account data
MatrixClientPeg.get().on('accountData', this._updateWidget); MatrixClientPeg.get().on('accountData', this.updateWidget);
// Initialise widget state from current account data // Initialise widget state from current account data
this._updateWidget(); this.updateWidget();
} }
componentWillUnmount() { public componentWillUnmount(): void {
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (client) client.removeListener('accountData', this._updateWidget); if (client) client.removeListener('accountData', this.updateWidget);
window.removeEventListener('resize', this._onResize); window.removeEventListener('resize', this.onResize);
if (this.dispatcherRef) { if (this.dispatcherRef) {
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
} }
} }
componentDidUpdate(prevProps, prevState) { public componentDidUpdate(prevProps: IProps, prevState: IState): void {
this._sendVisibilityToWidget(this.state.showStickers); this.sendVisibilityToWidget(this.state.showStickers);
} }
_imError(errorMsg, e) { private imError(errorMsg: string, e: Error): void {
console.error(errorMsg, e); console.error(errorMsg, e);
this.setState({ this.setState({
showStickers: false, showStickers: false,
@ -146,7 +157,7 @@ export default class Stickerpicker extends React.PureComponent {
}); });
} }
_updateWidget() { private updateWidget = (): void => {
const stickerpickerWidget = WidgetUtils.getStickerpickerWidgets()[0]; const stickerpickerWidget = WidgetUtils.getStickerpickerWidgets()[0];
if (!stickerpickerWidget) { if (!stickerpickerWidget) {
Stickerpicker.currentWidget = null; Stickerpicker.currentWidget = null;
@ -175,9 +186,9 @@ export default class Stickerpicker extends React.PureComponent {
stickerpickerWidget, stickerpickerWidget,
widgetId: stickerpickerWidget ? stickerpickerWidget.id : null, widgetId: stickerpickerWidget ? stickerpickerWidget.id : null,
}); });
} };
_onWidgetAction(payload) { private onWidgetAction = (payload: ActionPayload): void => {
switch (payload.action) { switch (payload.action) {
case "user_widget_updated": case "user_widget_updated":
this.forceUpdate(); this.forceUpdate();
@ -191,11 +202,11 @@ export default class Stickerpicker extends React.PureComponent {
this.setState({ showStickers: false }); this.setState({ showStickers: false });
break; break;
} }
} };
_defaultStickerpickerContent() { private defaultStickerpickerContent(): JSX.Element {
return ( return (
<AccessibleButton onClick={this._launchManageIntegrations} <AccessibleButton onClick={this.launchManageIntegrations}
className='mx_Stickers_contentPlaceholder'> className='mx_Stickers_contentPlaceholder'>
<p>{ _t("You don't currently have any stickerpacks enabled") }</p> <p>{ _t("You don't currently have any stickerpacks enabled") }</p>
<p className='mx_Stickers_addLink'>{ _t("Add some now") }</p> <p className='mx_Stickers_addLink'>{ _t("Add some now") }</p>
@ -204,29 +215,29 @@ export default class Stickerpicker extends React.PureComponent {
); );
} }
_errorStickerpickerContent() { private errorStickerpickerContent(): JSX.Element {
return ( return (
<div style={{ "text-align": "center" }} className="error"> <div style={{ textAlign: "center" }} className="error">
<p> { this.state.imError } </p> <p> { this.state.imError } </p>
</div> </div>
); );
} }
_sendVisibilityToWidget(visible) { private sendVisibilityToWidget(visible: boolean): void {
if (!this.state.stickerpickerWidget) return; if (!this.state.stickerpickerWidget) return;
const messaging = WidgetMessagingStore.instance.getMessagingForId(this.state.stickerpickerWidget.id); const messaging = WidgetMessagingStore.instance.getMessagingForId(this.state.stickerpickerWidget.id);
if (messaging && visible !== this._prevSentVisibility) { if (messaging && visible !== this.prevSentVisibility) {
messaging.updateVisibility(visible).catch(err => { messaging.updateVisibility(visible).catch(err => {
console.error("Error updating widget visibility: ", err); console.error("Error updating widget visibility: ", err);
}); });
this._prevSentVisibility = visible; this.prevSentVisibility = visible;
} }
} }
_getStickerpickerContent() { public getStickerpickerContent(): JSX.Element {
// Handle integration manager errors // Handle integration manager errors
if (this.state._imError) { if (this.state.imError) {
return this._errorStickerpickerContent(); return this.errorStickerpickerContent();
} }
// Stickers // Stickers
@ -244,7 +255,7 @@ export default class Stickerpicker extends React.PureComponent {
// Load stickerpack content // Load stickerpack content
if (stickerpickerWidget && stickerpickerWidget.content && stickerpickerWidget.content.url) { if (stickerpickerWidget && stickerpickerWidget.content && stickerpickerWidget.content.url) {
// Set default name // Set default name
stickerpickerWidget.content.name = stickerpickerWidget.name || _t("Stickerpack"); stickerpickerWidget.content.name = stickerpickerWidget.content.name || _t("Stickerpack");
// FIXME: could this use the same code as other apps? // FIXME: could this use the same code as other apps?
const stickerApp = { const stickerApp = {
@ -275,12 +286,12 @@ export default class Stickerpicker extends React.PureComponent {
creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId} creatorUserId={stickerpickerWidget.sender || MatrixClientPeg.get().credentials.userId}
waitForIframeLoad={true} waitForIframeLoad={true}
showMenubar={true} showMenubar={true}
onEditClick={this._launchManageIntegrations} onEditClick={this.launchManageIntegrations}
onDeleteClick={this._removeStickerpickerWidgets} onDeleteClick={this.removeStickerpickerWidgets}
showTitle={false} showTitle={false}
showCancel={false} showCancel={false}
showPopout={false} showPopout={false}
onMinimiseClick={this._onHideStickersClick} onMinimiseClick={this.onHideStickersClick}
handleMinimisePointerEvents={true} handleMinimisePointerEvents={true}
userWidget={true} userWidget={true}
/> />
@ -290,7 +301,7 @@ export default class Stickerpicker extends React.PureComponent {
); );
} else { } else {
// Default content to show if stickerpicker widget not added // Default content to show if stickerpicker widget not added
stickersContent = this._defaultStickerpickerContent(); stickersContent = this.defaultStickerpickerContent();
} }
return stickersContent; return stickersContent;
} }
@ -300,7 +311,7 @@ export default class Stickerpicker extends React.PureComponent {
* Show the sticker picker overlay * Show the sticker picker overlay
* If no stickerpacks have been added, show a link to the integration manager add sticker packs page. * If no stickerpacks have been added, show a link to the integration manager add sticker packs page.
*/ */
_onShowStickersClick(e) { private onShowStickersClick = (e: React.MouseEvent<HTMLElement>): void => {
if (!SettingsStore.getValue("integrationProvisioning")) { if (!SettingsStore.getValue("integrationProvisioning")) {
// Intercept this case and spawn a warning. // Intercept this case and spawn a warning.
return IntegrationManagers.sharedInstance().showDisabledDialog(); return IntegrationManagers.sharedInstance().showDisabledDialog();
@ -308,7 +319,7 @@ export default class Stickerpicker extends React.PureComponent {
// XXX: Simplify by using a context menu that is positioned relative to the sticker picker button // XXX: Simplify by using a context menu that is positioned relative to the sticker picker button
const buttonRect = e.target.getBoundingClientRect(); const buttonRect = e.currentTarget.getBoundingClientRect();
// The window X and Y offsets are to adjust position when zoomed in to page // The window X and Y offsets are to adjust position when zoomed in to page
let x = buttonRect.right + window.pageXOffset - 41; let x = buttonRect.right + window.pageXOffset - 41;
@ -324,50 +335,50 @@ export default class Stickerpicker extends React.PureComponent {
// Offset the chevron location, which is relative to the left of the context menu // Offset the chevron location, which is relative to the left of the context menu
// (10 = offset when context menu would not be displayed off viewport) // (10 = offset when context menu would not be displayed off viewport)
// (2 = context menu borders) // (2 = context menu borders)
const stickerPickerChevronOffset = Math.max(10, 2 + window.pageXOffset + buttonRect.left - x); const stickerpickerChevronOffset = Math.max(10, 2 + window.pageXOffset + buttonRect.left - x);
const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19; const y = (buttonRect.top + (buttonRect.height / 2) + window.pageYOffset) - 19;
this.setState({ this.setState({
showStickers: true, showStickers: true,
stickerPickerX: x, stickerpickerX: x,
stickerPickerY: y, stickerpickerY: y,
stickerPickerChevronOffset, stickerpickerChevronOffset,
}); });
} };
/** /**
* Trigger hiding of the sticker picker overlay * Trigger hiding of the sticker picker overlay
* @param {Event} ev Event that triggered the function call * @param {Event} ev Event that triggered the function call
*/ */
_onHideStickersClick(ev) { private onHideStickersClick = (ev: React.MouseEvent): void => {
if (this.state.showStickers) { if (this.state.showStickers) {
this.setState({ showStickers: false }); this.setState({ showStickers: false });
} }
} };
/** /**
* Called when the window is resized * Called when the window is resized
*/ */
_onResize() { private onResize = (): void => {
if (this.state.showStickers) { if (this.state.showStickers) {
this.setState({ showStickers: false }); this.setState({ showStickers: false });
} }
} };
/** /**
* The stickers picker was hidden * The stickers picker was hidden
*/ */
_onFinished() { private onFinished = (): void => {
if (this.state.showStickers) { if (this.state.showStickers) {
this.setState({ showStickers: false }); this.setState({ showStickers: false });
} }
} };
/** /**
* Launch the integration manager on the stickers integration page * Launch the integration manager on the stickers integration page
*/ */
_launchManageIntegrations = () => { private launchManageIntegrations = (): void => {
// TODO: Open the right integration manager for the widget // TODO: Open the right integration manager for the widget
if (SettingsStore.getValue("feature_many_integration_managers")) { if (SettingsStore.getValue("feature_many_integration_managers")) {
IntegrationManagers.sharedInstance().openAll( IntegrationManagers.sharedInstance().openAll(
@ -384,7 +395,7 @@ export default class Stickerpicker extends React.PureComponent {
} }
}; };
render() { public render(): JSX.Element {
let stickerPicker; let stickerPicker;
let stickersButton; let stickersButton;
const className = classNames( const className = classNames(
@ -400,26 +411,25 @@ export default class Stickerpicker extends React.PureComponent {
id='stickersButton' id='stickersButton'
key="controls_hide_stickers" key="controls_hide_stickers"
className={className} className={className}
onClick={this._onHideStickersClick} onClick={this.onHideStickersClick}
active={this.state.showStickers.toString()}
title={_t("Hide Stickers")} title={_t("Hide Stickers")}
/>; />;
const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu'); const GenericElementContextMenu = sdk.getComponent('context_menus.GenericElementContextMenu');
stickerPicker = <ContextMenu stickerPicker = <ContextMenu
chevronOffset={this.state.stickerPickerChevronOffset} chevronOffset={this.state.stickerpickerChevronOffset}
chevronFace="bottom" chevronFace={ChevronFace.Bottom}
left={this.state.stickerPickerX} left={this.state.stickerpickerX}
top={this.state.stickerPickerY} top={this.state.stickerpickerY}
menuWidth={this.popoverWidth} menuWidth={this.popoverWidth}
menuHeight={this.popoverHeight} menuHeight={this.popoverHeight}
onFinished={this._onFinished} onFinished={this.onFinished}
menuPaddingTop={0} menuPaddingTop={0}
menuPaddingLeft={0} menuPaddingLeft={0}
menuPaddingRight={0} menuPaddingRight={0}
zIndex={STICKERPICKER_Z_INDEX} zIndex={STICKERPICKER_Z_INDEX}
> >
<GenericElementContextMenu element={this._getStickerpickerContent()} onResize={this._onFinished} /> <GenericElementContextMenu element={this.getStickerpickerContent()} onResize={this.onFinished} />
</ContextMenu>; </ContextMenu>;
} else { } else {
// Show show-stickers button // Show show-stickers button
@ -428,7 +438,7 @@ export default class Stickerpicker extends React.PureComponent {
id='stickersButton' id='stickersButton'
key="controls_show_stickers" key="controls_show_stickers"
className="mx_MessageComposer_button mx_MessageComposer_stickers" className="mx_MessageComposer_button mx_MessageComposer_stickers"
onClick={this._onShowStickersClick} onClick={this.onShowStickersClick}
title={_t("Show Stickers")} title={_t("Show Stickers")}
/>; />;
} }