Skip to content
This repository was archived by the owner on Sep 11, 2024. It is now read-only.

Commit eabcbb3

Browse files
authored
Merge pull request #1888 from matrix-org/luke/instant-sticker-picker
Instant Sticker Picker
2 parents 2cde2a2 + 0689839 commit eabcbb3

File tree

6 files changed

+275
-82
lines changed

6 files changed

+275
-82
lines changed

res/css/views/rooms/_MessageComposer.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ limitations under the License.
3434
width: 100%;
3535
}
3636

37-
.mx_MessageComposer_row div:last-child{
37+
.mx_MessageComposer_row > div:last-child{
3838
padding-right: 0;
3939
}
4040

src/WidgetMessaging.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,16 @@ export default class WidgetMessaging {
9494
});
9595
}
9696

97+
sendVisibility(visible) {
98+
return this.messageToWidget({
99+
api: OUTBOUND_API_NAME,
100+
action: "visibility",
101+
visible,
102+
})
103+
.catch((error) => {
104+
console.error("Failed to send visibility: ", error);
105+
});
106+
}
97107

98108
start() {
99109
this.fromWidget.addEndpoint(this.widgetId, this.widgetUrl);

src/components/structures/ContextualMenu.js

Lines changed: 53 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,21 @@ import PropTypes from 'prop-types';
2626
// of doing reusable widgets like dialog boxes & menus where we go and
2727
// pass in a custom control as the actual body.
2828

29-
module.exports = {
30-
ContextualMenuContainerId: "mx_ContextualMenu_Container",
29+
const ContextualMenuContainerId = "mx_ContextualMenu_Container";
3130

31+
function getOrCreateContainer() {
32+
let container = document.getElementById(ContextualMenuContainerId);
33+
34+
if (!container) {
35+
container = document.createElement("div");
36+
container.id = ContextualMenuContainerId;
37+
document.body.appendChild(container);
38+
}
39+
40+
return container;
41+
}
42+
43+
export default class ContextualMenu extends React.Component {
3244
propTypes: {
3345
top: PropTypes.number,
3446
bottom: PropTypes.number,
@@ -45,39 +57,18 @@ module.exports = {
4557
menuPaddingRight: PropTypes.number,
4658
menuPaddingBottom: PropTypes.number,
4759
menuPaddingLeft: PropTypes.number,
48-
},
49-
50-
getOrCreateContainer: function() {
51-
let container = document.getElementById(this.ContextualMenuContainerId);
52-
53-
if (!container) {
54-
container = document.createElement("div");
55-
container.id = this.ContextualMenuContainerId;
56-
document.body.appendChild(container);
57-
}
58-
59-
return container;
60-
},
6160

62-
createMenu: function(Element, props) {
63-
const self = this;
64-
65-
const closeMenu = function(...args) {
66-
ReactDOM.unmountComponentAtNode(self.getOrCreateContainer());
67-
68-
if (props && props.onFinished) {
69-
props.onFinished.apply(null, args);
70-
}
71-
};
72-
73-
// Close the menu on window resize
74-
const windowResize = function() {
75-
closeMenu();
76-
};
61+
// If true, insert an invisible screen-sized element behind the
62+
// menu that when clicked will close it.
63+
hasBackground: PropTypes.bool,
64+
}
7765

66+
render() {
7867
const position = {};
7968
let chevronFace = null;
8069

70+
const props = this.props;
71+
8172
if (props.top) {
8273
position.top = props.top;
8374
} else {
@@ -158,21 +149,40 @@ module.exports = {
158149
menuStyle["paddingRight"] = props.menuPaddingRight;
159150
}
160151

152+
const ElementClass = props.elementClass;
153+
161154
// FIXME: If a menu uses getDefaultProps it clobbers the onFinished
162155
// property set here so you can't close the menu from a button click!
163-
const menu = (
164-
<div className={className} style={position}>
165-
<div className={menuClasses} style={menuStyle}>
166-
{ chevron }
167-
<Element {...props} onFinished={closeMenu} onResize={windowResize} />
168-
</div>
169-
<div className="mx_ContextualMenu_background" onClick={closeMenu}></div>
170-
<style>{ chevronCSS }</style>
156+
return <div className={className} style={position}>
157+
<div className={menuClasses} style={menuStyle}>
158+
{ chevron }
159+
<ElementClass {...props} onFinished={props.closeMenu} onResize={props.windowResize} />
171160
</div>
172-
);
161+
{ props.hasBackground && <div className="mx_ContextualMenu_background" onClick={props.closeMenu}></div> }
162+
<style>{ chevronCSS }</style>
163+
</div>;
164+
}
165+
}
166+
167+
export function createMenu(ElementClass, props) {
168+
const closeMenu = function(...args) {
169+
ReactDOM.unmountComponentAtNode(getOrCreateContainer());
170+
171+
if (props && props.onFinished) {
172+
props.onFinished.apply(null, args);
173+
}
174+
};
175+
176+
// We only reference closeMenu once per call to createMenu
177+
const menu = <ContextualMenu
178+
{...props}
179+
hasBackground={true}
180+
elementClass={ElementClass}
181+
closeMenu={closeMenu} // eslint-disable-line react/jsx-no-bind
182+
windowResize={closeMenu} // eslint-disable-line react/jsx-no-bind
183+
/>;
173184

174-
ReactDOM.render(menu, this.getOrCreateContainer());
185+
ReactDOM.render(menu, getOrCreateContainer());
175186

176-
return {close: closeMenu};
177-
},
178-
};
187+
return {close: closeMenu};
188+
}

src/components/views/elements/AppTile.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,13 @@ export default class AppTile extends React.Component {
169169
this.dispatcherRef = dis.register(this._onWidgetAction);
170170
}
171171

172+
componentDidUpdate() {
173+
// Allow parents to access widget messaging
174+
if (this.props.collectWidgetMessaging) {
175+
this.props.collectWidgetMessaging(this.widgetMessaging);
176+
}
177+
}
178+
172179
componentWillUnmount() {
173180
// Widget action listeners
174181
dis.unregister(this.dispatcherRef);
@@ -357,6 +364,9 @@ export default class AppTile extends React.Component {
357364
if (!this.widgetMessaging) {
358365
this._onInitialLoad();
359366
}
367+
if (this._exposeWidgetMessaging) {
368+
this._exposeWidgetMessaging(this.widgetMessaging);
369+
}
360370
}
361371

362372
/**
@@ -394,6 +404,7 @@ export default class AppTile extends React.Component {
394404
}).catch((err) => {
395405
console.log(`Failed to get capabilities for widget type ${this.props.type}`, this.props.id, err);
396406
});
407+
397408
this.setState({loading: false});
398409
}
399410

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
Copyright 2018 New Vector Ltd.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
const React = require('react');
18+
const ReactDOM = require('react-dom');
19+
20+
// Shamelessly ripped off Modal.js. There's probably a better way
21+
// of doing reusable widgets like dialog boxes & menus where we go and
22+
// pass in a custom control as the actual body.
23+
24+
const ContainerId = "mx_PersistedElement";
25+
26+
function getOrCreateContainer() {
27+
let container = document.getElementById(ContainerId);
28+
29+
if (!container) {
30+
container = document.createElement("div");
31+
container.id = ContainerId;
32+
document.body.appendChild(container);
33+
}
34+
35+
return container;
36+
}
37+
38+
// Greater than that of the ContextualMenu
39+
const PE_Z_INDEX = 3000;
40+
41+
/*
42+
* Class of component that renders its children in a separate ReactDOM virtual tree
43+
* in a container element appended to document.body.
44+
*
45+
* This prevents the children from being unmounted when the parent of PersistedElement
46+
* unmounts, allowing them to persist.
47+
*
48+
* When PE is unmounted, it hides the children using CSS. When mounted or updated, the
49+
* children are made visible and are positioned into a div that is given the same
50+
* bounding rect as the parent of PE.
51+
*/
52+
export default class PersistedElement extends React.Component {
53+
constructor() {
54+
super();
55+
this.collectChildContainer = this.collectChildContainer.bind(this);
56+
this.collectChild = this.collectChild.bind(this);
57+
}
58+
59+
collectChildContainer(ref) {
60+
this.childContainer = ref;
61+
}
62+
63+
collectChild(ref) {
64+
this.child = ref;
65+
this.updateChild();
66+
}
67+
68+
componentDidMount() {
69+
this.updateChild();
70+
}
71+
72+
componentDidUpdate() {
73+
this.updateChild();
74+
}
75+
76+
componentWillUnmount() {
77+
this.updateChildVisibility(this.child, false);
78+
}
79+
80+
updateChild() {
81+
this.updateChildPosition(this.child, this.childContainer);
82+
this.updateChildVisibility(this.child, true);
83+
}
84+
85+
updateChildVisibility(child, visible) {
86+
if (!child) return;
87+
child.style.display = visible ? 'block' : 'none';
88+
}
89+
90+
updateChildPosition(child, parent) {
91+
if (!child || !parent) return;
92+
93+
const parentRect = parent.getBoundingClientRect();
94+
Object.assign(child.style, {
95+
position: 'absolute',
96+
top: parentRect.top + 'px',
97+
left: parentRect.left + 'px',
98+
width: parentRect.width + 'px',
99+
height: parentRect.height + 'px',
100+
zIndex: PE_Z_INDEX,
101+
});
102+
}
103+
104+
render() {
105+
const content = <div ref={this.collectChild}>
106+
{this.props.children}
107+
</div>;
108+
109+
ReactDOM.render(content, getOrCreateContainer());
110+
111+
return <div ref={this.collectChildContainer}></div>;
112+
}
113+
}
114+

0 commit comments

Comments
 (0)