Merge branches 'develop' and 't3chguy/remove_bluebird' of github.com:matrix-org/matrix-react-sdk into t3chguy/remove_bluebird

This commit is contained in:
Michael Telatynski 2019-11-21 11:15:32 +00:00
commit d3f872bf7a
24 changed files with 262 additions and 107 deletions

View file

@ -133,6 +133,7 @@
"eslint": "^5.12.0", "eslint": "^5.12.0",
"eslint-config-google": "^0.7.1", "eslint-config-google": "^0.7.1",
"eslint-plugin-babel": "^5.2.1", "eslint-plugin-babel": "^5.2.1",
"eslint-plugin-jest": "^23.0.4",
"eslint-plugin-flowtype": "^2.30.0", "eslint-plugin-flowtype": "^2.30.0",
"eslint-plugin-react": "^7.7.0", "eslint-plugin-react": "^7.7.0",
"eslint-plugin-react-hooks": "^2.0.1", "eslint-plugin-react-hooks": "^2.0.1",

View file

@ -23,10 +23,6 @@ limitations under the License.
padding-left: 84px; padding-left: 84px;
} }
.mx_MessageComposer_wrapper.mx_MessageComposer_hasE2EIcon {
padding-left: 109px;
}
.mx_MessageComposer_replaced_wrapper { .mx_MessageComposer_replaced_wrapper {
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@ -78,10 +74,10 @@ limitations under the License.
.mx_MessageComposer_e2eIcon.mx_E2EIcon { .mx_MessageComposer_e2eIcon.mx_E2EIcon {
position: absolute; position: absolute;
left: 60px; left: 60px;
width: 16px;
&::after { height: 16px;
background-color: $composer-e2e-icon-color; margin-right: 0; // Counteract the E2EIcon class
} margin-left: 3px; // Counteract the E2EIcon class
} }
.mx_MessageComposer_noperm_error { .mx_MessageComposer_noperm_error {

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -57,7 +57,7 @@ import { ValidatedServerConfig } from "../../utils/AutoDiscoveryUtils";
import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils"; import AutoDiscoveryUtils from "../../utils/AutoDiscoveryUtils";
import DMRoomMap from '../../utils/DMRoomMap'; import DMRoomMap from '../../utils/DMRoomMap';
import { countRoomsWithNotif } from '../../RoomNotifs'; import { countRoomsWithNotif } from '../../RoomNotifs';
import { setTheme } from "../../theme"; import { ThemeWatcher } from "../../theme";
import { storeRoomAliasInCache } from '../../RoomAliasCache'; import { storeRoomAliasInCache } from '../../RoomAliasCache';
import { defer } from "../../utils/promise"; import { defer } from "../../utils/promise";
@ -268,7 +268,8 @@ export default createReactClass({
componentDidMount: function() { componentDidMount: function() {
this.dispatcherRef = dis.register(this.onAction); this.dispatcherRef = dis.register(this.onAction);
this._themeWatchRef = SettingsStore.watchSetting("theme", null, this._onThemeChanged); this._themeWatcher = new ThemeWatcher();
this._themeWatcher.start();
this.focusComposer = false; this.focusComposer = false;
@ -355,7 +356,7 @@ export default createReactClass({
componentWillUnmount: function() { componentWillUnmount: function() {
Lifecycle.stopMatrixClient(); Lifecycle.stopMatrixClient();
dis.unregister(this.dispatcherRef); dis.unregister(this.dispatcherRef);
SettingsStore.unwatchSetting(this._themeWatchRef); this._themeWatcher.stop();
window.removeEventListener("focus", this.onFocus); window.removeEventListener("focus", this.onFocus);
window.removeEventListener('resize', this.handleResize); window.removeEventListener('resize', this.handleResize);
this.state.resizeNotifier.removeListener("middlePanelResized", this._dispatchTimelineResize); this.state.resizeNotifier.removeListener("middlePanelResized", this._dispatchTimelineResize);
@ -378,13 +379,6 @@ export default createReactClass({
} }
}, },
_onThemeChanged: function(settingName, roomId, atLevel, newValue) {
dis.dispatch({
action: 'set_theme',
value: newValue,
});
},
startPageChangeTimer() { startPageChangeTimer() {
// Tor doesn't support performance // Tor doesn't support performance
if (!performance || !performance.mark) return null; if (!performance || !performance.mark) return null;
@ -666,9 +660,6 @@ export default createReactClass({
}); });
break; break;
} }
case 'set_theme':
setTheme(payload.value);
break;
case 'on_logging_in': case 'on_logging_in':
// We are now logging in, so set the state to reflect that // We are now logging in, so set the state to reflect that
// NB. This does not touch 'ready' since if our dispatches // NB. This does not touch 'ready' since if our dispatches

View file

@ -34,7 +34,7 @@ import dis from '../../../dispatcher';
import ActiveWidgetStore from '../../../stores/ActiveWidgetStore'; import ActiveWidgetStore from '../../../stores/ActiveWidgetStore';
import classNames from 'classnames'; import classNames from 'classnames';
import {IntegrationManagers} from "../../../integrations/IntegrationManagers"; import {IntegrationManagers} from "../../../integrations/IntegrationManagers";
import SettingsStore from "../../../settings/SettingsStore"; import SettingsStore, {SettingLevel} from "../../../settings/SettingsStore";
const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:']; const ALLOWED_APP_URL_SCHEMES = ['https:', 'http:'];
const ENABLE_REACT_PERF = false; const ENABLE_REACT_PERF = false;
@ -69,8 +69,11 @@ export default class AppTile extends React.Component {
* @return {Object} Updated component state to be set with setState * @return {Object} Updated component state to be set with setState
*/ */
_getNewState(newProps) { _getNewState(newProps) {
const widgetPermissionId = [newProps.room.roomId, encodeURIComponent(newProps.url)].join('_'); // This is a function to make the impact of calling SettingsStore slightly less
const hasPermissionToLoad = localStorage.getItem(widgetPermissionId); const hasPermissionToLoad = () => {
const currentlyAllowedWidgets = SettingsStore.getValue("allowedWidgets", newProps.room.roomId);
return !!currentlyAllowedWidgets[newProps.eventId];
};
const PersistedElement = sdk.getComponent("elements.PersistedElement"); const PersistedElement = sdk.getComponent("elements.PersistedElement");
return { return {
@ -78,10 +81,9 @@ export default class AppTile extends React.Component {
// True while the iframe content is loading // True while the iframe content is loading
loading: this.props.waitForIframeLoad && !PersistedElement.isMounted(this._persistKey), loading: this.props.waitForIframeLoad && !PersistedElement.isMounted(this._persistKey),
widgetUrl: this._addWurlParams(newProps.url), widgetUrl: this._addWurlParams(newProps.url),
widgetPermissionId: widgetPermissionId,
// Assume that widget has permission to load if we are the user who // Assume that widget has permission to load if we are the user who
// added it to the room, or if explicitly granted by the user // added it to the room, or if explicitly granted by the user
hasPermissionToLoad: hasPermissionToLoad === 'true' || newProps.userId === newProps.creatorUserId, hasPermissionToLoad: newProps.userId === newProps.creatorUserId || hasPermissionToLoad(),
error: null, error: null,
deleting: false, deleting: false,
widgetPageTitle: newProps.widgetPageTitle, widgetPageTitle: newProps.widgetPageTitle,
@ -446,24 +448,38 @@ export default class AppTile extends React.Component {
}); });
} }
/* TODO -- Store permission in account data so that it is persisted across multiple devices */
_grantWidgetPermission() { _grantWidgetPermission() {
console.warn('Granting permission to load widget - ', this.state.widgetUrl); const roomId = this.props.room.roomId;
localStorage.setItem(this.state.widgetPermissionId, true); console.info("Granting permission for widget to load: " + this.props.eventId);
this.setState({hasPermissionToLoad: true}); const current = SettingsStore.getValue("allowedWidgets", roomId);
// Now that we have permission, fetch the IM token current[this.props.eventId] = true;
this.setScalarToken(); SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).then(() => {
this.setState({hasPermissionToLoad: true});
// Fetch a token for the integration manager, now that we're allowed to
this.setScalarToken();
}).catch(err => {
console.error(err);
// We don't really need to do anything about this - the user will just hit the button again.
});
} }
_revokeWidgetPermission() { _revokeWidgetPermission() {
console.warn('Revoking permission to load widget - ', this.state.widgetUrl); const roomId = this.props.room.roomId;
localStorage.removeItem(this.state.widgetPermissionId); console.info("Revoking permission for widget to load: " + this.props.eventId);
this.setState({hasPermissionToLoad: false}); const current = SettingsStore.getValue("allowedWidgets", roomId);
current[this.props.eventId] = false;
SettingsStore.setValue("allowedWidgets", roomId, SettingLevel.ROOM_ACCOUNT, current).then(() => {
this.setState({hasPermissionToLoad: false});
// Force the widget to be non-persistent // Force the widget to be non-persistent (able to be deleted/forgotten)
ActiveWidgetStore.destroyPersistentWidget(this.props.id); ActiveWidgetStore.destroyPersistentWidget(this.props.id);
const PersistedElement = sdk.getComponent("elements.PersistedElement"); const PersistedElement = sdk.getComponent("elements.PersistedElement");
PersistedElement.destroyElement(this._persistKey); PersistedElement.destroyElement(this._persistKey);
}).catch(err => {
console.error(err);
// We don't really need to do anything about this - the user will just hit the button again.
});
} }
formatAppTileName() { formatAppTileName() {
@ -720,6 +736,7 @@ AppTile.displayName ='AppTile';
AppTile.propTypes = { AppTile.propTypes = {
id: PropTypes.string.isRequired, id: PropTypes.string.isRequired,
eventId: PropTypes.string, // required for room widgets
url: PropTypes.string.isRequired, url: PropTypes.string.isRequired,
name: PropTypes.string.isRequired, name: PropTypes.string.isRequired,
room: PropTypes.object.isRequired, room: PropTypes.object.isRequired,

View file

@ -67,13 +67,15 @@ module.exports = createReactClass({
return ev.getStateKey() === ActiveWidgetStore.getPersistentWidgetId(); return ev.getStateKey() === ActiveWidgetStore.getPersistentWidgetId();
}); });
const app = WidgetUtils.makeAppConfig( const app = WidgetUtils.makeAppConfig(
appEvent.getStateKey(), appEvent.getContent(), appEvent.getSender(), persistentWidgetInRoomId, appEvent.getStateKey(), appEvent.getContent(), appEvent.getSender(),
persistentWidgetInRoomId, appEvent.getId(),
); );
const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, persistentWidgetInRoomId); const capWhitelist = WidgetUtils.getCapWhitelistForAppTypeInRoomId(app.type, persistentWidgetInRoomId);
const AppTile = sdk.getComponent('elements.AppTile'); const AppTile = sdk.getComponent('elements.AppTile');
return <AppTile return <AppTile
key={app.id} key={app.id}
id={app.id} id={app.id}
eventId={app.eventId}
url={app.url} url={app.url}
name={app.name} name={app.name}
type={app.type} type={app.type}

View file

@ -43,7 +43,8 @@ export default class ReactionsRowButtonTooltip extends React.PureComponent {
if (room) { if (room) {
const senders = []; const senders = [];
for (const reactionEvent of reactionEvents) { for (const reactionEvent of reactionEvents) {
const { name } = room.getMember(reactionEvent.getSender()); const member = room.getMember(reactionEvent.getSender());
const name = member ? member.name : reactionEvent.getSender();
senders.push(name); senders.push(name);
} }
const shortName = unicodeToShortcode(content); const shortName = unicodeToShortcode(content);

View file

@ -107,7 +107,9 @@ module.exports = createReactClass({
this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room), this.props.room.roomId, WidgetUtils.getRoomWidgets(this.props.room),
); );
return widgets.map((ev) => { return widgets.map((ev) => {
return WidgetUtils.makeAppConfig(ev.getStateKey(), ev.getContent(), ev.getSender()); return WidgetUtils.makeAppConfig(
ev.getStateKey(), ev.getContent(), ev.getSender(), ev.getRoomId(), ev.getId(),
);
}); });
}, },
@ -159,6 +161,7 @@ module.exports = createReactClass({
return (<AppTile return (<AppTile
key={app.id} key={app.id}
id={app.id} id={app.id}
eventId={app.eventId}
url={app.url} url={app.url}
name={app.name} name={app.name}
type={app.type} type={app.type}

View file

@ -25,7 +25,6 @@ import RoomViewStore from '../../../stores/RoomViewStore';
import Stickerpicker from './Stickerpicker'; import Stickerpicker from './Stickerpicker';
import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks'; import { makeRoomPermalink } from '../../../utils/permalinks/Permalinks';
import ContentMessages from '../../../ContentMessages'; import ContentMessages from '../../../ContentMessages';
import classNames from 'classnames';
import E2EIcon from './E2EIcon'; import E2EIcon from './E2EIcon';
function ComposerAvatar(props) { function ComposerAvatar(props) {
@ -353,13 +352,9 @@ export default class MessageComposer extends React.Component {
); );
} }
const wrapperClasses = classNames({
mx_MessageComposer_wrapper: true,
mx_MessageComposer_hasE2EIcon: !!this.props.e2eStatus,
});
return ( return (
<div className="mx_MessageComposer"> <div className="mx_MessageComposer">
<div className={wrapperClasses}> <div className="mx_MessageComposer_wrapper">
<div className="mx_MessageComposer_row"> <div className="mx_MessageComposer_row">
{ controls } { controls }
</div> </div>

View file

@ -460,13 +460,9 @@ export default class SlateMessageComposer extends React.Component {
const showFormatBar = this.state.showFormatting && this.state.inputState.isRichTextEnabled; const showFormatBar = this.state.showFormatting && this.state.inputState.isRichTextEnabled;
const wrapperClasses = classNames({
mx_MessageComposer_wrapper: true,
mx_MessageComposer_hasE2EIcon: !!this.props.e2eStatus,
});
return ( return (
<div className="mx_MessageComposer"> <div className="mx_MessageComposer">
<div className={wrapperClasses}> <div className="mx_MessageComposer_wrapper">
<div className="mx_MessageComposer_row"> <div className="mx_MessageComposer_row">
{ controls } { controls }
</div> </div>

View file

@ -27,7 +27,7 @@ import LanguageDropdown from "../../../elements/LanguageDropdown";
import AccessibleButton from "../../../elements/AccessibleButton"; import AccessibleButton from "../../../elements/AccessibleButton";
import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog"; import DeactivateAccountDialog from "../../../dialogs/DeactivateAccountDialog";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import {enumerateThemes} from "../../../../../theme"; import {enumerateThemes, ThemeWatcher} from "../../../../../theme";
import PlatformPeg from "../../../../../PlatformPeg"; import PlatformPeg from "../../../../../PlatformPeg";
import MatrixClientPeg from "../../../../../MatrixClientPeg"; import MatrixClientPeg from "../../../../../MatrixClientPeg";
import sdk from "../../../../.."; import sdk from "../../../../..";
@ -50,6 +50,7 @@ export default class GeneralUserSettingsTab extends React.Component {
this.state = { this.state = {
language: languageHandler.getCurrentLanguage(), language: languageHandler.getCurrentLanguage(),
theme: SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme"), theme: SettingsStore.getValueAt(SettingLevel.ACCOUNT, "theme"),
useSystemTheme: SettingsStore.getValueAt(SettingLevel.DEVICE, "use_system_theme"),
haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()), haveIdServer: Boolean(MatrixClientPeg.get().getIdentityServerUrl()),
serverSupportsSeparateAddAndBind: null, serverSupportsSeparateAddAndBind: null,
idServerHasUnsignedTerms: false, idServerHasUnsignedTerms: false,
@ -177,16 +178,25 @@ export default class GeneralUserSettingsTab extends React.Component {
// so remember what the value was before we tried to set it so we can revert // so remember what the value was before we tried to set it so we can revert
const oldTheme = SettingsStore.getValue('theme'); const oldTheme = SettingsStore.getValue('theme');
SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme).catch(() => { SettingsStore.setValue("theme", null, SettingLevel.ACCOUNT, newTheme).catch(() => {
dis.dispatch({action: 'set_theme', value: oldTheme}); dis.dispatch({action: 'recheck_theme'});
this.setState({theme: oldTheme}); this.setState({theme: oldTheme});
}); });
this.setState({theme: newTheme}); this.setState({theme: newTheme});
// The settings watcher doesn't fire until the echo comes back from the // The settings watcher doesn't fire until the echo comes back from the
// server, so to make the theme change immediately we need to manually // server, so to make the theme change immediately we need to manually
// do the dispatch now // do the dispatch now
dis.dispatch({action: 'set_theme', value: newTheme}); // XXX: The local echoed value appears to be unreliable, in particular
// when settings custom themes(!) so adding forceTheme to override
// the value from settings.
dis.dispatch({action: 'recheck_theme', forceTheme: newTheme});
}; };
_onUseSystemThemeChanged = (checked) => {
this.setState({useSystemTheme: checked});
dis.dispatch({action: 'recheck_theme'});
}
_onPasswordChangeError = (err) => { _onPasswordChangeError = (err) => {
// TODO: Figure out a design that doesn't involve replacing the current dialog // TODO: Figure out a design that doesn't involve replacing the current dialog
let errMsg = err.error || ""; let errMsg = err.error || "";
@ -297,11 +307,24 @@ export default class GeneralUserSettingsTab extends React.Component {
_renderThemeSection() { _renderThemeSection() {
const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag"); const SettingsFlag = sdk.getComponent("views.elements.SettingsFlag");
const themeWatcher = new ThemeWatcher();
let systemThemeSection;
if (themeWatcher.isSystemThemeSupported()) {
systemThemeSection = <div>
<SettingsFlag name="use_system_theme" level={SettingLevel.DEVICE}
onChange={this._onUseSystemThemeChanged}
/>
</div>;
}
return ( return (
<div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_themeSection"> <div className="mx_SettingsTab_section mx_GeneralUserSettingsTab_themeSection">
<span className="mx_SettingsTab_subheading">{_t("Theme")}</span> <span className="mx_SettingsTab_subheading">{_t("Theme")}</span>
{systemThemeSection}
<Field id="theme" label={_t("Theme")} element="select" <Field id="theme" label={_t("Theme")} element="select"
value={this.state.theme} onChange={this._onThemeChange}> value={this.state.theme} onChange={this._onThemeChange}
disabled={this.state.useSystemTheme}
>
{Object.entries(enumerateThemes()).map(([theme, text]) => { {Object.entries(enumerateThemes()).map(([theme, text]) => {
return <option key={theme} value={theme}>{text}</option>; return <option key={theme} value={theme}>{text}</option>;
})} })}

View file

@ -364,6 +364,7 @@
"Automatically replace plain text Emoji": "Automatically replace plain text Emoji", "Automatically replace plain text Emoji": "Automatically replace plain text Emoji",
"Mirror local video feed": "Mirror local video feed", "Mirror local video feed": "Mirror local video feed",
"Enable Community Filter Panel": "Enable Community Filter Panel", "Enable Community Filter Panel": "Enable Community Filter Panel",
"Match system dark mode setting": "Match system dark mode setting",
"Allow Peer-to-Peer for 1:1 calls": "Allow Peer-to-Peer for 1:1 calls", "Allow Peer-to-Peer for 1:1 calls": "Allow Peer-to-Peer for 1:1 calls",
"Send analytics data": "Send analytics data", "Send analytics data": "Send analytics data",
"Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device", "Never send encrypted messages to unverified devices from this device": "Never send encrypted messages to unverified devices from this device",

View file

@ -281,6 +281,11 @@ export const SETTINGS = {
supportedLevels: LEVELS_ACCOUNT_SETTINGS, supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: [], default: [],
}, },
"use_system_theme": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS,
default: true,
displayName: _td("Match system dark mode setting"),
},
"webRtcAllowPeerToPeer": { "webRtcAllowPeerToPeer": {
supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG, supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS_WITH_CONFIG,
displayName: _td('Allow Peer-to-Peer for 1:1 calls'), displayName: _td('Allow Peer-to-Peer for 1:1 calls'),

View file

@ -19,8 +19,75 @@ import {_t} from "./languageHandler";
export const DEFAULT_THEME = "light"; export const DEFAULT_THEME = "light";
import Tinter from "./Tinter"; import Tinter from "./Tinter";
import dis from "./dispatcher";
import SettingsStore from "./settings/SettingsStore"; import SettingsStore from "./settings/SettingsStore";
export class ThemeWatcher {
static _instance = null;
constructor() {
this._themeWatchRef = null;
this._systemThemeWatchRef = null;
this._dispatcherRef = null;
// we have both here as each may either match or not match, so by having both
// we can get the tristate of dark/light/unsupported
this._preferDark = global.matchMedia("(prefers-color-scheme: dark)");
this._preferLight = global.matchMedia("(prefers-color-scheme: light)");
this._currentTheme = this.getEffectiveTheme();
}
start() {
this._themeWatchRef = SettingsStore.watchSetting("theme", null, this._onChange);
this._systemThemeWatchRef = SettingsStore.watchSetting("use_system_theme", null, this._onChange);
this._preferDark.addEventListener('change', this._onChange);
this._preferLight.addEventListener('change', this._onChange);
this._dispatcherRef = dis.register(this._onAction);
}
stop() {
this._preferDark.removeEventListener('change', this._onChange);
this._preferLight.removeEventListener('change', this._onChange);
SettingsStore.unwatchSetting(this._systemThemeWatchRef);
SettingsStore.unwatchSetting(this._themeWatchRef);
dis.unregister(this._dispatcherRef);
}
_onChange = () => {
this.recheck();
}
_onAction = (payload) => {
if (payload.action === 'recheck_theme') {
// XXX forceTheme
this.recheck(payload.forceTheme);
}
}
// XXX: forceTheme param aded here as local echo appears to be unreliable
// https://github.com/vector-im/riot-web/issues/11443
recheck(forceTheme) {
const oldTheme = this._currentTheme;
this._currentTheme = forceTheme === undefined ? this.getEffectiveTheme() : forceTheme;
if (oldTheme !== this._currentTheme) {
setTheme(this._currentTheme);
}
}
getEffectiveTheme() {
if (SettingsStore.getValue('use_system_theme')) {
if (this._preferDark.matches) return 'dark';
if (this._preferLight.matches) return 'light';
}
return SettingsStore.getValue('theme');
}
isSystemThemeSupported() {
return this._preferDark.matches || this._preferLight.matches;
}
}
export function enumerateThemes() { export function enumerateThemes() {
const BUILTIN_THEMES = { const BUILTIN_THEMES = {
"light": _t("Light theme"), "light": _t("Light theme"),
@ -60,30 +127,17 @@ function getCustomTheme(themeName) {
return customTheme; return customTheme;
} }
/**
* Gets the underlying theme name for the given theme. This is usually the theme or
* CSS resource that the theme relies upon to load.
* @param {string} theme The theme name to get the base of.
* @returns {string} The base theme (typically "light" or "dark").
*/
export function getBaseTheme(theme) {
if (!theme) return "light";
if (theme.startsWith("custom-")) {
const customTheme = getCustomTheme(theme.substr(7));
return customTheme.is_dark ? "dark-custom" : "light-custom";
}
return theme; // it's probably a base theme
}
/** /**
* Called whenever someone changes the theme * Called whenever someone changes the theme
* Async function that returns once the theme has been set
* (ie. the CSS has been loaded)
* *
* @param {string} theme new theme * @param {string} theme new theme
*/ */
export function setTheme(theme) { export async function setTheme(theme) {
if (!theme) { if (!theme) {
theme = SettingsStore.getValue("theme"); const themeWatcher = new ThemeWatcher();
theme = themeWatcher.getEffectiveTheme();
} }
let stylesheetName = theme; let stylesheetName = theme;
if (theme.startsWith("custom-")) { if (theme.startsWith("custom-")) {
@ -122,38 +176,41 @@ export function setTheme(theme) {
styleElements[stylesheetName].disabled = false; styleElements[stylesheetName].disabled = false;
const switchTheme = function() { return new Promise((resolve) => {
// we re-enable our theme here just in case we raced with another const switchTheme = function() {
// theme set request as per https://github.com/vector-im/riot-web/issues/5601. // we re-enable our theme here just in case we raced with another
// We could alternatively lock or similar to stop the race, but // theme set request as per https://github.com/vector-im/riot-web/issues/5601.
// this is probably good enough for now. // We could alternatively lock or similar to stop the race, but
styleElements[stylesheetName].disabled = false; // this is probably good enough for now.
Object.values(styleElements).forEach((a) => { styleElements[stylesheetName].disabled = false;
if (a == styleElements[stylesheetName]) return; Object.values(styleElements).forEach((a) => {
a.disabled = true; if (a == styleElements[stylesheetName]) return;
}); a.disabled = true;
Tinter.setTheme(theme); });
}; Tinter.setTheme(theme);
resolve();
};
// turns out that Firefox preloads the CSS for link elements with // turns out that Firefox preloads the CSS for link elements with
// the disabled attribute, but Chrome doesn't. // the disabled attribute, but Chrome doesn't.
let cssLoaded = false; let cssLoaded = false;
styleElements[stylesheetName].onload = () => { styleElements[stylesheetName].onload = () => {
switchTheme(); switchTheme();
}; };
for (let i = 0; i < document.styleSheets.length; i++) { for (let i = 0; i < document.styleSheets.length; i++) {
const ss = document.styleSheets[i]; const ss = document.styleSheets[i];
if (ss && ss.href === styleElements[stylesheetName].href) { if (ss && ss.href === styleElements[stylesheetName].href) {
cssLoaded = true; cssLoaded = true;
break; break;
}
} }
}
if (cssLoaded) { if (cssLoaded) {
styleElements[stylesheetName].onload = undefined; styleElements[stylesheetName].onload = undefined;
switchTheme(); switchTheme();
} }
});
} }

View file

@ -400,7 +400,7 @@ export default class WidgetUtils {
return client.setAccountData('m.widgets', userWidgets); return client.setAccountData('m.widgets', userWidgets);
} }
static makeAppConfig(appId, app, senderUserId, roomId) { static makeAppConfig(appId, app, senderUserId, roomId, eventId) {
const myUserId = MatrixClientPeg.get().credentials.userId; const myUserId = MatrixClientPeg.get().credentials.userId;
const user = MatrixClientPeg.get().getUser(myUserId); const user = MatrixClientPeg.get().getUser(myUserId);
const params = { const params = {
@ -419,6 +419,7 @@ export default class WidgetUtils {
app.creatorUserId = senderUserId; app.creatorUserId = senderUserId;
app.id = appId; app.id = appId;
app.eventId = eventId;
app.name = app.name || app.type; app.name = app.name || app.type;
if (app.data) { if (app.data) {

View file

@ -244,6 +244,11 @@
"@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-coverage" "*"
"@types/istanbul-lib-report" "*" "@types/istanbul-lib-report" "*"
"@types/json-schema@^7.0.3":
version "7.0.3"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636"
integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A==
"@types/minimatch@*": "@types/minimatch@*":
version "3.0.3" version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d"
@ -293,6 +298,28 @@
dependencies: dependencies:
"@types/yargs-parser" "*" "@types/yargs-parser" "*"
"@typescript-eslint/experimental-utils@^2.5.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.8.0.tgz#208b4164d175587e9b03ce6fea97d55f19c30ca9"
integrity sha512-jZ05E4SxCbbXseQGXOKf3ESKcsGxT8Ucpkp1jiVp55MGhOvZB2twmWKf894PAuVQTCgbPbJz9ZbRDqtUWzP8xA==
dependencies:
"@types/json-schema" "^7.0.3"
"@typescript-eslint/typescript-estree" "2.8.0"
eslint-scope "^5.0.0"
"@typescript-eslint/typescript-estree@2.8.0":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.8.0.tgz#fcc3fe6532840085d29b75432c8a59895876aeca"
integrity sha512-ksvjBDTdbAQ04cR5JyFSDX113k66FxH1tAXmi+dj6hufsl/G0eMc/f1GgLjEVPkYClDbRKv+rnBFuE5EusomUw==
dependencies:
debug "^4.1.1"
eslint-visitor-keys "^1.1.0"
glob "^7.1.6"
is-glob "^4.0.1"
lodash.unescape "4.0.1"
semver "^6.3.0"
tsutils "^3.17.1"
"@webassemblyjs/ast@1.8.5": "@webassemblyjs/ast@1.8.5":
version "1.8.5" version "1.8.5"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359"
@ -2881,6 +2908,13 @@ eslint-plugin-flowtype@^2.30.0:
dependencies: dependencies:
lodash "^4.17.10" lodash "^4.17.10"
eslint-plugin-jest@^23.0.4:
version "23.0.4"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.0.4.tgz#1ab81ffe3b16c5168efa72cbd4db14d335092aa0"
integrity sha512-OaP8hhT8chJNodUPvLJ6vl8gnalcsU/Ww1t9oR3HnGdEWjm/DdCCUXLOral+IPGAeWu/EwgVQCK/QtxALpH1Yw==
dependencies:
"@typescript-eslint/experimental-utils" "^2.5.0"
eslint-plugin-react-hooks@^2.0.1: eslint-plugin-react-hooks@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.0.1.tgz#e898ec26a0a335af6f7b0ad1f0bedda7143ed756" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.0.1.tgz#e898ec26a0a335af6f7b0ad1f0bedda7143ed756"
@ -2914,6 +2948,14 @@ eslint-scope@^4.0.3:
esrecurse "^4.1.0" esrecurse "^4.1.0"
estraverse "^4.1.1" estraverse "^4.1.1"
eslint-scope@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.0.0.tgz#e87c8887c73e8d1ec84f1ca591645c358bfc8fb9"
integrity sha512-oYrhJW7S0bxAFDvWqzvMPRm6pcgcnWc4QnofCAqRTRfQC0JcwenzGglTtsLyIuuWFfkqDG9vz67cnttSd53djw==
dependencies:
esrecurse "^4.1.0"
estraverse "^4.1.1"
eslint-utils@^1.3.1: eslint-utils@^1.3.1:
version "1.4.2" version "1.4.2"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.2.tgz#166a5180ef6ab7eb462f162fd0e6f2463d7309ab"
@ -2921,7 +2963,7 @@ eslint-utils@^1.3.1:
dependencies: dependencies:
eslint-visitor-keys "^1.0.0" eslint-visitor-keys "^1.0.0"
eslint-visitor-keys@^1.0.0: eslint-visitor-keys@^1.0.0, eslint-visitor-keys@^1.1.0:
version "1.1.0" version "1.1.0"
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2"
integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==
@ -3704,6 +3746,18 @@ glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
once "^1.3.0" once "^1.3.0"
path-is-absolute "^1.0.0" path-is-absolute "^1.0.0"
glob@^7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
fs.realpath "^1.0.0"
inflight "^1.0.4"
inherits "2"
minimatch "^3.0.4"
once "^1.3.0"
path-is-absolute "^1.0.0"
global-modules@2.0.0, global-modules@^2.0.0: global-modules@2.0.0, global-modules@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-2.0.0.tgz#997605ad2345f27f51539bea26574421215c7780"
@ -5028,6 +5082,11 @@ lodash.mergewith@^4.6.1:
resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55"
integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==
lodash.unescape@4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.unescape/-/lodash.unescape-4.0.1.tgz#bf2249886ce514cda112fae9218cdc065211fc9c"
integrity sha1-vyJJiGzlFM2hEvrpIYzcBlIR/Jw=
lodash@^4.1.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.2.1: lodash@^4.1.1, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.2.1:
version "4.17.15" version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
@ -7139,7 +7198,7 @@ selection-is-backward@^1.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
semver@6.3.0: semver@6.3.0, semver@^6.3.0:
version "6.3.0" version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
@ -8076,11 +8135,18 @@ trough@^1.0.0:
resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.4.tgz#3b52b1f13924f460c3fbfd0df69b587dbcbc762e" resolved "https://registry.yarnpkg.com/trough/-/trough-1.0.4.tgz#3b52b1f13924f460c3fbfd0df69b587dbcbc762e"
integrity sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q== integrity sha512-tdzBRDGWcI1OpPVmChbdSKhvSVurznZ8X36AYURAcl+0o2ldlCY2XPzyXNNxwJwwyIU+rIglTCG4kxtNKBQH7Q==
tslib@^1.9.0: tslib@^1.8.1, tslib@^1.9.0:
version "1.10.0" version "1.10.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
tsutils@^3.17.1:
version "3.17.1"
resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759"
integrity sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==
dependencies:
tslib "^1.8.1"
tty-browserify@0.0.0: tty-browserify@0.0.0:
version "0.0.0" version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"