diff --git a/src/components/views/settings/tabs/room/NotificationSettingsTab.js b/src/components/views/settings/tabs/room/NotificationSettingsTab.js index fa56fa2cb65..4c0dbd8d11d 100644 --- a/src/components/views/settings/tabs/room/NotificationSettingsTab.js +++ b/src/components/views/settings/tabs/room/NotificationSettingsTab.js @@ -19,10 +19,12 @@ import PropTypes from 'prop-types'; import {_t} from "../../../../../languageHandler"; import {MatrixClientPeg} from "../../../../../MatrixClientPeg"; import AccessibleButton from "../../../elements/AccessibleButton"; -import Notifier from "../../../../../Notifier"; import SettingsStore from '../../../../../settings/SettingsStore'; import {SettingLevel} from "../../../../../settings/SettingLevel"; import {replaceableComponent} from "../../../../../utils/replaceableComponent"; +import Field from "../../../elements/Field"; +import Modal from '../../../../../Modal'; +import * as sdk from '../../../../../index'; @replaceableComponent("views.settings.tabs.room.NotificationsSettingsTab") export default class NotificationsSettingsTab extends React.Component { @@ -37,37 +39,91 @@ export default class NotificationsSettingsTab extends React.Component { this.state = { currentSound: "default", - uploadedFile: null, + currentSoundReplaced: false, + selected: null, + soundLibrary: {}, }; } // TODO: [REACT-WARNING] Replace component with real class, use constructor for refs UNSAFE_componentWillMount() { // eslint-disable-line camelcase - const soundData = Notifier.getSoundForRoom(this.props.roomId); - if (!soundData) { - return; - } - this.setState({currentSound: soundData.name || soundData.url}); + const soundData = SettingsStore.getValue("notificationSound", this.props.roomId); + const soundLibrary = SettingsStore.getValue("soundLibrary", null); + console.log(soundLibrary); + const selected = (soundData === null) ? "default" : soundData.name; + this.setState({ + currentSound: soundData.name || soundData.url, + selected: selected, + soundLibrary: soundLibrary, + }); } - async _triggerUploader(e) { - e.stopPropagation(); - e.preventDefault(); - + async _triggerUploader() { this._soundUpload.current.click(); } async _onSoundUploadChanged(e) { if (!e.target.files || !e.target.files.length) { - this.setState({ - uploadedFile: null, - }); return; } const file = e.target.files[0]; + const soundLibrary = this.state.soundLibrary; + + if (file.name in soundLibrary) { + const QuestionDialog = sdk.getComponent('dialogs.QuestionDialog'); + Modal.createDialog(QuestionDialog, { + title: _t("Replace File"), + description: _t("There already exists a file with this name. " + + "Are you sure, you want to replace it?"), + button: _t("Replace"), + onFinished: () => { + this.setState({currentSoundReplaced: true}); + this._uploadSound(file); + }, + }); + + return; + } + this._uploadSound(file); + } + + async _uploadSound(file) { + let type = file.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( + file, { + type, + }, + ); + + const soundJSON = { + name: file.name, + type: type, + size: file.size, + url, + }; + + const soundLibrary = this.state.soundLibrary; + soundLibrary[soundJSON.name] = soundJSON; + + await SettingsStore.setValue( + "soundLibrary", + null, + SettingLevel.ACCOUNT, + soundLibrary, + ); + this.setState({ - uploadedFile: file, + soundLibrary: soundLibrary, + selected: soundJSON.name, }); } @@ -86,45 +142,30 @@ export default class NotificationsSettingsTab extends React.Component { } async _saveSound() { - if (!this.state.uploadedFile) { + if (!this.state.selected) { 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"; + if (this.state.selected == "default") { + this._clearSound(); + return; } - const url = await MatrixClientPeg.get().uploadContent( - this.state.uploadedFile, { - type, - }, - ); + const soundJSON = this.state.soundLibrary[this.state.selected]; await SettingsStore.setValue( "notificationSound", this.props.roomId, SettingLevel.ROOM_ACCOUNT, - { - name: this.state.uploadedFile.name, - type: type, - size: this.state.uploadedFile.size, - url, - }, + soundJSON, ); this.setState({ - uploadedFile: null, - currentSound: this.state.uploadedFile.name, + currentSound: soundJSON.name, }); } - _clearSound(e) { - e.stopPropagation(); - e.preventDefault(); + _clearSound() { SettingsStore.setValue( "notificationSound", this.props.roomId, @@ -137,16 +178,32 @@ export default class NotificationsSettingsTab extends React.Component { }); } - render() { - let currentUploadedFile = null; - if (this.state.uploadedFile) { - currentUploadedFile = ( -
- {_t("Uploaded sound")}: {this.state.uploadedFile.name} -
- ); + _onReset() { + this.setState({ + selected: this.state.currentSound, + }); + } + + _onChangeSelection(e) { + e.stopPropagation(); + e.preventDefault(); + + if (e.target.value === "upload") { + this._triggerUploader(); + return; } + this.setState({ + selected: e.target.value, + }); + } + + + render() { + const notChanged = this.state.currentSound == this.state.selected && !this.state.currentSoundReplaced; + const soundOptions = Object.keys(this.state.soundLibrary) + .map((sound, i) => ); + return (
{_t("Notifications")}
@@ -154,23 +211,34 @@ export default class NotificationsSettingsTab extends React.Component { {_t("Sounds")}
{_t("Notification sound")}: {this.state.currentSound}
- - {_t("Reset")} -
-

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

+

{_t("Select a custom sound")}

+ + + {soundOptions} + +
- +
- {currentUploadedFile} - - - {_t("Browse")} + + {_t("Reset")} - - + {_t("Save")}
diff --git a/src/i18n/strings/en_EN.json b/src/i18n/strings/en_EN.json index dcad970300f..8178e56d0f6 100644 --- a/src/i18n/strings/en_EN.json +++ b/src/i18n/strings/en_EN.json @@ -1352,11 +1352,13 @@ "Bridges": "Bridges", "URL Previews": "URL Previews", "Room Addresses": "Room Addresses", - "Uploaded sound": "Uploaded sound", + "Replace File": "Replace File", + "There already exists a file with this name. Are you sure, you want to replace it?": "There already exists a file with this name. Are you sure, you want to replace it?", + "Replace": "Replace", "Sounds": "Sounds", "Notification sound": "Notification sound", - "Set a new custom sound": "Set a new custom sound", - "Browse": "Browse", + "Select a custom sound": "Select a custom sound", + "upload": "upload", "Change room avatar": "Change room avatar", "Change room name": "Change room name", "Change main address for the room": "Change main address for the room", diff --git a/src/settings/Settings.ts b/src/settings/Settings.ts index 1497a2208d7..9dbc642afe9 100644 --- a/src/settings/Settings.ts +++ b/src/settings/Settings.ts @@ -561,6 +561,10 @@ export const SETTINGS: {[setting: string]: ISetting} = { supportedLevels: LEVELS_DEVICE_ONLY_SETTINGS, default: true, }, + "soundLibrary": { + supportedLevels: [SettingLevel.ACCOUNT], + default: {}, + }, "enableWidgetScreenshots": { supportedLevels: LEVELS_ACCOUNT_SETTINGS, displayName: _td('Enable widget screenshots on supported widgets'),