From 63ab7736ca4ab0ee4cfccc8f7b0957b2709e8d2f Mon Sep 17 00:00:00 2001 From: Will Hunt Date: Fri, 19 Apr 2019 16:27:30 +0100 Subject: [PATCH] Add a fancy room tab and uploader --- src/Notifier.js | 19 ++- .../views/dialogs/RoomSettingsDialog.js | 8 +- .../tabs/room/NotificationSettingsTab.js | 139 ++++++++++++++++++ src/i18n/strings/en_EN.json | 6 +- 4 files changed, 166 insertions(+), 6 deletions(-) create mode 100644 src/components/views/settings/tabs/room/NotificationSettingsTab.js diff --git a/src/Notifier.js b/src/Notifier.js index e8d0c27a31..041b91f4b2 100644 --- a/src/Notifier.js +++ b/src/Notifier.js @@ -96,11 +96,19 @@ const Notifier = { } }, - _getSoundForRoom: async function(room) { + setRoomSound: function(room, soundData) { + return MatrixClientPeg.get().setRoomAccountData(room.roomId, "uk.half-shot.notification.sound", soundData); + }, + + clearRoomSound: function(room) { + return room.setAccountData("uk.half-shot.notification.sound", null); + }, + + getSoundForRoom: async function(room) { // We do no caching here because the SDK caches the event content // and the browser will cache the sound. let ev = await room.getAccountData("uk.half-shot.notification.sound"); - if (!ev) { + if (!ev || !ev.getContent()) { // Check the account data. ev = await MatrixClientPeg.get().getAccountData("uk.half-shot.notification.sound"); if (!ev) { @@ -112,15 +120,18 @@ const Notifier = { console.warn(`${room.roomId} has custom notification sound event, but no url key`); return null; } + return { url: MatrixClientPeg.get().mxcUrlToHttp(content.url), + name: content.name, type: content.type, + size: content.size, }; }, _playAudioNotification: function(ev, room) { - this._getSoundForRoom(room).then((sound) => { - console.log(`Got sound ${sound || "default"} for ${room.roomId}`); + this.getSoundForRoom(room).then((sound) => { + console.log(`Got sound ${sound.name || "default"} for ${room.roomId}`); // XXX: How do we ensure this is a sound file and not // going to be exploited? const selector = document.querySelector(sound ? `audio[src='${sound.url}']` : "#messageAudio"); diff --git a/src/components/views/dialogs/RoomSettingsDialog.js b/src/components/views/dialogs/RoomSettingsDialog.js index 05ed262078..733c5002f5 100644 --- a/src/components/views/dialogs/RoomSettingsDialog.js +++ b/src/components/views/dialogs/RoomSettingsDialog.js @@ -22,7 +22,8 @@ import AdvancedRoomSettingsTab from "../settings/tabs/room/AdvancedRoomSettingsT import RolesRoomSettingsTab from "../settings/tabs/room/RolesRoomSettingsTab"; import GeneralRoomSettingsTab from "../settings/tabs/room/GeneralRoomSettingsTab"; import SecurityRoomSettingsTab from "../settings/tabs/room/SecurityRoomSettingsTab"; -import sdk from "../../../index"; +import NotificationSettingsTab from "../settings/tabs/room/NotificationSettingsTab"; +import sdk from "../../../index";RolesRoomSettingsTab import MatrixClientPeg from "../../../MatrixClientPeg"; export default class RoomSettingsDialog extends React.Component { @@ -49,6 +50,11 @@ export default class RoomSettingsDialog extends React.Component { "mx_RoomSettingsDialog_rolesIcon", , )); + tabs.push(new Tab( + _td("Notifications"), + "mx_RoomSettingsDialog_rolesIcon", + , + )) tabs.push(new Tab( _td("Advanced"), "mx_RoomSettingsDialog_warningIcon", diff --git a/src/components/views/settings/tabs/room/NotificationSettingsTab.js b/src/components/views/settings/tabs/room/NotificationSettingsTab.js new file mode 100644 index 0000000000..35a223a1d8 --- /dev/null +++ b/src/components/views/settings/tabs/room/NotificationSettingsTab.js @@ -0,0 +1,139 @@ +/* +Copyright 2019 New Vector Ltd + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +import React from 'react'; +import PropTypes from 'prop-types'; +import {_t} from "../../../../../languageHandler"; +import MatrixClientPeg from "../../../../../MatrixClientPeg"; +import sdk from "../../../../.."; +import AccessibleButton from "../../../elements/AccessibleButton"; +import Modal from "../../../../../Modal"; +import dis from "../../../../../dispatcher"; +import Notifier from "../../../../../Notifier"; + +export default class NotificationsSettingsTab extends React.Component { + static propTypes = { + roomId: PropTypes.string.isRequired, + closeSettingsFn: PropTypes.func.isRequired, + }; + + constructor() { + super(); + + this.state = { + currentSound: "default", + uploadedFile: null, + }; + } + + componentWillMount() { + const room = MatrixClientPeg.get().getRoom(this.props.roomId); + Notifier.getSoundForRoom(room).then((soundData) => { + if (!soundData) { + return; + } + this.setState({currentSound: soundData.name || soundData.url}) + }) + } + + _onSoundUploadChanged(e) { + if (!e.target.files || !e.target.files.length) { + this.setState({ + uploadedFile: null, + }); + return; + } + + const file = e.target.files[0]; + this.setState({ + uploadedFile: file, + }); + } + + async _saveSound (e) { + e.stopPropagation(); + e.preventDefault(); + if (!this.state.uploadedFile) { + return; + } + let type = this.state.uploadedFile.type; + if (type === "video/ogg") { + // XXX: I've observed browsers allowing users to pick a audio/ogg files, + // and then calling it a video/ogg. This is a lame hack, but man browsers + // suck at detecting mimetypes. + type = "audio/ogg"; + } + const url = await MatrixClientPeg.get().uploadContent( + this.state.uploadedFile, { + type, + }, + ); + + const room = MatrixClientPeg.get().getRoom(this.props.roomId); + + await Notifier.setRoomSound(room, { + name: this.state.uploadedFile.name, + type: type, + size: this.state.uploadedFile.size, + url, + }); + + this.setState({ + uploadedFile: null, + uploadedFileUrl: null, + currentSound: this.state.uploadedFile.name, + }); + } + + _clearSound (e) { + e.stopPropagation(); + e.preventDefault(); + const room = client.getRoom(this.props.roomId); + Notifier.clearRoomSound(room); + + this.setState({ + currentSound: "default", + }); + } + + render() { + const client = MatrixClientPeg.get(); + + return ( +
+
{_t("Notifications")}
+
+ {_t("Sounds")} +
+ {_t("Notification sound")}: {this.state.currentSound} +
+
+

{_t("Set a new custom sound")}

+
+ + + {_t("Save")} + +
+ + {_t("Reset to default sound")} + +
+
+
+ ); + } +} diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index 2b50fd9ad3..7f94bdc9cd 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1614,5 +1614,9 @@ "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.": "If you didn't remove the recovery method, an attacker may be trying to access your account. Change your account password and set a new recovery method immediately in Settings.", "Failed to set direct chat tag": "Failed to set direct chat tag", "Failed to remove tag %(tagName)s from room": "Failed to remove tag %(tagName)s from room", - "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room" + "Failed to add tag %(tagName)s to room": "Failed to add tag %(tagName)s to room", + "Sounds": "Sounds", + "Notification sound": "Notification sound", + "Set a new custom sound": "Set a new custom sound", + "Reset to default sound": "Reset to default sound" }