Merge branch 'develop' into travis/granular-settings
This commit is contained in:
commit
bd4102eb3d
14 changed files with 451 additions and 133 deletions
|
@ -93,6 +93,7 @@ class MatrixClientPeg {
|
||||||
const opts = utils.deepCopy(this.opts);
|
const opts = utils.deepCopy(this.opts);
|
||||||
// the react sdk doesn't work without this, so don't allow
|
// the react sdk doesn't work without this, so don't allow
|
||||||
opts.pendingEventOrdering = "detached";
|
opts.pendingEventOrdering = "detached";
|
||||||
|
opts.disablePresence = true; // we do this manually
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const promise = this.matrixClient.store.startup();
|
const promise = this.matrixClient.store.startup();
|
||||||
|
|
|
@ -56,13 +56,27 @@ class Presence {
|
||||||
return this.state;
|
return this.state;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current status message.
|
||||||
|
* @returns {String} the status message, may be null
|
||||||
|
*/
|
||||||
|
getStatusMessage() {
|
||||||
|
return this.statusMessage;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the presence state.
|
* Set the presence state.
|
||||||
* If the state has changed, the Home Server will be notified.
|
* If the state has changed, the Home Server will be notified.
|
||||||
* @param {string} newState the new presence state (see PRESENCE enum)
|
* @param {string} newState the new presence state (see PRESENCE enum)
|
||||||
|
* @param {String} statusMessage an optional status message for the presence
|
||||||
|
* @param {boolean} maintain true to have this status maintained by this tracker
|
||||||
*/
|
*/
|
||||||
setState(newState) {
|
setState(newState, statusMessage=null, maintain=false) {
|
||||||
if (newState === this.state) {
|
if (this.maintain) {
|
||||||
|
// Don't update presence if we're maintaining a particular status
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newState === this.state && statusMessage === this.statusMessage) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (PRESENCE_STATES.indexOf(newState) === -1) {
|
if (PRESENCE_STATES.indexOf(newState) === -1) {
|
||||||
|
@ -72,21 +86,37 @@ class Presence {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const old_state = this.state;
|
const old_state = this.state;
|
||||||
|
const old_message = this.statusMessage;
|
||||||
this.state = newState;
|
this.state = newState;
|
||||||
|
this.statusMessage = statusMessage;
|
||||||
|
this.maintain = maintain;
|
||||||
|
|
||||||
if (MatrixClientPeg.get().isGuest()) {
|
if (MatrixClientPeg.get().isGuest()) {
|
||||||
return; // don't try to set presence when a guest; it won't work.
|
return; // don't try to set presence when a guest; it won't work.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateContent = {
|
||||||
|
presence: this.state,
|
||||||
|
status_msg: this.statusMessage ? this.statusMessage : '',
|
||||||
|
};
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
MatrixClientPeg.get().setPresence(this.state).done(function() {
|
MatrixClientPeg.get().setPresence(updateContent).done(function() {
|
||||||
console.log("Presence: %s", newState);
|
console.log("Presence: %s", newState);
|
||||||
|
|
||||||
|
// We have to dispatch because the js-sdk is unreliable at telling us about our own presence
|
||||||
|
dis.dispatch({action: "self_presence_updated", statusInfo: updateContent});
|
||||||
}, function(err) {
|
}, function(err) {
|
||||||
console.error("Failed to set presence: %s", err);
|
console.error("Failed to set presence: %s", err);
|
||||||
self.state = old_state;
|
self.state = old_state;
|
||||||
|
self.statusMessage = old_message;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stopMaintainingStatus() {
|
||||||
|
this.maintain = false;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback called when the user made no action on the page for UNAVAILABLE_TIME ms.
|
* Callback called when the user made no action on the page for UNAVAILABLE_TIME ms.
|
||||||
* @private
|
* @private
|
||||||
|
@ -95,7 +125,8 @@ class Presence {
|
||||||
this.setState("unavailable");
|
this.setState("unavailable");
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUserActivity() {
|
_onUserActivity(payload) {
|
||||||
|
if (payload.action === "sync_state" || payload.action === "self_presence_updated") return;
|
||||||
this._resetTimer();
|
this._resetTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
11
src/Roles.js
11
src/Roles.js
|
@ -15,19 +15,20 @@ limitations under the License.
|
||||||
*/
|
*/
|
||||||
import { _t } from './languageHandler';
|
import { _t } from './languageHandler';
|
||||||
|
|
||||||
export function levelRoleMap() {
|
export function levelRoleMap(usersDefault) {
|
||||||
return {
|
return {
|
||||||
undefined: _t('Default'),
|
undefined: _t('Default'),
|
||||||
0: _t('User'),
|
0: _t('Restricted'),
|
||||||
|
[usersDefault]: _t('Default'),
|
||||||
50: _t('Moderator'),
|
50: _t('Moderator'),
|
||||||
100: _t('Admin'),
|
100: _t('Admin'),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function textualPowerLevel(level, userDefault) {
|
export function textualPowerLevel(level, usersDefault) {
|
||||||
const LEVEL_ROLE_MAP = this.levelRoleMap();
|
const LEVEL_ROLE_MAP = this.levelRoleMap(usersDefault);
|
||||||
if (LEVEL_ROLE_MAP[level]) {
|
if (LEVEL_ROLE_MAP[level]) {
|
||||||
return LEVEL_ROLE_MAP[level] + (level !== undefined ? ` (${level})` : ` (${userDefault})`);
|
return LEVEL_ROLE_MAP[level] + (level !== undefined ? ` (${level})` : ` (${usersDefault})`);
|
||||||
} else {
|
} else {
|
||||||
return level;
|
return level;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,9 +17,10 @@ limitations under the License.
|
||||||
|
|
||||||
const DEBUG = 0;
|
const DEBUG = 0;
|
||||||
|
|
||||||
// utility to turn #rrggbb into [red,green,blue]
|
// utility to turn #rrggbb or rgb(r,g,b) into [red,green,blue]
|
||||||
function hexToRgb(color) {
|
function colorToRgb(color) {
|
||||||
if (color[0] === '#') color = color.slice(1);
|
if (color[0] === '#') {
|
||||||
|
color = color.slice(1);
|
||||||
if (color.length === 3) {
|
if (color.length === 3) {
|
||||||
color = color[0] + color[0] +
|
color = color[0] + color[0] +
|
||||||
color[1] + color[1] +
|
color[1] + color[1] +
|
||||||
|
@ -30,10 +31,20 @@ function hexToRgb(color) {
|
||||||
const g = (val >> 8) & 255;
|
const g = (val >> 8) & 255;
|
||||||
const b = val & 255;
|
const b = val & 255;
|
||||||
return [r, g, b];
|
return [r, g, b];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let match = color.match(/rgb\((.*?),(.*?),(.*?)\)/);
|
||||||
|
if (match) {
|
||||||
|
return [ parseInt(match[1]),
|
||||||
|
parseInt(match[2]),
|
||||||
|
parseInt(match[3]) ];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return [0,0,0];
|
||||||
}
|
}
|
||||||
|
|
||||||
// utility to turn [red,green,blue] into #rrggbb
|
// utility to turn [red,green,blue] into #rrggbb
|
||||||
function rgbToHex(rgb) {
|
function rgbToColor(rgb) {
|
||||||
const val = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
|
const val = (rgb[0] << 16) | (rgb[1] << 8) | rgb[2];
|
||||||
return '#' + (0x1000000 + val).toString(16).slice(1);
|
return '#' + (0x1000000 + val).toString(16).slice(1);
|
||||||
}
|
}
|
||||||
|
@ -45,7 +56,7 @@ class Tinter {
|
||||||
this.keyRgb = [
|
this.keyRgb = [
|
||||||
"rgb(118, 207, 166)", // Vector Green
|
"rgb(118, 207, 166)", // Vector Green
|
||||||
"rgb(234, 245, 240)", // Vector Light Green
|
"rgb(234, 245, 240)", // Vector Light Green
|
||||||
"rgb(211, 239, 225)", // Unused: BottomLeftMenu (20% Green overlaid on Light Green)
|
"rgb(211, 239, 225)", // roomsublist-label-bg-color (20% Green overlaid on Light Green)
|
||||||
];
|
];
|
||||||
|
|
||||||
// Some algebra workings for calculating the tint % of Vector Green & Light Green
|
// Some algebra workings for calculating the tint % of Vector Green & Light Green
|
||||||
|
@ -59,11 +70,11 @@ class Tinter {
|
||||||
this.keyHex = [
|
this.keyHex = [
|
||||||
"#76CFA6", // Vector Green
|
"#76CFA6", // Vector Green
|
||||||
"#EAF5F0", // Vector Light Green
|
"#EAF5F0", // Vector Light Green
|
||||||
"#D3EFE1", // Unused: BottomLeftMenu (20% Green overlaid on Light Green)
|
"#D3EFE1", // roomsublist-label-bg-color (20% Green overlaid on Light Green)
|
||||||
"#FFFFFF", // white highlights of the SVGs (for switching to dark theme)
|
"#FFFFFF", // white highlights of the SVGs (for switching to dark theme)
|
||||||
];
|
];
|
||||||
|
|
||||||
// cache of our replacement colours
|
// track the replacement colours actually being used
|
||||||
// defaults to our keys.
|
// defaults to our keys.
|
||||||
this.colors = [
|
this.colors = [
|
||||||
this.keyHex[0],
|
this.keyHex[0],
|
||||||
|
@ -72,6 +83,15 @@ class Tinter {
|
||||||
this.keyHex[3],
|
this.keyHex[3],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// track the most current tint request inputs (which may differ from the
|
||||||
|
// end result stored in this.colors
|
||||||
|
this.currentTint = [
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
];
|
||||||
|
|
||||||
this.cssFixups = [
|
this.cssFixups = [
|
||||||
// { theme: {
|
// { theme: {
|
||||||
// style: a style object that should be fixed up taken from a stylesheet
|
// style: a style object that should be fixed up taken from a stylesheet
|
||||||
|
@ -101,6 +121,9 @@ class Tinter {
|
||||||
|
|
||||||
// the currently loaded theme (if any)
|
// the currently loaded theme (if any)
|
||||||
this.theme = undefined;
|
this.theme = undefined;
|
||||||
|
|
||||||
|
// whether to force a tint (e.g. after changing theme)
|
||||||
|
this.forceTint = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -122,48 +145,58 @@ class Tinter {
|
||||||
return this.keyRgb;
|
return this.keyRgb;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentColors() {
|
|
||||||
return this.colors;
|
|
||||||
}
|
|
||||||
|
|
||||||
tint(primaryColor, secondaryColor, tertiaryColor) {
|
tint(primaryColor, secondaryColor, tertiaryColor) {
|
||||||
|
this.currentTint[0] = primaryColor;
|
||||||
|
this.currentTint[1] = secondaryColor;
|
||||||
|
this.currentTint[2] = tertiaryColor;
|
||||||
|
|
||||||
this.calcCssFixups();
|
this.calcCssFixups();
|
||||||
|
|
||||||
|
if (DEBUG) console.log("Tinter.tint(" + primaryColor + ", " +
|
||||||
|
secondaryColor + ", " +
|
||||||
|
tertiaryColor + ")");
|
||||||
|
|
||||||
if (!primaryColor) {
|
if (!primaryColor) {
|
||||||
primaryColor = this.keyRgb[0];
|
primaryColor = this.keyRgb[0];
|
||||||
secondaryColor = this.keyRgb[1];
|
secondaryColor = this.keyRgb[1];
|
||||||
|
tertiaryColor = this.keyRgb[2];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!secondaryColor) {
|
if (!secondaryColor) {
|
||||||
const x = 0.16; // average weighting factor calculated from vector green & light green
|
const x = 0.16; // average weighting factor calculated from vector green & light green
|
||||||
const rgb = hexToRgb(primaryColor);
|
const rgb = colorToRgb(primaryColor);
|
||||||
rgb[0] = x * rgb[0] + (1 - x) * 255;
|
rgb[0] = x * rgb[0] + (1 - x) * 255;
|
||||||
rgb[1] = x * rgb[1] + (1 - x) * 255;
|
rgb[1] = x * rgb[1] + (1 - x) * 255;
|
||||||
rgb[2] = x * rgb[2] + (1 - x) * 255;
|
rgb[2] = x * rgb[2] + (1 - x) * 255;
|
||||||
secondaryColor = rgbToHex(rgb);
|
secondaryColor = rgbToColor(rgb);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!tertiaryColor) {
|
if (!tertiaryColor) {
|
||||||
const x = 0.19;
|
const x = 0.19;
|
||||||
const rgb1 = hexToRgb(primaryColor);
|
const rgb1 = colorToRgb(primaryColor);
|
||||||
const rgb2 = hexToRgb(secondaryColor);
|
const rgb2 = colorToRgb(secondaryColor);
|
||||||
rgb1[0] = x * rgb1[0] + (1 - x) * rgb2[0];
|
rgb1[0] = x * rgb1[0] + (1 - x) * rgb2[0];
|
||||||
rgb1[1] = x * rgb1[1] + (1 - x) * rgb2[1];
|
rgb1[1] = x * rgb1[1] + (1 - x) * rgb2[1];
|
||||||
rgb1[2] = x * rgb1[2] + (1 - x) * rgb2[2];
|
rgb1[2] = x * rgb1[2] + (1 - x) * rgb2[2];
|
||||||
tertiaryColor = rgbToHex(rgb1);
|
tertiaryColor = rgbToColor(rgb1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.colors[0] === primaryColor &&
|
if (this.forceTint == false &&
|
||||||
|
this.colors[0] === primaryColor &&
|
||||||
this.colors[1] === secondaryColor &&
|
this.colors[1] === secondaryColor &&
|
||||||
this.colors[2] === tertiaryColor) {
|
this.colors[2] === tertiaryColor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.forceTint = false;
|
||||||
|
|
||||||
this.colors[0] = primaryColor;
|
this.colors[0] = primaryColor;
|
||||||
this.colors[1] = secondaryColor;
|
this.colors[1] = secondaryColor;
|
||||||
this.colors[2] = tertiaryColor;
|
this.colors[2] = tertiaryColor;
|
||||||
|
|
||||||
if (DEBUG) console.log("Tinter.tint");
|
if (DEBUG) console.log("Tinter.tint final: (" + primaryColor + ", " +
|
||||||
|
secondaryColor + ", " +
|
||||||
|
tertiaryColor + ")");
|
||||||
|
|
||||||
// go through manually fixing up the stylesheets.
|
// go through manually fixing up the stylesheets.
|
||||||
this.applyCssFixups();
|
this.applyCssFixups();
|
||||||
|
@ -176,6 +209,8 @@ class Tinter {
|
||||||
}
|
}
|
||||||
|
|
||||||
tintSvgWhite(whiteColor) {
|
tintSvgWhite(whiteColor) {
|
||||||
|
this.currentTint[3] = whiteColor;
|
||||||
|
|
||||||
if (!whiteColor) {
|
if (!whiteColor) {
|
||||||
whiteColor = this.colors[3];
|
whiteColor = this.colors[3];
|
||||||
}
|
}
|
||||||
|
@ -202,15 +237,31 @@ class Tinter {
|
||||||
document.getElementById('mx_theme_secondaryAccentColor')
|
document.getElementById('mx_theme_secondaryAccentColor')
|
||||||
).color;
|
).color;
|
||||||
}
|
}
|
||||||
|
if (document.getElementById('mx_theme_tertiaryAccentColor')) {
|
||||||
|
this.keyRgb[2] = window.getComputedStyle(
|
||||||
|
document.getElementById('mx_theme_tertiaryAccentColor')
|
||||||
|
).color;
|
||||||
|
}
|
||||||
|
|
||||||
this.calcCssFixups();
|
this.calcCssFixups();
|
||||||
|
this.forceTint = true;
|
||||||
|
|
||||||
|
this.tint(this.currentTint[0], this.currentTint[1], this.currentTint[2]);
|
||||||
|
|
||||||
|
if (theme === 'dark') {
|
||||||
|
// abuse the tinter to change all the SVG's #fff to #2d2d2d
|
||||||
|
// XXX: obviously this shouldn't be hardcoded here.
|
||||||
|
this.tintSvgWhite('#2d2d2d');
|
||||||
|
} else {
|
||||||
|
this.tintSvgWhite('#ffffff');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
calcCssFixups() {
|
calcCssFixups() {
|
||||||
// cache our fixups
|
// cache our fixups
|
||||||
if (this.cssFixups[this.theme]) return;
|
if (this.cssFixups[this.theme]) return;
|
||||||
|
|
||||||
if (DEBUG) console.trace("calcCssFixups start for " + this.theme + " (checking " +
|
if (DEBUG) console.debug("calcCssFixups start for " + this.theme + " (checking " +
|
||||||
document.styleSheets.length +
|
document.styleSheets.length +
|
||||||
" stylesheets)");
|
" stylesheets)");
|
||||||
|
|
||||||
|
@ -279,8 +330,16 @@ class Tinter {
|
||||||
" fixups)");
|
" fixups)");
|
||||||
for (let i = 0; i < this.cssFixups[this.theme].length; i++) {
|
for (let i = 0; i < this.cssFixups[this.theme].length; i++) {
|
||||||
const cssFixup = this.cssFixups[this.theme][i];
|
const cssFixup = this.cssFixups[this.theme][i];
|
||||||
|
try {
|
||||||
cssFixup.style[cssFixup.attr] = this.colors[cssFixup.index];
|
cssFixup.style[cssFixup.attr] = this.colors[cssFixup.index];
|
||||||
}
|
}
|
||||||
|
catch (e) {
|
||||||
|
// Firefox Quantum explodes if you manually edit the CSS in the
|
||||||
|
// inspector and then try to do a tint, as apparently all the
|
||||||
|
// fixups are then stale.
|
||||||
|
console.error("Failed to apply cssFixup in Tinter! ", e.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (DEBUG) console.log("applyCssFixups end");
|
if (DEBUG) console.log("applyCssFixups end");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@ module.exports = {
|
||||||
menuHeight: React.PropTypes.number,
|
menuHeight: React.PropTypes.number,
|
||||||
chevronOffset: React.PropTypes.number,
|
chevronOffset: React.PropTypes.number,
|
||||||
menuColour: React.PropTypes.string,
|
menuColour: React.PropTypes.string,
|
||||||
|
chevronFace: React.PropTypes.string, // top, bottom, left, right
|
||||||
},
|
},
|
||||||
|
|
||||||
getOrCreateContainer: function() {
|
getOrCreateContainer: function() {
|
||||||
|
@ -58,12 +59,30 @@ module.exports = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const position = {
|
const position = {};
|
||||||
top: props.top,
|
let chevronFace = null;
|
||||||
};
|
|
||||||
|
if (props.top) {
|
||||||
|
position.top = props.top;
|
||||||
|
} else {
|
||||||
|
position.bottom = props.bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (props.left) {
|
||||||
|
position.left = props.left;
|
||||||
|
chevronFace = 'left';
|
||||||
|
} else {
|
||||||
|
position.right = props.right;
|
||||||
|
chevronFace = 'right';
|
||||||
|
}
|
||||||
|
|
||||||
const chevronOffset = {};
|
const chevronOffset = {};
|
||||||
if (props.chevronOffset) {
|
if (props.chevronFace) {
|
||||||
|
chevronFace = props.chevronFace;
|
||||||
|
}
|
||||||
|
if (chevronFace === 'top' || chevronFace === 'bottom') {
|
||||||
|
chevronOffset.left = props.chevronOffset;
|
||||||
|
} else {
|
||||||
chevronOffset.top = props.chevronOffset;
|
chevronOffset.top = props.chevronOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -74,28 +93,27 @@ module.exports = {
|
||||||
.mx_ContextualMenu_chevron_left:after {
|
.mx_ContextualMenu_chevron_left:after {
|
||||||
border-right-color: ${props.menuColour};
|
border-right-color: ${props.menuColour};
|
||||||
}
|
}
|
||||||
|
|
||||||
.mx_ContextualMenu_chevron_right:after {
|
.mx_ContextualMenu_chevron_right:after {
|
||||||
border-left-color: ${props.menuColour};
|
border-left-color: ${props.menuColour};
|
||||||
}
|
}
|
||||||
|
.mx_ContextualMenu_chevron_top:after {
|
||||||
|
border-left-color: ${props.menuColour};
|
||||||
|
}
|
||||||
|
.mx_ContextualMenu_chevron_bottom:after {
|
||||||
|
border-left-color: ${props.menuColour};
|
||||||
|
}
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let chevron = null;
|
const chevron = <div style={chevronOffset} className={"mx_ContextualMenu_chevron_" + chevronFace}></div>;
|
||||||
if (props.left) {
|
|
||||||
chevron = <div style={chevronOffset} className="mx_ContextualMenu_chevron_left"></div>;
|
|
||||||
position.left = props.left;
|
|
||||||
} else {
|
|
||||||
chevron = <div style={chevronOffset} className="mx_ContextualMenu_chevron_right"></div>;
|
|
||||||
position.right = props.right;
|
|
||||||
}
|
|
||||||
|
|
||||||
const className = 'mx_ContextualMenu_wrapper';
|
const className = 'mx_ContextualMenu_wrapper';
|
||||||
|
|
||||||
const menuClasses = classNames({
|
const menuClasses = classNames({
|
||||||
'mx_ContextualMenu': true,
|
'mx_ContextualMenu': true,
|
||||||
'mx_ContextualMenu_left': props.left,
|
'mx_ContextualMenu_left': chevronFace === 'left',
|
||||||
'mx_ContextualMenu_right': !props.left,
|
'mx_ContextualMenu_right': chevronFace === 'right',
|
||||||
|
'mx_ContextualMenu_top': chevronFace === 'top',
|
||||||
|
'mx_ContextualMenu_bottom': chevronFace === 'bottom',
|
||||||
});
|
});
|
||||||
|
|
||||||
const menuStyle = {};
|
const menuStyle = {};
|
||||||
|
|
|
@ -288,7 +288,9 @@ module.exports = React.createClass({
|
||||||
this.handleResize();
|
this.handleResize();
|
||||||
window.addEventListener('resize', this.handleResize);
|
window.addEventListener('resize', this.handleResize);
|
||||||
|
|
||||||
// check we have the right tint applied for this theme
|
// check we have the right tint applied for this theme.
|
||||||
|
// N.B. we don't call the whole of setTheme() here as we may be
|
||||||
|
// racing with the theme CSS download finishing from index.js
|
||||||
Tinter.tint();
|
Tinter.tint();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -912,22 +914,44 @@ module.exports = React.createClass({
|
||||||
// disable all of them first, then enable the one we want. Chrome only
|
// disable all of them first, then enable the one we want. Chrome only
|
||||||
// bothers to do an update on a true->false transition, so this ensures
|
// bothers to do an update on a true->false transition, so this ensures
|
||||||
// that we get exactly one update, at the right time.
|
// that we get exactly one update, at the right time.
|
||||||
|
//
|
||||||
|
// ^ This comment was true when we used to use alternative stylesheets
|
||||||
|
// for the CSS. Nowadays we just set them all as disabled in index.html
|
||||||
|
// and enable them as needed. It might be cleaner to disable them all
|
||||||
|
// at the same time to prevent loading two themes simultaneously and
|
||||||
|
// having them interact badly... but this causes a flash of unstyled app
|
||||||
|
// which is even uglier. So we don't.
|
||||||
|
|
||||||
Object.values(styleElements).forEach((a) => {
|
|
||||||
a.disabled = true;
|
|
||||||
});
|
|
||||||
styleElements[theme].disabled = false;
|
styleElements[theme].disabled = false;
|
||||||
|
|
||||||
|
const switchTheme = function() {
|
||||||
|
Object.values(styleElements).forEach((a) => {
|
||||||
|
if (a == styleElements[theme]) return;
|
||||||
|
a.disabled = true;
|
||||||
|
});
|
||||||
Tinter.setTheme(theme);
|
Tinter.setTheme(theme);
|
||||||
const colors = Tinter.getCurrentColors();
|
};
|
||||||
Tinter.tint(colors[0], colors[1]);
|
|
||||||
|
|
||||||
if (theme === 'dark') {
|
// turns out that Firefox preloads the CSS for link elements with
|
||||||
// abuse the tinter to change all the SVG's #fff to #2d2d2d
|
// the disabled attribute, but Chrome doesn't.
|
||||||
// XXX: obviously this shouldn't be hardcoded here.
|
|
||||||
Tinter.tintSvgWhite('#2d2d2d');
|
let cssLoaded = false;
|
||||||
} else {
|
|
||||||
Tinter.tintSvgWhite('#ffffff');
|
styleElements[theme].onload = () => {
|
||||||
|
switchTheme();
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let i = 0; i < document.styleSheets.length; i++) {
|
||||||
|
const ss = document.styleSheets[i];
|
||||||
|
if (ss && ss.href === styleElements[theme].href) {
|
||||||
|
cssLoaded = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cssLoaded) {
|
||||||
|
styleElements[theme].onload = undefined;
|
||||||
|
switchTheme();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -370,6 +370,9 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
let serverConfig;
|
||||||
|
let header;
|
||||||
|
|
||||||
if (!SdkConfig.get().disable_custom_urls) {
|
if (!SdkConfig.get().disable_custom_urls) {
|
||||||
serverConfig = <ServerConfig ref="serverConfig"
|
serverConfig = <ServerConfig ref="serverConfig"
|
||||||
withToggleButton={true}
|
withToggleButton={true}
|
||||||
|
@ -381,9 +384,6 @@ module.exports = React.createClass({
|
||||||
delayTimeMs={1000} />;
|
delayTimeMs={1000} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
let serverConfig;
|
|
||||||
let header;
|
|
||||||
|
|
||||||
// FIXME: remove status.im theme tweaks
|
// FIXME: remove status.im theme tweaks
|
||||||
const theme = SettingsStore.getValue("theme");
|
const theme = SettingsStore.getValue("theme");
|
||||||
if (theme !== "status") {
|
if (theme !== "status") {
|
||||||
|
|
157
src/components/views/avatars/MemberPresenceAvatar.js
Normal file
157
src/components/views/avatars/MemberPresenceAvatar.js
Normal file
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import * as sdk from "../../../index";
|
||||||
|
import MatrixClientPeg from "../../../MatrixClientPeg";
|
||||||
|
import AccessibleButton from '../elements/AccessibleButton';
|
||||||
|
import Presence from "../../../Presence";
|
||||||
|
import dispatcher from "../../../dispatcher";
|
||||||
|
import UserSettingsStore from "../../../UserSettingsStore";
|
||||||
|
import * as ContextualMenu from "../../structures/ContextualMenu";
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
displayName: 'MemberPresenceAvatar',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
member: React.PropTypes.object.isRequired,
|
||||||
|
width: React.PropTypes.number,
|
||||||
|
height: React.PropTypes.number,
|
||||||
|
resizeMethod: React.PropTypes.string,
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
resizeMethod: 'crop',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
const presenceState = this.props.member.user.presence;
|
||||||
|
const presenceMessage = this.props.member.user.presenceStatusMsg;
|
||||||
|
return {
|
||||||
|
status: presenceState,
|
||||||
|
message: presenceMessage,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
MatrixClientPeg.get().on("User.presence", this.onUserPresence);
|
||||||
|
this.dispatcherRef = dispatcher.register(this.onAction);
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillUnmount: function() {
|
||||||
|
if (MatrixClientPeg.get()) {
|
||||||
|
MatrixClientPeg.get().removeListener("User.presence", this.onUserPresence);
|
||||||
|
}
|
||||||
|
dispatcher.unregister(this.dispatcherRef);
|
||||||
|
},
|
||||||
|
|
||||||
|
onAction: function(payload) {
|
||||||
|
if (payload.action !== "self_presence_updated") return;
|
||||||
|
if (this.props.member.userId !== MatrixClientPeg.get().getUserId()) return;
|
||||||
|
this.setState({
|
||||||
|
status: payload.statusInfo.presence,
|
||||||
|
message: payload.statusInfo.status_msg,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onUserPresence: function(event, user) {
|
||||||
|
if (user.userId !== MatrixClientPeg.get().getUserId()) return;
|
||||||
|
this.setState({
|
||||||
|
status: user.presence,
|
||||||
|
message: user.presenceStatusMsg,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
onStatusChange: function(newStatus) {
|
||||||
|
Presence.stopMaintainingStatus();
|
||||||
|
if (newStatus === "online") {
|
||||||
|
Presence.setState(newStatus);
|
||||||
|
} else Presence.setState(newStatus, null, true);
|
||||||
|
},
|
||||||
|
|
||||||
|
onClick: function(e) {
|
||||||
|
const PresenceContextMenu = sdk.getComponent('context_menus.PresenceContextMenu');
|
||||||
|
const elementRect = e.target.getBoundingClientRect();
|
||||||
|
|
||||||
|
// The window X and Y offsets are to adjust position when zoomed in to page
|
||||||
|
const x = (elementRect.left + window.pageXOffset) - (elementRect.width / 2) + 3;
|
||||||
|
const chevronOffset = 12;
|
||||||
|
let y = elementRect.top + (elementRect.height / 2) + window.pageYOffset;
|
||||||
|
y = y - (chevronOffset + 4); // where 4 is 1/4 the height of the chevron
|
||||||
|
|
||||||
|
ContextualMenu.createMenu(PresenceContextMenu, {
|
||||||
|
chevronOffset: chevronOffset,
|
||||||
|
chevronFace: 'bottom',
|
||||||
|
left: x,
|
||||||
|
top: y,
|
||||||
|
menuWidth: 125,
|
||||||
|
currentStatus: this.state.status,
|
||||||
|
onChange: this.onStatusChange,
|
||||||
|
});
|
||||||
|
|
||||||
|
e.stopPropagation();
|
||||||
|
// const presenceState = this.props.member.user.presence;
|
||||||
|
// const presenceLastActiveAgo = this.props.member.user.lastActiveAgo;
|
||||||
|
// const presenceLastTs = this.props.member.user.lastPresenceTs;
|
||||||
|
// const presenceCurrentlyActive = this.props.member.user.currentlyActive;
|
||||||
|
// const presenceMessage = this.props.member.user.presenceStatusMsg;
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const MemberAvatar = sdk.getComponent("avatars.MemberAvatar");
|
||||||
|
|
||||||
|
let onClickFn = null;
|
||||||
|
if (this.props.member.userId === MatrixClientPeg.get().getUserId()) {
|
||||||
|
onClickFn = this.onClick;
|
||||||
|
}
|
||||||
|
|
||||||
|
const avatarNode = (
|
||||||
|
<MemberAvatar member={this.props.member} width={this.props.width} height={this.props.height}
|
||||||
|
resizeMethod={this.props.resizeMethod} />
|
||||||
|
);
|
||||||
|
let statusNode = (
|
||||||
|
<span className={"mx_MemberPresenceAvatar_status mx_MemberPresenceAvatar_status_" + this.state.status} />
|
||||||
|
);
|
||||||
|
|
||||||
|
// LABS: Disable presence management functions for now
|
||||||
|
if (!UserSettingsStore.isFeatureEnabled("feature_presence_management")) {
|
||||||
|
statusNode = null;
|
||||||
|
onClickFn = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let avatar = (
|
||||||
|
<div className="mx_MemberPresenceAvatar">
|
||||||
|
{ avatarNode }
|
||||||
|
{ statusNode }
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
if (onClickFn) {
|
||||||
|
avatar = (
|
||||||
|
<AccessibleButton onClick={onClickFn} className="mx_MemberPresenceAvatar" element="div">
|
||||||
|
{ avatarNode }
|
||||||
|
{ statusNode }
|
||||||
|
</AccessibleButton>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return avatar;
|
||||||
|
},
|
||||||
|
});
|
|
@ -381,12 +381,12 @@ export default React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className={this.props.fullWidth ? "mx_AppTileFullWidth" : "mx_AppTile"} id={this.props.id}>
|
<div className={this.props.fullWidth ? "mx_AppTileFullWidth" : "mx_AppTile"} id={this.props.id}>
|
||||||
<div ref="menu_bar" className="mx_AppTileMenuBar" onClick={this.onClickMenuBar}>
|
<div ref="menu_bar" className="mx_AppTileMenuBar" onClick={this.onClickMenuBar}>
|
||||||
{ this.formatAppTileName() }
|
<b>{ this.formatAppTileName() }</b>
|
||||||
<span className="mx_AppTileMenuBarWidgets">
|
<span className="mx_AppTileMenuBarWidgets">
|
||||||
{ /* Edit widget */ }
|
{ /* Edit widget */ }
|
||||||
{ showEditButton && <img
|
{ showEditButton && <img
|
||||||
src="img/edit.svg"
|
src="img/edit_green.svg"
|
||||||
className="mx_filterFlipColor mx_AppTileMenuBarWidget mx_AppTileMenuBarWidgetPadding"
|
className="mx_AppTileMenuBarWidget mx_AppTileMenuBarWidgetPadding"
|
||||||
width="8" height="8"
|
width="8" height="8"
|
||||||
alt={_t('Edit')}
|
alt={_t('Edit')}
|
||||||
title={_t('Edit')}
|
title={_t('Edit')}
|
||||||
|
|
|
@ -20,14 +20,16 @@ import React from 'react';
|
||||||
import * as Roles from '../../../Roles';
|
import * as Roles from '../../../Roles';
|
||||||
import { _t } from '../../../languageHandler';
|
import { _t } from '../../../languageHandler';
|
||||||
|
|
||||||
let LEVEL_ROLE_MAP = {};
|
|
||||||
const reverseRoles = {};
|
|
||||||
|
|
||||||
module.exports = React.createClass({
|
module.exports = React.createClass({
|
||||||
displayName: 'PowerSelector',
|
displayName: 'PowerSelector',
|
||||||
|
|
||||||
propTypes: {
|
propTypes: {
|
||||||
value: React.PropTypes.number.isRequired,
|
value: React.PropTypes.number.isRequired,
|
||||||
|
// The maximum value that can be set with the power selector
|
||||||
|
maxValue: React.PropTypes.number.isRequired,
|
||||||
|
|
||||||
|
// Default user power level for the room
|
||||||
|
usersDefault: React.PropTypes.number.isRequired,
|
||||||
|
|
||||||
// if true, the <select/> should be a 'controlled' form element and updated by React
|
// if true, the <select/> should be a 'controlled' form element and updated by React
|
||||||
// to reflect the current value, rather than left freeform.
|
// to reflect the current value, rather than left freeform.
|
||||||
|
@ -43,78 +45,98 @@ module.exports = React.createClass({
|
||||||
|
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
custom: (LEVEL_ROLE_MAP[this.props.value] === undefined),
|
levelRoleMap: {},
|
||||||
|
// List of power levels to show in the drop-down
|
||||||
|
options: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
maxValue: Infinity,
|
||||||
|
usersDefault: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
LEVEL_ROLE_MAP = Roles.levelRoleMap();
|
this._initStateFromProps(this.props);
|
||||||
Object.keys(LEVEL_ROLE_MAP).forEach(function(key) {
|
},
|
||||||
reverseRoles[LEVEL_ROLE_MAP[key]] = key;
|
|
||||||
|
componentWillReceiveProps: function(newProps) {
|
||||||
|
this._initStateFromProps(newProps);
|
||||||
|
},
|
||||||
|
|
||||||
|
_initStateFromProps: function(newProps) {
|
||||||
|
// This needs to be done now because levelRoleMap has translated strings
|
||||||
|
const levelRoleMap = Roles.levelRoleMap(newProps.usersDefault);
|
||||||
|
const options = Object.keys(levelRoleMap).filter((l) => {
|
||||||
|
return l === undefined || l <= newProps.maxValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
levelRoleMap,
|
||||||
|
options,
|
||||||
|
custom: levelRoleMap[newProps.value] === undefined,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onSelectChange: function(event) {
|
onSelectChange: function(event) {
|
||||||
this.setState({ custom: event.target.value === "Custom" });
|
this.setState({ custom: event.target.value === "SELECT_VALUE_CUSTOM" });
|
||||||
if (event.target.value !== "Custom") {
|
if (event.target.value !== "SELECT_VALUE_CUSTOM") {
|
||||||
this.props.onChange(this.getValue());
|
this.props.onChange(event.target.value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onCustomBlur: function(event) {
|
onCustomBlur: function(event) {
|
||||||
this.props.onChange(this.getValue());
|
this.props.onChange(parseInt(this.refs.custom.value));
|
||||||
},
|
},
|
||||||
|
|
||||||
onCustomKeyDown: function(event) {
|
onCustomKeyDown: function(event) {
|
||||||
if (event.key == "Enter") {
|
if (event.key == "Enter") {
|
||||||
this.props.onChange(this.getValue());
|
this.props.onChange(parseInt(this.refs.custom.value));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
getValue: function() {
|
|
||||||
let value;
|
|
||||||
if (this.refs.select) {
|
|
||||||
value = reverseRoles[this.refs.select.value];
|
|
||||||
if (this.refs.custom) {
|
|
||||||
if (value === undefined) value = parseInt( this.refs.custom.value );
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
},
|
|
||||||
|
|
||||||
render: function() {
|
render: function() {
|
||||||
let customPicker;
|
let customPicker;
|
||||||
if (this.state.custom) {
|
if (this.state.custom) {
|
||||||
let input;
|
|
||||||
if (this.props.disabled) {
|
if (this.props.disabled) {
|
||||||
input = <span>{ this.props.value }</span>;
|
customPicker = <span>{ _t(
|
||||||
|
"Custom of %(powerLevel)s",
|
||||||
|
{ powerLevel: this.props.value },
|
||||||
|
) }</span>;
|
||||||
} else {
|
} else {
|
||||||
input = <input ref="custom" type="text" size="3" defaultValue={this.props.value} onBlur={this.onCustomBlur} onKeyDown={this.onCustomKeyDown} />;
|
customPicker = <span> = <input
|
||||||
|
ref="custom"
|
||||||
|
type="text"
|
||||||
|
size="3"
|
||||||
|
defaultValue={this.props.value}
|
||||||
|
onBlur={this.onCustomBlur}
|
||||||
|
onKeyDown={this.onCustomKeyDown}
|
||||||
|
/>
|
||||||
|
</span>;
|
||||||
}
|
}
|
||||||
customPicker = <span> of { input }</span>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let selectValue;
|
let selectValue;
|
||||||
if (this.state.custom) {
|
if (this.state.custom) {
|
||||||
selectValue = "Custom";
|
selectValue = "SELECT_VALUE_CUSTOM";
|
||||||
} else {
|
} else {
|
||||||
selectValue = LEVEL_ROLE_MAP[this.props.value] || "Custom";
|
selectValue = this.state.levelRoleMap[this.props.value] ?
|
||||||
|
this.props.value : "SELECT_VALUE_CUSTOM";
|
||||||
}
|
}
|
||||||
let select;
|
let select;
|
||||||
if (this.props.disabled) {
|
if (this.props.disabled) {
|
||||||
select = <span>{ selectValue }</span>;
|
select = <span>{ this.state.levelRoleMap[selectValue] }</span>;
|
||||||
} else {
|
} else {
|
||||||
// Each level must have a definition in LEVEL_ROLE_MAP
|
// Each level must have a definition in this.state.levelRoleMap
|
||||||
const levels = [0, 50, 100];
|
let options = this.state.options.map((level) => {
|
||||||
let options = levels.map((level) => {
|
|
||||||
return {
|
return {
|
||||||
value: LEVEL_ROLE_MAP[level],
|
value: level,
|
||||||
// Give a userDefault (users_default in the power event) of 0 but
|
text: Roles.textualPowerLevel(level, this.props.usersDefault),
|
||||||
// because level !== undefined, this should never be used.
|
|
||||||
text: Roles.textualPowerLevel(level, 0),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
options.push({ value: "Custom", text: _t("Custom level") });
|
options.push({ value: "SELECT_VALUE_CUSTOM", text: _t("Custom level") });
|
||||||
options = options.map((op) => {
|
options = options.map((op) => {
|
||||||
return <option value={op.value} key={op.value}>{ op.text }</option>;
|
return <option value={op.value} key={op.value}>{ op.text }</option>;
|
||||||
});
|
});
|
||||||
|
|
|
@ -494,7 +494,6 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
const defaultPerms = {
|
const defaultPerms = {
|
||||||
can: {},
|
can: {},
|
||||||
muted: false,
|
muted: false,
|
||||||
modifyLevel: false,
|
|
||||||
};
|
};
|
||||||
const room = this.props.matrixClient.getRoom(member.roomId);
|
const room = this.props.matrixClient.getRoom(member.roomId);
|
||||||
if (!room) return defaultPerms;
|
if (!room) return defaultPerms;
|
||||||
|
@ -516,13 +515,15 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
},
|
},
|
||||||
|
|
||||||
_calculateCanPermissions: function(me, them, powerLevels) {
|
_calculateCanPermissions: function(me, them, powerLevels) {
|
||||||
|
const isMe = me.userId === them.userId;
|
||||||
const can = {
|
const can = {
|
||||||
kick: false,
|
kick: false,
|
||||||
ban: false,
|
ban: false,
|
||||||
mute: false,
|
mute: false,
|
||||||
modifyLevel: false,
|
modifyLevel: false,
|
||||||
|
modifyLevelMax: 0,
|
||||||
};
|
};
|
||||||
const canAffectUser = them.powerLevel < me.powerLevel;
|
const canAffectUser = them.powerLevel < me.powerLevel || isMe;
|
||||||
if (!canAffectUser) {
|
if (!canAffectUser) {
|
||||||
//console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel);
|
//console.log("Cannot affect user: %s >= %s", them.powerLevel, me.powerLevel);
|
||||||
return can;
|
return can;
|
||||||
|
@ -531,16 +532,13 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
(powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) ||
|
(powerLevels.events ? powerLevels.events["m.room.power_levels"] : null) ||
|
||||||
powerLevels.state_default
|
powerLevels.state_default
|
||||||
);
|
);
|
||||||
const levelToSend = (
|
|
||||||
(powerLevels.events ? powerLevels.events["m.room.message"] : null) ||
|
|
||||||
powerLevels.events_default
|
|
||||||
);
|
|
||||||
|
|
||||||
can.kick = me.powerLevel >= powerLevels.kick;
|
can.kick = me.powerLevel >= powerLevels.kick;
|
||||||
can.ban = me.powerLevel >= powerLevels.ban;
|
can.ban = me.powerLevel >= powerLevels.ban;
|
||||||
can.mute = me.powerLevel >= editPowerLevel;
|
can.mute = me.powerLevel >= editPowerLevel;
|
||||||
can.toggleMod = me.powerLevel > them.powerLevel && them.powerLevel >= levelToSend;
|
can.modifyLevel = me.powerLevel >= editPowerLevel && (isMe || me.powerLevel > them.powerLevel);
|
||||||
can.modifyLevel = me.powerLevel > them.powerLevel && me.powerLevel >= editPowerLevel;
|
can.modifyLevelMax = me.powerLevel;
|
||||||
|
|
||||||
return can;
|
return can;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -832,8 +830,11 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
presenceCurrentlyActive = this.props.member.user.currentlyActive;
|
presenceCurrentlyActive = this.props.member.user.currentlyActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
let roomMemberDetails = null;
|
const room = this.props.matrixClient.getRoom(this.props.member.roomId);
|
||||||
|
const poweLevelEvent = room ? room.currentState.getStateEvents("m.room.power_levels", "") : null;
|
||||||
|
const powerLevelUsersDefault = poweLevelEvent.getContent().users_default;
|
||||||
|
|
||||||
|
let roomMemberDetails = null;
|
||||||
if (this.props.member.roomId) { // is in room
|
if (this.props.member.roomId) { // is in room
|
||||||
const PowerSelector = sdk.getComponent('elements.PowerSelector');
|
const PowerSelector = sdk.getComponent('elements.PowerSelector');
|
||||||
const PresenceLabel = sdk.getComponent('rooms.PresenceLabel');
|
const PresenceLabel = sdk.getComponent('rooms.PresenceLabel');
|
||||||
|
@ -842,7 +843,9 @@ module.exports = withMatrixClient(React.createClass({
|
||||||
{ _t("Level:") } <b>
|
{ _t("Level:") } <b>
|
||||||
<PowerSelector controlled={true}
|
<PowerSelector controlled={true}
|
||||||
value={parseInt(this.props.member.powerLevel)}
|
value={parseInt(this.props.member.powerLevel)}
|
||||||
|
maxValue={this.state.can.modifyLevelMax}
|
||||||
disabled={!this.state.can.modifyLevel}
|
disabled={!this.state.can.modifyLevel}
|
||||||
|
usersDefault={powerLevelUsersDefault}
|
||||||
onChange={this.onPowerChange} />
|
onChange={this.onPowerChange} />
|
||||||
</b>
|
</b>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -238,7 +238,7 @@ export default class MessageComposer extends React.Component {
|
||||||
render() {
|
render() {
|
||||||
const me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
|
const me = this.props.room.getMember(MatrixClientPeg.get().credentials.userId);
|
||||||
const uploadInputStyle = {display: 'none'};
|
const uploadInputStyle = {display: 'none'};
|
||||||
const MemberAvatar = sdk.getComponent('avatars.MemberAvatar');
|
const MemberPresenceAvatar = sdk.getComponent('avatars.MemberPresenceAvatar');
|
||||||
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
const TintableSvg = sdk.getComponent("elements.TintableSvg");
|
||||||
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
|
const MessageComposerInput = sdk.getComponent("rooms.MessageComposerInput");
|
||||||
|
|
||||||
|
@ -246,7 +246,7 @@ export default class MessageComposer extends React.Component {
|
||||||
|
|
||||||
controls.push(
|
controls.push(
|
||||||
<div key="controls_avatar" className="mx_MessageComposer_avatar">
|
<div key="controls_avatar" className="mx_MessageComposer_avatar">
|
||||||
<MemberAvatar member={me} width={24} height={24} />
|
<MemberPresenceAvatar member={me} width={24} height={24} />
|
||||||
</div>,
|
</div>,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -901,31 +901,31 @@ module.exports = React.createClass({
|
||||||
<div className="mx_RoomSettings_powerLevels mx_RoomSettings_settings">
|
<div className="mx_RoomSettings_powerLevels mx_RoomSettings_settings">
|
||||||
<div className="mx_RoomSettings_powerLevel">
|
<div className="mx_RoomSettings_powerLevel">
|
||||||
<span className="mx_RoomSettings_powerLevelKey">{ _t('The default role for new room members is') } </span>
|
<span className="mx_RoomSettings_powerLevelKey">{ _t('The default role for new room members is') } </span>
|
||||||
<PowerSelector ref="users_default" value={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < default_user_level} onChange={this.onPowerLevelsChanged} />
|
<PowerSelector ref="users_default" value={default_user_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < default_user_level} onChange={this.onPowerLevelsChanged} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomSettings_powerLevel">
|
<div className="mx_RoomSettings_powerLevel">
|
||||||
<span className="mx_RoomSettings_powerLevelKey">{ _t('To send messages, you must be a') } </span>
|
<span className="mx_RoomSettings_powerLevelKey">{ _t('To send messages, you must be a') } </span>
|
||||||
<PowerSelector ref="events_default" value={send_level} controlled={false} disabled={!can_change_levels || current_user_level < send_level} onChange={this.onPowerLevelsChanged} />
|
<PowerSelector ref="events_default" value={send_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < send_level} onChange={this.onPowerLevelsChanged} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomSettings_powerLevel">
|
<div className="mx_RoomSettings_powerLevel">
|
||||||
<span className="mx_RoomSettings_powerLevelKey">{ _t('To invite users into the room, you must be a') } </span>
|
<span className="mx_RoomSettings_powerLevelKey">{ _t('To invite users into the room, you must be a') } </span>
|
||||||
<PowerSelector ref="invite" value={invite_level} controlled={false} disabled={!can_change_levels || current_user_level < invite_level} onChange={this.onPowerLevelsChanged} />
|
<PowerSelector ref="invite" value={invite_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < invite_level} onChange={this.onPowerLevelsChanged} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomSettings_powerLevel">
|
<div className="mx_RoomSettings_powerLevel">
|
||||||
<span className="mx_RoomSettings_powerLevelKey">{ _t('To configure the room, you must be a') } </span>
|
<span className="mx_RoomSettings_powerLevelKey">{ _t('To configure the room, you must be a') } </span>
|
||||||
<PowerSelector ref="state_default" value={state_level} controlled={false} disabled={!can_change_levels || current_user_level < state_level} onChange={this.onPowerLevelsChanged} />
|
<PowerSelector ref="state_default" value={state_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < state_level} onChange={this.onPowerLevelsChanged} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomSettings_powerLevel">
|
<div className="mx_RoomSettings_powerLevel">
|
||||||
<span className="mx_RoomSettings_powerLevelKey">{ _t('To kick users, you must be a') } </span>
|
<span className="mx_RoomSettings_powerLevelKey">{ _t('To kick users, you must be a') } </span>
|
||||||
<PowerSelector ref="kick" value={kick_level} controlled={false} disabled={!can_change_levels || current_user_level < kick_level} onChange={this.onPowerLevelsChanged} />
|
<PowerSelector ref="kick" value={kick_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < kick_level} onChange={this.onPowerLevelsChanged} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomSettings_powerLevel">
|
<div className="mx_RoomSettings_powerLevel">
|
||||||
<span className="mx_RoomSettings_powerLevelKey">{ _t('To ban users, you must be a') } </span>
|
<span className="mx_RoomSettings_powerLevelKey">{ _t('To ban users, you must be a') } </span>
|
||||||
<PowerSelector ref="ban" value={ban_level} controlled={false} disabled={!can_change_levels || current_user_level < ban_level} onChange={this.onPowerLevelsChanged} />
|
<PowerSelector ref="ban" value={ban_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < ban_level} onChange={this.onPowerLevelsChanged} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomSettings_powerLevel">
|
<div className="mx_RoomSettings_powerLevel">
|
||||||
<span className="mx_RoomSettings_powerLevelKey">{ _t('To remove other users\' messages, you must be a') } </span>
|
<span className="mx_RoomSettings_powerLevelKey">{ _t('To remove other users\' messages, you must be a') } </span>
|
||||||
<PowerSelector ref="redact" value={redact_level} controlled={false} disabled={!can_change_levels || current_user_level < redact_level} onChange={this.onPowerLevelsChanged} />
|
<PowerSelector ref="redact" value={redact_level} usersDefault={default_user_level} controlled={false} disabled={!can_change_levels || current_user_level < redact_level} onChange={this.onPowerLevelsChanged} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ Object.keys(events_levels).map(function(event_type, i) {
|
{ Object.keys(events_levels).map(function(event_type, i) {
|
||||||
|
@ -935,7 +935,7 @@ module.exports = React.createClass({
|
||||||
return (
|
return (
|
||||||
<div className="mx_RoomSettings_powerLevel" key={event_type}>
|
<div className="mx_RoomSettings_powerLevel" key={event_type}>
|
||||||
<span className="mx_RoomSettings_powerLevelKey">{ label } </span>
|
<span className="mx_RoomSettings_powerLevelKey">{ label } </span>
|
||||||
<PowerSelector ref={"event_levels_"+event_type} value={events_levels[event_type]} onChange={self.onPowerLevelsChanged}
|
<PowerSelector ref={"event_levels_"+event_type} value={events_levels[event_type]} usersDefault={default_user_level} onChange={self.onPowerLevelsChanged}
|
||||||
controlled={false} disabled={!can_change_levels || current_user_level < events_levels[event_type]} />
|
controlled={false} disabled={!can_change_levels || current_user_level < events_levels[event_type]} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -63,7 +63,7 @@
|
||||||
"This email address was not found": "This email address was not found",
|
"This email address was not found": "This email address was not found",
|
||||||
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Your email address does not appear to be associated with a Matrix ID on this Homeserver.",
|
"Your email address does not appear to be associated with a Matrix ID on this Homeserver.": "Your email address does not appear to be associated with a Matrix ID on this Homeserver.",
|
||||||
"Default": "Default",
|
"Default": "Default",
|
||||||
"User": "User",
|
"Restricted": "Restricted",
|
||||||
"Moderator": "Moderator",
|
"Moderator": "Moderator",
|
||||||
"Admin": "Admin",
|
"Admin": "Admin",
|
||||||
"Start a chat": "Start a chat",
|
"Start a chat": "Start a chat",
|
||||||
|
@ -150,8 +150,8 @@
|
||||||
"%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s",
|
"%(widgetName)s widget modified by %(senderName)s": "%(widgetName)s widget modified by %(senderName)s",
|
||||||
"%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s",
|
"%(widgetName)s widget added by %(senderName)s": "%(widgetName)s widget added by %(senderName)s",
|
||||||
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s",
|
"%(widgetName)s widget removed by %(senderName)s": "%(widgetName)s widget removed by %(senderName)s",
|
||||||
"Communities": "Communities",
|
|
||||||
"Message Pinning": "Message Pinning",
|
"Message Pinning": "Message Pinning",
|
||||||
|
"Presence Management": "Presence Management",
|
||||||
"%(displayName)s is typing": "%(displayName)s is typing",
|
"%(displayName)s is typing": "%(displayName)s is typing",
|
||||||
"%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing",
|
"%(names)s and %(count)s others are typing|other": "%(names)s and %(count)s others are typing",
|
||||||
"%(names)s and %(count)s others are typing|one": "%(names)s and one other is typing",
|
"%(names)s and %(count)s others are typing|one": "%(names)s and one other is typing",
|
||||||
|
@ -524,6 +524,7 @@
|
||||||
"Unverify": "Unverify",
|
"Unverify": "Unverify",
|
||||||
"Verify...": "Verify...",
|
"Verify...": "Verify...",
|
||||||
"No results": "No results",
|
"No results": "No results",
|
||||||
|
"Communities": "Communities",
|
||||||
"Home": "Home",
|
"Home": "Home",
|
||||||
"Integrations Error": "Integrations Error",
|
"Integrations Error": "Integrations Error",
|
||||||
"Could not connect to the integration server": "Could not connect to the integration server",
|
"Could not connect to the integration server": "Could not connect to the integration server",
|
||||||
|
@ -580,6 +581,7 @@
|
||||||
"%(items)s and %(count)s others|other": "%(items)s and %(count)s others",
|
"%(items)s and %(count)s others|other": "%(items)s and %(count)s others",
|
||||||
"%(items)s and %(count)s others|one": "%(items)s and one other",
|
"%(items)s and %(count)s others|one": "%(items)s and one other",
|
||||||
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
|
"%(items)s and %(lastItem)s": "%(items)s and %(lastItem)s",
|
||||||
|
"Custom of %(powerLevel)s": "Custom of %(powerLevel)s",
|
||||||
"Custom level": "Custom level",
|
"Custom level": "Custom level",
|
||||||
"Room directory": "Room directory",
|
"Room directory": "Room directory",
|
||||||
"Start chat": "Start chat",
|
"Start chat": "Start chat",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue