Rebuild SettingsStore to be better supported
This does away with the room- and account-style settings, and just replaces them with `supportedLevels`. The handlers have also been moved out to be in better support of the other options, like SdkConfig and per-room-per-device. Signed-off-by: Travis Ralston <travpc@gmail.com>
This commit is contained in:
parent
c43bf336a9
commit
989bdcf5fb
10 changed files with 736 additions and 431 deletions
|
@ -1,431 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2017 Travis Ralston
|
|
||||||
|
|
||||||
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 Promise from 'bluebird';
|
|
||||||
import MatrixClientPeg from './MatrixClientPeg';
|
|
||||||
|
|
||||||
const SETTINGS = [
|
|
||||||
/*
|
|
||||||
// EXAMPLE SETTING
|
|
||||||
{
|
|
||||||
name: "my-setting",
|
|
||||||
type: "room", // or "account"
|
|
||||||
ignoreLevels: [], // options: "device", "room-account", "account", "room"
|
|
||||||
// "room-account" and "room" don't apply for `type: account`.
|
|
||||||
defaults: {
|
|
||||||
your: "defaults",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TODO: Populate this
|
|
||||||
];
|
|
||||||
|
|
||||||
// This controls the priority of particular handlers. Handler order should match the
|
|
||||||
// documentation throughout this file, as should the `types`. The priority is directly
|
|
||||||
// related to the index in the map, where index 0 is highest preference.
|
|
||||||
const PRIORITY_MAP = [
|
|
||||||
{level: 'device', settingClass: DeviceSetting, types: ['room', 'account']},
|
|
||||||
{level: 'room-account', settingClass: RoomAccountSetting, types: ['room']},
|
|
||||||
{level: 'account', settingClass: AccountSetting, types: ['room', 'account']},
|
|
||||||
{level: 'room', settingClass: RoomSetting, types: ['room']},
|
|
||||||
{level: 'default', settingClass: DefaultSetting, types: ['room', 'account']},
|
|
||||||
|
|
||||||
// TODO: Add support for 'legacy' settings (old events, etc)
|
|
||||||
// TODO: Labs handler? (or make UserSettingsStore use this as a backend)
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Controls and manages application settings at different levels through a variety of
|
|
||||||
* backends. Settings may be overridden at each level to provide the user with more
|
|
||||||
* options for customization and tailoring of their experience. These levels are most
|
|
||||||
* notably at the device, room, and account levels. The preferred order of levels is:
|
|
||||||
* - per-device
|
|
||||||
* - per-account in a particular room
|
|
||||||
* - per-account
|
|
||||||
* - per-room
|
|
||||||
* - defaults (as defined here)
|
|
||||||
*
|
|
||||||
* There are two types of settings: Account and Room.
|
|
||||||
*
|
|
||||||
* Account Settings use the same preferences described above, but do not look at the
|
|
||||||
* per-account in a particular room or the per-room levels. Account Settings are best
|
|
||||||
* used for things like which theme the user would prefer.
|
|
||||||
*
|
|
||||||
* Room settings use the exact preferences described above. Room Settings are best
|
|
||||||
* suited for settings which room administrators may want to define a default for the
|
|
||||||
* room members, or where users may want an individual room to be different. Using the
|
|
||||||
* setting definitions, particular preferences may be excluded to prevent, for example,
|
|
||||||
* room administrators from defining that all messages should have timestamps when the
|
|
||||||
* user may not want that. An example of a Room Setting would be URL previews.
|
|
||||||
*/
|
|
||||||
export default class GranularSettingStore {
|
|
||||||
/**
|
|
||||||
* Gets the content for an account setting.
|
|
||||||
* @param {string} name The name of the setting to lookup
|
|
||||||
* @returns {Promise<*>} Resolves to the content for the setting, or null if the
|
|
||||||
* value cannot be found.
|
|
||||||
*/
|
|
||||||
static getAccountSetting(name) {
|
|
||||||
const handlers = GranularSettingStore._getHandlers('account');
|
|
||||||
const initFn = (SettingClass) => new SettingClass('account', name);
|
|
||||||
return GranularSettingStore._iterateHandlers(handlers, initFn);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the content for a room setting.
|
|
||||||
* @param {string} name The name of the setting to lookup
|
|
||||||
* @param {string} roomId The room ID to lookup the setting for
|
|
||||||
* @returns {Promise<*>} Resolves to the content for the setting, or null if the
|
|
||||||
* value cannot be found.
|
|
||||||
*/
|
|
||||||
static getRoomSetting(name, roomId) {
|
|
||||||
const handlers = GranularSettingStore._getHandlers('room');
|
|
||||||
const initFn = (SettingClass) => new SettingClass('room', name, roomId);
|
|
||||||
return GranularSettingStore._iterateHandlers(handlers, initFn);
|
|
||||||
}
|
|
||||||
|
|
||||||
static _iterateHandlers(handlers, initFn) {
|
|
||||||
let index = 0;
|
|
||||||
const wrapperFn = () => {
|
|
||||||
// If we hit the end with no result, return 'not found'
|
|
||||||
if (handlers.length >= index) return null;
|
|
||||||
|
|
||||||
// Get the handler, increment the index, and create a setting object
|
|
||||||
const handler = handlers[index++];
|
|
||||||
const setting = initFn(handler.settingClass);
|
|
||||||
|
|
||||||
// Try to read the value of the setting. If we get nothing for a value,
|
|
||||||
// then try the next handler. Otherwise, return the value early.
|
|
||||||
return Promise.resolve(setting.getValue()).then((value) => {
|
|
||||||
if (!value) return wrapperFn();
|
|
||||||
return value;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return wrapperFn();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the content for a particular account setting at a given level in the hierarchy.
|
|
||||||
* If the setting does not exist at the given level, this will attempt to create it. The
|
|
||||||
* default level may not be modified.
|
|
||||||
* @param {string} name The name of the setting.
|
|
||||||
* @param {string} level The level to set the value of. Either 'device' or 'account'.
|
|
||||||
* @param {Object} content The value for the setting, or null to clear the level's value.
|
|
||||||
* @returns {Promise} Resolves when completed
|
|
||||||
*/
|
|
||||||
static setAccountSetting(name, level, content) {
|
|
||||||
const handler = GranularSettingStore._getHandler('account', level);
|
|
||||||
if (!handler) throw new Error("Missing account setting handler for " + name + " at " + level);
|
|
||||||
|
|
||||||
const SettingClass = handler.settingClass;
|
|
||||||
const setting = new SettingClass('account', name);
|
|
||||||
return Promise.resolve(setting.setValue(content));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the content for a particular room setting at a given level in the hierarchy. If
|
|
||||||
* the setting does not exist at the given level, this will attempt to create it. The
|
|
||||||
* default level may not be modified.
|
|
||||||
* @param {string} name The name of the setting.
|
|
||||||
* @param {string} level The level to set the value of. One of 'device', 'room-account',
|
|
||||||
* 'account', or 'room'.
|
|
||||||
* @param {string} roomId The room ID to set the value of.
|
|
||||||
* @param {Object} content The value for the setting, or null to clear the level's value.
|
|
||||||
* @returns {Promise} Resolves when completed
|
|
||||||
*/
|
|
||||||
static setRoomSetting(name, level, roomId, content) {
|
|
||||||
const handler = GranularSettingStore._getHandler('room', level);
|
|
||||||
if (!handler) throw new Error("Missing room setting handler for " + name + " at " + level);
|
|
||||||
|
|
||||||
const SettingClass = handler.settingClass;
|
|
||||||
const setting = new SettingClass('room', name, roomId);
|
|
||||||
return Promise.resolve(setting.setValue(content));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks to ensure the current user may set the given account setting.
|
|
||||||
* @param {string} name The name of the setting.
|
|
||||||
* @param {string} level The level to check at. Either 'device' or 'account'.
|
|
||||||
* @returns {boolean} Whether or not the current user may set the account setting value.
|
|
||||||
*/
|
|
||||||
static canSetAccountSetting(name, level) {
|
|
||||||
const handler = GranularSettingStore._getHandler('account', level);
|
|
||||||
if (!handler) return false;
|
|
||||||
|
|
||||||
const SettingClass = handler.settingClass;
|
|
||||||
const setting = new SettingClass('account', name);
|
|
||||||
return setting.canSetValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks to ensure the current user may set the given room setting.
|
|
||||||
* @param {string} name The name of the setting.
|
|
||||||
* @param {string} level The level to check at. One of 'device', 'room-account', 'account',
|
|
||||||
* or 'room'.
|
|
||||||
* @param {string} roomId The room ID to check in.
|
|
||||||
* @returns {boolean} Whether or not the current user may set the room setting value.
|
|
||||||
*/
|
|
||||||
static canSetRoomSetting(name, level, roomId) {
|
|
||||||
const handler = GranularSettingStore._getHandler('room', level);
|
|
||||||
if (!handler) return false;
|
|
||||||
|
|
||||||
const SettingClass = handler.settingClass;
|
|
||||||
const setting = new SettingClass('room', name, roomId);
|
|
||||||
return setting.canSetValue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes an account setting at a given level, forcing the level to inherit from an
|
|
||||||
* earlier stage in the hierarchy.
|
|
||||||
* @param {string} name The name of the setting.
|
|
||||||
* @param {string} level The level to clear. Either 'device' or 'account'.
|
|
||||||
*/
|
|
||||||
static removeAccountSetting(name, level) {
|
|
||||||
// This is just a convenience method.
|
|
||||||
GranularSettingStore.setAccountSetting(name, level, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes a room setting at a given level, forcing the level to inherit from an earlier
|
|
||||||
* stage in the hierarchy.
|
|
||||||
* @param {string} name The name of the setting.
|
|
||||||
* @param {string} level The level to clear. One of 'device', 'room-account', 'account',
|
|
||||||
* or 'room'.
|
|
||||||
* @param {string} roomId The room ID to clear the setting on.
|
|
||||||
*/
|
|
||||||
static removeRoomSetting(name, level, roomId) {
|
|
||||||
// This is just a convenience method.
|
|
||||||
GranularSettingStore.setRoomSetting(name, level, roomId, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines whether or not a particular level is supported on the current platform.
|
|
||||||
* @param {string} level The level to check. One of 'device', 'room-account', 'account',
|
|
||||||
* 'room', or 'default'.
|
|
||||||
* @returns {boolean} Whether or not the level is supported.
|
|
||||||
*/
|
|
||||||
static isLevelSupported(level) {
|
|
||||||
return GranularSettingStore._getHandlersAtLevel(level).length > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static _getHandlersAtLevel(level) {
|
|
||||||
return PRIORITY_MAP.filter((h) => h.level === level && h.settingClass.isSupported());
|
|
||||||
}
|
|
||||||
|
|
||||||
static _getHandlers(type) {
|
|
||||||
return PRIORITY_MAP.filter((h) => {
|
|
||||||
if (!h.types.includes(type)) return false;
|
|
||||||
if (!h.settingClass.isSupported()) return false;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
static _getHandler(type, level) {
|
|
||||||
const handlers = GranularSettingStore._getHandlers(type);
|
|
||||||
return handlers.filter((h) => h.level === level)[0];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate of properties is assumed to be done well prior to instantiation of these classes,
|
|
||||||
// therefore these classes don't do any sanity checking. The following interface is assumed:
|
|
||||||
// constructor(type, name, roomId) - roomId may be null for type=='account'
|
|
||||||
// getValue() - returns a promise for the value. Falsey resolves are treated as 'not found'.
|
|
||||||
// setValue(content) - sets the new value for the setting. Falsey should remove the value.
|
|
||||||
// canSetValue() - returns true if the current user can set this setting.
|
|
||||||
// static isSupported() - returns true if the setting type is supported
|
|
||||||
|
|
||||||
class DefaultSetting {
|
|
||||||
constructor(type, name, roomId = null) {
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
this.roomId = roomId;
|
|
||||||
}
|
|
||||||
|
|
||||||
getValue() {
|
|
||||||
for (const setting of SETTINGS) {
|
|
||||||
if (setting.type === this.type && setting.name === this.name) {
|
|
||||||
return setting.defaults;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
setValue() {
|
|
||||||
throw new Error("Operation not permitted: Cannot set value of a default setting.");
|
|
||||||
}
|
|
||||||
|
|
||||||
canSetValue() {
|
|
||||||
// It's a default, so no, you can't.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static isSupported() {
|
|
||||||
return true; // defaults are always accepted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DeviceSetting {
|
|
||||||
constructor(type, name, roomId = null) {
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
this.roomId = roomId;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getKey() {
|
|
||||||
return "mx_setting_" + this.name + "_" + this.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
getValue() {
|
|
||||||
if (!localStorage) return null;
|
|
||||||
const value = localStorage.getItem(this._getKey());
|
|
||||||
if (!value) return null;
|
|
||||||
return JSON.parse(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
setValue(content) {
|
|
||||||
if (!localStorage) throw new Error("Operation not possible: No device storage available.");
|
|
||||||
if (!content) localStorage.removeItem(this._getKey());
|
|
||||||
else localStorage.setItem(this._getKey(), JSON.stringify(content));
|
|
||||||
}
|
|
||||||
|
|
||||||
canSetValue() {
|
|
||||||
// The user likely has control over their own localstorage.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static isSupported() {
|
|
||||||
// We can only do something if we have localstorage
|
|
||||||
return !!localStorage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RoomAccountSetting {
|
|
||||||
constructor(type, name, roomId = null) {
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
this.roomId = roomId;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getEventType() {
|
|
||||||
return "im.vector.setting." + this.type + "." + this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
getValue() {
|
|
||||||
if (!MatrixClientPeg.get()) return null;
|
|
||||||
|
|
||||||
const room = MatrixClientPeg.getRoom(this.roomId);
|
|
||||||
if (!room) return null;
|
|
||||||
|
|
||||||
const event = room.getAccountData(this._getEventType());
|
|
||||||
if (!event || !event.getContent()) return null;
|
|
||||||
|
|
||||||
return event.getContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
setValue(content) {
|
|
||||||
if (!MatrixClientPeg.get()) throw new Error("Operation not possible: No client peg");
|
|
||||||
return MatrixClientPeg.get().setRoomAccountData(this.roomId, this._getEventType(), content);
|
|
||||||
}
|
|
||||||
|
|
||||||
canSetValue() {
|
|
||||||
// It's their own room account data, so they should be able to set it.
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static isSupported() {
|
|
||||||
// We can only do something if we have a client
|
|
||||||
return !!MatrixClientPeg.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class AccountSetting {
|
|
||||||
constructor(type, name, roomId = null) {
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
this.roomId = roomId;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getEventType() {
|
|
||||||
return "im.vector.setting." + this.type + "." + this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
getValue() {
|
|
||||||
if (!MatrixClientPeg.get()) return null;
|
|
||||||
return MatrixClientPeg.getAccountData(this._getEventType());
|
|
||||||
}
|
|
||||||
|
|
||||||
setValue(content) {
|
|
||||||
if (!MatrixClientPeg.get()) throw new Error("Operation not possible: No client peg");
|
|
||||||
return MatrixClientPeg.setAccountData(this._getEventType(), content);
|
|
||||||
}
|
|
||||||
|
|
||||||
canSetValue() {
|
|
||||||
// It's their own account data, so they should be able to set it
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
static isSupported() {
|
|
||||||
// We can only do something if we have a client
|
|
||||||
return !!MatrixClientPeg.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RoomSetting {
|
|
||||||
constructor(type, name, roomId = null) {
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
this.roomId = roomId;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getEventType() {
|
|
||||||
return "im.vector.setting." + this.type + "." + this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
getValue() {
|
|
||||||
if (!MatrixClientPeg.get()) return null;
|
|
||||||
|
|
||||||
const room = MatrixClientPeg.get().getRoom(this.roomId);
|
|
||||||
if (!room) return null;
|
|
||||||
|
|
||||||
const stateEvent = room.currentState.getStateEvents(this._getEventType(), "");
|
|
||||||
if (!stateEvent || !stateEvent.getContent()) return null;
|
|
||||||
|
|
||||||
return stateEvent.getContent();
|
|
||||||
}
|
|
||||||
|
|
||||||
setValue(content) {
|
|
||||||
if (!MatrixClientPeg.get()) throw new Error("Operation not possible: No client peg");
|
|
||||||
|
|
||||||
return MatrixClientPeg.get().sendStateEvent(this.roomId, this._getEventType(), content, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
canSetValue() {
|
|
||||||
const cli = MatrixClientPeg.get();
|
|
||||||
|
|
||||||
const room = cli.getRoom(this.roomId);
|
|
||||||
if (!room) return false; // They're not in the room, likely.
|
|
||||||
|
|
||||||
return room.currentState.maySendStateEvent(this._getEventType(), cli.getUserId());
|
|
||||||
}
|
|
||||||
|
|
||||||
static isSupported() {
|
|
||||||
// We can only do something if we have a client
|
|
||||||
return !!MatrixClientPeg.get();
|
|
||||||
}
|
|
||||||
}
|
|
47
src/settings/AccountSettingsHandler.js
Normal file
47
src/settings/AccountSettingsHandler.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Travis Ralston
|
||||||
|
|
||||||
|
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 Promise from 'bluebird';
|
||||||
|
import SettingsHandler from "./SettingsHandler";
|
||||||
|
import MatrixClientPeg from '../MatrixClientPeg';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets and sets settings at the "account" level for the current user.
|
||||||
|
* This handler does not make use of the roomId parameter.
|
||||||
|
*/
|
||||||
|
export default class AccountSettingHandler extends SettingsHandler {
|
||||||
|
getValue(settingName, roomId) {
|
||||||
|
const value = MatrixClientPeg.get().getAccountData(this._getEventType(settingName));
|
||||||
|
if (!value) return Promise.reject();
|
||||||
|
return Promise.resolve(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(settingName, roomId, newValue) {
|
||||||
|
return MatrixClientPeg.get().setAccountData(this._getEventType(settingName), newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
canSetValue(settingName, roomId) {
|
||||||
|
return true; // It's their account, so they should be able to
|
||||||
|
}
|
||||||
|
|
||||||
|
isSupported() {
|
||||||
|
return !!MatrixClientPeg.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getEventType(settingName) {
|
||||||
|
return "im.vector.setting." + settingName;
|
||||||
|
}
|
||||||
|
}
|
43
src/settings/ConfigSettingsHandler.js
Normal file
43
src/settings/ConfigSettingsHandler.js
Normal file
|
@ -0,0 +1,43 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Travis Ralston
|
||||||
|
|
||||||
|
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 Promise from 'bluebird';
|
||||||
|
import SettingsHandler from "./SettingsHandler";
|
||||||
|
import SdkConfig from "../SdkConfig";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets and sets settings at the "config" level. This handler does not make use of the
|
||||||
|
* roomId parameter.
|
||||||
|
*/
|
||||||
|
export default class ConfigSettingsHandler extends SettingsHandler {
|
||||||
|
getValue(settingName, roomId) {
|
||||||
|
const settingsConfig = SdkConfig.get()["settingDefaults"];
|
||||||
|
if (!settingsConfig || !settingsConfig[settingName]) return Promise.reject();
|
||||||
|
return Promise.resolve(settingsConfig[settingName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(settingName, roomId, newValue) {
|
||||||
|
throw new Error("Cannot change settings at the config level");
|
||||||
|
}
|
||||||
|
|
||||||
|
canSetValue(settingName, roomId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSupported() {
|
||||||
|
return true; // SdkConfig is always there
|
||||||
|
}
|
||||||
|
}
|
51
src/settings/DefaultSettingsHandler.js
Normal file
51
src/settings/DefaultSettingsHandler.js
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Travis Ralston
|
||||||
|
|
||||||
|
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 Promise from 'bluebird';
|
||||||
|
import SettingsHandler from "./SettingsHandler";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets settings at the "default" level. This handler does not support setting values.
|
||||||
|
* This handler does not make use of the roomId parameter.
|
||||||
|
*/
|
||||||
|
export default class DefaultSettingsHandler extends SettingsHandler {
|
||||||
|
/**
|
||||||
|
* Creates a new default settings handler with the given defaults
|
||||||
|
* @param {object} defaults The default setting values, keyed by setting name.
|
||||||
|
*/
|
||||||
|
constructor(defaults) {
|
||||||
|
super();
|
||||||
|
this._defaults = defaults;
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(settingName, roomId) {
|
||||||
|
const value = this._defaults[settingName];
|
||||||
|
if (!value) return Promise.reject();
|
||||||
|
return Promise.resolve(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(settingName, roomId, newValue) {
|
||||||
|
throw new Error("Cannot set values on the default level handler");
|
||||||
|
}
|
||||||
|
|
||||||
|
canSetValue(settingName, roomId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSupported() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
90
src/settings/DeviceSettingsHandler.js
Normal file
90
src/settings/DeviceSettingsHandler.js
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Travis Ralston
|
||||||
|
|
||||||
|
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 Promise from 'bluebird';
|
||||||
|
import SettingsHandler from "./SettingsHandler";
|
||||||
|
import MatrixClientPeg from "../MatrixClientPeg";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets and sets settings at the "device" level for the current device.
|
||||||
|
* This handler does not make use of the roomId parameter. This handler
|
||||||
|
* will special-case features to support legacy settings.
|
||||||
|
*/
|
||||||
|
export default class DeviceSettingsHandler extends SettingsHandler {
|
||||||
|
/**
|
||||||
|
* Creates a new device settings handler
|
||||||
|
* @param {string[]} featureNames The names of known features.
|
||||||
|
*/
|
||||||
|
constructor(featureNames) {
|
||||||
|
super();
|
||||||
|
this._featureNames = featureNames;
|
||||||
|
}
|
||||||
|
|
||||||
|
getValue(settingName, roomId) {
|
||||||
|
if (this._featureNames.includes(settingName)) {
|
||||||
|
return Promise.resolve(this._readFeature(settingName));
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = localStorage.getItem(this._getKey(settingName));
|
||||||
|
if (!value) return Promise.reject();
|
||||||
|
return Promise.resolve(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(settingName, roomId, newValue) {
|
||||||
|
if (this._featureNames.includes(settingName)) {
|
||||||
|
return Promise.resolve(this._writeFeature(settingName));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newValue === null) {
|
||||||
|
localStorage.removeItem(this._getKey(settingName));
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(this._getKey(settingName), newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
canSetValue(settingName, roomId) {
|
||||||
|
return true; // It's their device, so they should be able to
|
||||||
|
}
|
||||||
|
|
||||||
|
isSupported() {
|
||||||
|
return !!localStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getKey(settingName) {
|
||||||
|
return "mx_setting_" + settingName;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: features intentionally don't use the same key as settings to avoid conflicts
|
||||||
|
// and to be backwards compatible.
|
||||||
|
|
||||||
|
_readFeature(featureName) {
|
||||||
|
if (MatrixClientPeg.get() && MatrixClientPeg.get().isGuest()) {
|
||||||
|
// Guests should not have any labs features enabled.
|
||||||
|
return {enabled: false};
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = localStorage.getItem("mx_labs_feature_" + featureName);
|
||||||
|
const enabled = value === "true";
|
||||||
|
|
||||||
|
return {enabled};
|
||||||
|
}
|
||||||
|
|
||||||
|
_writeFeature(featureName, enabled) {
|
||||||
|
localStorage.setItem("mx_labs_feature_" + featureName, enabled);
|
||||||
|
}
|
||||||
|
}
|
52
src/settings/RoomAccountSettingsHandler.js
Normal file
52
src/settings/RoomAccountSettingsHandler.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Travis Ralston
|
||||||
|
|
||||||
|
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 Promise from 'bluebird';
|
||||||
|
import SettingsHandler from "./SettingsHandler";
|
||||||
|
import MatrixClientPeg from '../MatrixClientPeg';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets and sets settings at the "room-account" level for the current user.
|
||||||
|
*/
|
||||||
|
export default class RoomAccountSettingsHandler extends SettingsHandler {
|
||||||
|
getValue(settingName, roomId) {
|
||||||
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
|
if (!room) return Promise.reject();
|
||||||
|
|
||||||
|
const value = room.getAccountData(this._getEventType(settingName));
|
||||||
|
if (!value) return Promise.reject();
|
||||||
|
return Promise.resolve(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(settingName, roomId, newValue) {
|
||||||
|
return MatrixClientPeg.get().setRoomAccountData(
|
||||||
|
roomId, this._getEventType(settingName), newValue
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
canSetValue(settingName, roomId) {
|
||||||
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
|
return !!room; // If they have the room, they can set their own account data
|
||||||
|
}
|
||||||
|
|
||||||
|
isSupported() {
|
||||||
|
return !!MatrixClientPeg.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getEventType(settingName) {
|
||||||
|
return "im.vector.setting." + settingName;
|
||||||
|
}
|
||||||
|
}
|
52
src/settings/RoomDeviceSettingsHandler.js
Normal file
52
src/settings/RoomDeviceSettingsHandler.js
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Travis Ralston
|
||||||
|
|
||||||
|
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 Promise from 'bluebird';
|
||||||
|
import SettingsHandler from "./SettingsHandler";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets and sets settings at the "room-device" level for the current device in a particular
|
||||||
|
* room.
|
||||||
|
*/
|
||||||
|
export default class RoomDeviceSettingsHandler extends SettingsHandler {
|
||||||
|
getValue(settingName, roomId) {
|
||||||
|
const value = localStorage.getItem(this._getKey(settingName, roomId));
|
||||||
|
if (!value) return Promise.reject();
|
||||||
|
return Promise.resolve(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(settingName, roomId, newValue) {
|
||||||
|
if (newValue === null) {
|
||||||
|
localStorage.removeItem(this._getKey(settingName, roomId));
|
||||||
|
} else {
|
||||||
|
localStorage.setItem(this._getKey(settingName, roomId), newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
canSetValue(settingName, roomId) {
|
||||||
|
return true; // It's their device, so they should be able to
|
||||||
|
}
|
||||||
|
|
||||||
|
isSupported() {
|
||||||
|
return !!localStorage;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getKey(settingName, roomId) {
|
||||||
|
return "mx_setting_" + settingName + "_" + roomId;
|
||||||
|
}
|
||||||
|
}
|
56
src/settings/RoomSettingsHandler.js
Normal file
56
src/settings/RoomSettingsHandler.js
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Travis Ralston
|
||||||
|
|
||||||
|
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 Promise from 'bluebird';
|
||||||
|
import SettingsHandler from "./SettingsHandler";
|
||||||
|
import MatrixClientPeg from '../MatrixClientPeg';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets and sets settings at the "room" level.
|
||||||
|
*/
|
||||||
|
export default class RoomSettingsHandler extends SettingsHandler {
|
||||||
|
getValue(settingName, roomId) {
|
||||||
|
const room = MatrixClientPeg.get().getRoom(roomId);
|
||||||
|
if (!room) return Promise.reject();
|
||||||
|
|
||||||
|
const event = room.currentState.getStateEvents(this._getEventType(settingName), "");
|
||||||
|
if (!event || !event.getContent()) return Promise.reject();
|
||||||
|
return Promise.resolve(event.getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
setValue(settingName, roomId, newValue) {
|
||||||
|
return MatrixClientPeg.get().sendStateEvent(
|
||||||
|
roomId, this._getEventType(settingName), newValue, ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
canSetValue(settingName, roomId) {
|
||||||
|
const cli = MatrixClientPeg.get();
|
||||||
|
const room = cli.getRoom(roomId);
|
||||||
|
const eventType = this._getEventType(settingName);
|
||||||
|
|
||||||
|
if (!room) return false;
|
||||||
|
return room.currentState.maySendStateEvent(eventType, cli.getUserId());
|
||||||
|
}
|
||||||
|
|
||||||
|
isSupported() {
|
||||||
|
return !!MatrixClientPeg.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
_getEventType(settingName) {
|
||||||
|
return "im.vector.setting." + settingName;
|
||||||
|
}
|
||||||
|
}
|
70
src/settings/SettingsHandler.js
Normal file
70
src/settings/SettingsHandler.js
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Travis Ralston
|
||||||
|
|
||||||
|
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 Promise from 'bluebird';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents the base class for all level handlers. This class performs no logic
|
||||||
|
* and should be overridden.
|
||||||
|
*/
|
||||||
|
export default class SettingsHandler {
|
||||||
|
/**
|
||||||
|
* Gets the value for a particular setting at this level for a particular room.
|
||||||
|
* If no room is applicable, the roomId may be null. The roomId may not be
|
||||||
|
* applicable to this level and may be ignored by the handler.
|
||||||
|
* @param {string} settingName The name of the setting.
|
||||||
|
* @param {String} roomId The room ID to read from, may be null.
|
||||||
|
* @return {Promise<object>} Resolves to the setting value. Rejected if the value
|
||||||
|
* could not be found.
|
||||||
|
*/
|
||||||
|
getValue(settingName, roomId) {
|
||||||
|
throw new Error("Operation not possible: getValue was not overridden");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value for a particular setting at this level for a particular room.
|
||||||
|
* If no room is applicable, the roomId may be null. The roomId may not be
|
||||||
|
* applicable to this level and may be ignored by the handler. Setting a value
|
||||||
|
* to null will cause the level to remove the value. The current user should be
|
||||||
|
* able to set the value prior to calling this.
|
||||||
|
* @param {string} settingName The name of the setting to change.
|
||||||
|
* @param {String} roomId The room ID to set the value in, may be null.
|
||||||
|
* @param {Object} newValue The new value for the setting, may be null.
|
||||||
|
* @return {Promise} Resolves when the setting has been saved.
|
||||||
|
*/
|
||||||
|
setValue(settingName, roomId, newValue) {
|
||||||
|
throw new Error("Operation not possible: setValue was not overridden");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the current user is able to set the value of the given setting
|
||||||
|
* in the given room at this level.
|
||||||
|
* @param {string} settingName The name of the setting to check.
|
||||||
|
* @param {String} roomId The room ID to check in, may be null
|
||||||
|
* @returns {boolean} True if the setting can be set by the user, false otherwise.
|
||||||
|
*/
|
||||||
|
canSetValue(settingName, roomId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if this level is supported on this device.
|
||||||
|
* @returns {boolean} True if this level is supported on the current device.
|
||||||
|
*/
|
||||||
|
isSupported() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
275
src/settings/SettingsStore.js
Normal file
275
src/settings/SettingsStore.js
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 Travis Ralston
|
||||||
|
|
||||||
|
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 Promise from 'bluebird';
|
||||||
|
import DeviceSettingsHandler from "./DeviceSettingsHandler";
|
||||||
|
import RoomDeviceSettingsHandler from "./RoomDeviceSettingsHandler";
|
||||||
|
import DefaultSettingsHandler from "./DefaultSettingsHandler";
|
||||||
|
import RoomAccountSettingsHandler from "./RoomAccountSettingsHandler";
|
||||||
|
import AccountSettingsHandler from "./AccountSettingsHandler";
|
||||||
|
import RoomSettingsHandler from "./RoomSettingsHandler";
|
||||||
|
import ConfigSettingsHandler from "./ConfigSettingsHandler";
|
||||||
|
import {_t, _td} from '../languageHandler';
|
||||||
|
import SdkConfig from "../SdkConfig";
|
||||||
|
|
||||||
|
// Preset levels for room-based settings (eg: URL previews).
|
||||||
|
// Doesn't include 'room' because most settings don't need it. Use .concat('room') to add.
|
||||||
|
const LEVELS_PRESET_ROOM = ['device', 'room-device', 'room-account', 'account'];
|
||||||
|
|
||||||
|
// Preset levels for account-based settings (eg: interface language).
|
||||||
|
const LEVELS_PRESET_ACCOUNT = ['device', 'account'];
|
||||||
|
|
||||||
|
// Preset levels for features (labs) settings.
|
||||||
|
const LEVELS_PRESET_FEATURE = ['device'];
|
||||||
|
|
||||||
|
const SETTINGS = {
|
||||||
|
"my-setting": {
|
||||||
|
isFeature: false, // optional
|
||||||
|
displayName: _td("Cool Name"),
|
||||||
|
supportedLevels: [
|
||||||
|
// The order does not matter.
|
||||||
|
|
||||||
|
"device", // Affects the current device only
|
||||||
|
"room-device", // Affects the current room on the current device
|
||||||
|
"room-account", // Affects the current room for the current account
|
||||||
|
"account", // Affects the current account
|
||||||
|
"room", // Affects the current room (controlled by room admins)
|
||||||
|
|
||||||
|
// "default" and "config" are always supported and do not get listed here.
|
||||||
|
],
|
||||||
|
defaults: {
|
||||||
|
your: "value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// TODO: Populate this
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert the above into simpler formats for the handlers
|
||||||
|
let defaultSettings = {};
|
||||||
|
let featureNames = [];
|
||||||
|
for (let key of Object.keys(SETTINGS)) {
|
||||||
|
defaultSettings[key] = SETTINGS[key].defaults;
|
||||||
|
if (SETTINGS[key].isFeature) featureNames.push(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
const LEVEL_HANDLERS = {
|
||||||
|
"device": new DeviceSettingsHandler(featureNames),
|
||||||
|
"room-device": new RoomDeviceSettingsHandler(),
|
||||||
|
"room-account": new RoomAccountSettingsHandler(),
|
||||||
|
"account": new AccountSettingsHandler(),
|
||||||
|
"room": new RoomSettingsHandler(),
|
||||||
|
"config": new ConfigSettingsHandler(),
|
||||||
|
"default": new DefaultSettingsHandler(defaultSettings),
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controls and manages application settings by providing varying levels at which the
|
||||||
|
* setting value may be specified. The levels are then used to determine what the setting
|
||||||
|
* value should be given a set of circumstances. The levels, in priority order, are:
|
||||||
|
* - "device" - Values are determined by the current device
|
||||||
|
* - "room-device" - Values are determined by the current device for a particular room
|
||||||
|
* - "room-account" - Values are determined by the current account for a particular room
|
||||||
|
* - "account" - Values are determined by the current account
|
||||||
|
* - "room" - Values are determined by a particular room (by the room admins)
|
||||||
|
* - "config" - Values are determined by the config.json
|
||||||
|
* - "default" - Values are determined by the hardcoded defaults
|
||||||
|
*
|
||||||
|
* Each level has a different method to storing the setting value. For implementation
|
||||||
|
* specific details, please see the handlers. The "config" and "default" levels are
|
||||||
|
* both always supported on all platforms. All other settings should be guarded by
|
||||||
|
* isLevelSupported() prior to attempting to set the value.
|
||||||
|
*
|
||||||
|
* Settings can also represent features. Features are significant portions of the
|
||||||
|
* application that warrant a dedicated setting to toggle them on or off. Features are
|
||||||
|
* special-cased to ensure that their values respect the configuration (for example, a
|
||||||
|
* feature may be reported as disabled even though a user has specifically requested it
|
||||||
|
* be enabled).
|
||||||
|
*/
|
||||||
|
export default class SettingsStore {
|
||||||
|
/**
|
||||||
|
* Gets the translated display name for a given setting
|
||||||
|
* @param {string} settingName The setting to look up.
|
||||||
|
* @return {String} The display name for the setting, or null if not found.
|
||||||
|
*/
|
||||||
|
static getDisplayName(settingName) {
|
||||||
|
if (!SETTINGS[settingName] || !SETTINGS[settingName].displayName) return null;
|
||||||
|
return _t(SETTINGS[settingName].displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a setting is also a feature.
|
||||||
|
* @param {string} settingName The setting to look up.
|
||||||
|
* @return {boolean} True if the setting is a feature.
|
||||||
|
*/
|
||||||
|
static isFeature(settingName) {
|
||||||
|
if (!SETTINGS[settingName]) return false;
|
||||||
|
return SETTINGS[settingName].isFeature;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a given feature is enabled. The feature given must be a known
|
||||||
|
* feature.
|
||||||
|
* @param {string} settingName The name of the setting that is a feature.
|
||||||
|
* @param {String} roomId The optional room ID to validate in, may be null.
|
||||||
|
* @return {boolean} True if the feature is enabled, false otherwise
|
||||||
|
*/
|
||||||
|
static isFeatureEnabled(settingName, roomId = null) {
|
||||||
|
if (!SettingsStore.isFeature(settingName)) {
|
||||||
|
throw new Error("Setting " + settingName + " is not a feature");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronously get the setting value (which should be {enabled: true/false})
|
||||||
|
const value = Promise.coroutine(function* () {
|
||||||
|
return yield SettingsStore.getValue(settingName, roomId);
|
||||||
|
})();
|
||||||
|
|
||||||
|
return value.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the value of a setting. The room ID is optional if the setting is not to
|
||||||
|
* be applied to any particular room, otherwise it should be supplied.
|
||||||
|
* @param {string} settingName The name of the setting to read the value of.
|
||||||
|
* @param {String} roomId The room ID to read the setting value in, may be null.
|
||||||
|
* @return {Promise<*>} Resolves to the value for the setting. May result in null.
|
||||||
|
*/
|
||||||
|
static getValue(settingName, roomId) {
|
||||||
|
const levelOrder = [
|
||||||
|
'device', 'room-device', 'room-account', 'account', 'room', 'config', 'default'
|
||||||
|
];
|
||||||
|
|
||||||
|
if (SettingsStore.isFeature(settingName)) {
|
||||||
|
const configValue = SettingsStore._getFeatureState(settingName);
|
||||||
|
if (configValue === "enable") return Promise.resolve({enabled: true});
|
||||||
|
if (configValue === "disable") return Promise.resolve({enabled: false});
|
||||||
|
// else let it fall through the default process
|
||||||
|
}
|
||||||
|
|
||||||
|
const handlers = SettingsStore._getHandlers(settingName);
|
||||||
|
|
||||||
|
// This wrapper function allows for iterating over the levelOrder to find a suitable
|
||||||
|
// handler that is supported by the setting. It does this by building the promise chain
|
||||||
|
// on the fly, wrapping the rejection from handler.getValue() to try the next handler.
|
||||||
|
// If the last handler also rejects the getValue() call, then this wrapper will convert
|
||||||
|
// the reply to `null` as per our contract to the caller.
|
||||||
|
let index = 0;
|
||||||
|
const wrapperFn = () => {
|
||||||
|
// Find the next handler that we can use
|
||||||
|
let handler = null;
|
||||||
|
while (!handler && index < levelOrder.length) {
|
||||||
|
handler = handlers[levelOrder[index++]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// No handler == no reply (happens when the last available handler rejects)
|
||||||
|
if (!handler) return null;
|
||||||
|
|
||||||
|
// Get the value and see if the handler will reject us (meaning it doesn't have
|
||||||
|
// a value for us).
|
||||||
|
const value = handler.getValue(settingName, roomId);
|
||||||
|
return value.then(null, () => wrapperFn()); // pass success through
|
||||||
|
};
|
||||||
|
|
||||||
|
return wrapperFn();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value for a setting. The room ID is optional if the setting is not being
|
||||||
|
* set for a particular room, otherwise it should be supplied. The value may be null
|
||||||
|
* to indicate that the level should no longer have an override.
|
||||||
|
* @param {string} settingName The name of the setting to change.
|
||||||
|
* @param {String} roomId The room ID to change the value in, may be null.
|
||||||
|
* @param {"device"|"room-device"|"room-account"|"account"|"room"} level The level
|
||||||
|
* to change the value at.
|
||||||
|
* @param {Object} value The new value of the setting, may be null.
|
||||||
|
* @return {Promise} Resolves when the setting has been changed.
|
||||||
|
*/
|
||||||
|
static setValue(settingName, roomId, level, value) {
|
||||||
|
const handler = SettingsStore._getHandler(settingName, level);
|
||||||
|
if (!handler) {
|
||||||
|
throw new Error("Setting " + settingName + " does not have a handler for " + level);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handler.canSetValue(settingName, roomId)) {
|
||||||
|
throw new Error("User cannot set " + settingName + " at level " + level);
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler.setValue(settingName, roomId, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the current user is permitted to set the given setting at the given
|
||||||
|
* level for a particular room. The room ID is optional if the setting is not being
|
||||||
|
* set for a particular room, otherwise it should be supplied.
|
||||||
|
* @param {string} settingName The name of the setting to check.
|
||||||
|
* @param {String} roomId The room ID to check in, may be null.
|
||||||
|
* @param {"device"|"room-device"|"room-account"|"account"|"room"} level The level to
|
||||||
|
* check at.
|
||||||
|
* @return {boolean} True if the user may set the setting, false otherwise.
|
||||||
|
*/
|
||||||
|
static canSetValue(settingName, roomId, level) {
|
||||||
|
const handler = SettingsStore._getHandler(settingName, level);
|
||||||
|
if (!handler) return false;
|
||||||
|
return handler.canSetValue(settingName, roomId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the given level is supported on this device.
|
||||||
|
* @param {"device"|"room-device"|"room-account"|"account"|"room"} level The level
|
||||||
|
* to check the feasibility of.
|
||||||
|
* @return {boolean} True if the level is supported, false otherwise.
|
||||||
|
*/
|
||||||
|
static isLevelSupported(level) {
|
||||||
|
if (!LEVEL_HANDLERS[level]) return false;
|
||||||
|
return LEVEL_HANDLERS[level].isSupported();
|
||||||
|
}
|
||||||
|
|
||||||
|
static _getHandler(settingName, level) {
|
||||||
|
const handlers = SettingsStore._getHandlers(settingName);
|
||||||
|
if (!handlers[level]) return null;
|
||||||
|
return handlers[level];
|
||||||
|
}
|
||||||
|
|
||||||
|
static _getHandlers(settingName) {
|
||||||
|
if (!SETTINGS[settingName]) return {};
|
||||||
|
|
||||||
|
const handlers = {};
|
||||||
|
for (let level of SETTINGS[settingName].supportedLevels) {
|
||||||
|
if (!LEVEL_HANDLERS[level]) throw new Error("Unexpected level " + level);
|
||||||
|
handlers[level] = LEVEL_HANDLERS[level];
|
||||||
|
}
|
||||||
|
|
||||||
|
return handlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
static _getFeatureState(settingName) {
|
||||||
|
const featuresConfig = SdkConfig.get()['features'];
|
||||||
|
const enableLabs = SdkConfig.get()['enableLabs']; // we'll honour the old flag
|
||||||
|
|
||||||
|
let featureState = enableLabs ? "labs" : "disable";
|
||||||
|
if (featuresConfig && featuresConfig[settingName] !== undefined) {
|
||||||
|
featureState = featuresConfig[settingName];
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowedStates = ['enable', 'disable', 'labs'];
|
||||||
|
if (!allowedStates.contains(featureState)) {
|
||||||
|
console.warn("Feature state '" + featureState + "' is invalid for " + settingName);
|
||||||
|
featureState = "disable"; // to prevent accidental features.
|
||||||
|
}
|
||||||
|
|
||||||
|
return featureState;
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue