Factor out EditableItemList from AliasSettings
Such that we can reuse the same UI elsewhere, namely when editing related groups of a room (which is an upcoming feature).
This commit is contained in:
parent
03581adf85
commit
8243c39d83
2 changed files with 204 additions and 74 deletions
162
src/components/views/elements/EditableItemList.js
Normal file
162
src/components/views/elements/EditableItemList.js
Normal file
|
@ -0,0 +1,162 @@
|
||||||
|
/*
|
||||||
|
Copyright 2017 New Vector Ltd.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import sdk from '../../../index';
|
||||||
|
import {_t} from '../../../languageHandler.js';
|
||||||
|
|
||||||
|
const EditableItem = React.createClass({
|
||||||
|
displayName: 'EditableItem',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
initialValue: PropTypes.string,
|
||||||
|
index: PropTypes.number,
|
||||||
|
placeholder: PropTypes.string,
|
||||||
|
|
||||||
|
onChange: PropTypes.func,
|
||||||
|
onRemove: PropTypes.func,
|
||||||
|
onAdd: PropTypes.func,
|
||||||
|
},
|
||||||
|
|
||||||
|
onChange: function(value) {
|
||||||
|
this.setState({ value });
|
||||||
|
if (this.props.onChange) this.props.onChange(value, this.props.index);
|
||||||
|
},
|
||||||
|
|
||||||
|
onRemove: function() {
|
||||||
|
this.props.onRemove(this.props.index);
|
||||||
|
},
|
||||||
|
|
||||||
|
onAdd: function() {
|
||||||
|
this.props.onAdd(this.state.value);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const EditableText = sdk.getComponent('elements.EditableText');
|
||||||
|
return <div className="mx_EditableItem">
|
||||||
|
<EditableText
|
||||||
|
className="mx_EditableItem_editable"
|
||||||
|
placeholderClassName="mx_EditableItem_editablePlaceholder"
|
||||||
|
placeholder={this.props.placeholder}
|
||||||
|
blurToCancel={false}
|
||||||
|
editable={true}
|
||||||
|
initialValue={this.props.initialValue}
|
||||||
|
onValueChanged={this.onChange} />
|
||||||
|
{ this.props.onAdd ?
|
||||||
|
<div className="mx_EditableItem_addButton">
|
||||||
|
<img className="mx_filterFlipColor"
|
||||||
|
src="img/plus.svg" width="14" height="14"
|
||||||
|
alt={_t("Add")} onClick={this.onAdd} />
|
||||||
|
</div>
|
||||||
|
:
|
||||||
|
<div className="mx_EditableItem_removeButton">
|
||||||
|
<img className="mx_filterFlipColor"
|
||||||
|
src="img/cancel-small.svg" width="14" height="14"
|
||||||
|
alt={_t("Delete")} onClick={this.onRemove} />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = React.createClass({
|
||||||
|
displayName: 'EditableItemList',
|
||||||
|
|
||||||
|
propTypes: {
|
||||||
|
items: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||||
|
onNewItemChanged: PropTypes.func,
|
||||||
|
onItemAdded: PropTypes.func,
|
||||||
|
onItemEdited: PropTypes.func,
|
||||||
|
onItemRemoved: PropTypes. func,
|
||||||
|
},
|
||||||
|
|
||||||
|
getDefaultProps: function() {
|
||||||
|
return {
|
||||||
|
onItemAdded: () => {},
|
||||||
|
onItemEdited: () => {},
|
||||||
|
onItemRemoved: () => {},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
getInitialState: function() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillReceiveProps: function(nextProps) {
|
||||||
|
},
|
||||||
|
|
||||||
|
componentWillMount: function() {
|
||||||
|
},
|
||||||
|
|
||||||
|
componentDidMount: function() {
|
||||||
|
},
|
||||||
|
|
||||||
|
onItemAdded: function(value) {
|
||||||
|
console.info('onItemAdded', value);
|
||||||
|
this.props.onItemAdded(value);
|
||||||
|
},
|
||||||
|
|
||||||
|
onItemEdited: function(value, index) {
|
||||||
|
console.info('onItemEdited', value, index);
|
||||||
|
if (value.length === 0) {
|
||||||
|
this.onItemRemoved(index);
|
||||||
|
} else {
|
||||||
|
this.onItemEdited(value, index);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onItemRemoved: function(index) {
|
||||||
|
console.info('onItemRemoved', index);
|
||||||
|
this.props.onItemRemoved(index);
|
||||||
|
},
|
||||||
|
|
||||||
|
onNewItemChanged: function(value) {
|
||||||
|
this.props.onNewItemChanged(value);
|
||||||
|
},
|
||||||
|
|
||||||
|
render: function() {
|
||||||
|
const editableItems = this.props.items.map((item, index) => {
|
||||||
|
return <EditableItem
|
||||||
|
key={index}
|
||||||
|
index={index}
|
||||||
|
initialValue={item}
|
||||||
|
onChange={this.onItemEdited}
|
||||||
|
onRemove={this.onItemRemoved}
|
||||||
|
placeholder={this.props.placeholder}
|
||||||
|
/>;
|
||||||
|
});
|
||||||
|
|
||||||
|
const label = this.props.items.length > 0 ?
|
||||||
|
this.props.itemsLabel : this.props.noItemsLabel;
|
||||||
|
|
||||||
|
console.info('New item:', this.props.newItem);
|
||||||
|
|
||||||
|
return (<div className="mx_EditableItemList">
|
||||||
|
<div className="mx_EditableItemList_label">
|
||||||
|
{ label }
|
||||||
|
</div>
|
||||||
|
{ editableItems }
|
||||||
|
<EditableItem
|
||||||
|
key={-1}
|
||||||
|
initialValue={this.props.newItem}
|
||||||
|
onAdd={this.onItemAdded}
|
||||||
|
onChange={this.onNewItemChanged}
|
||||||
|
placeholder={this.props.placeholder}
|
||||||
|
/>
|
||||||
|
</div>);
|
||||||
|
},
|
||||||
|
});
|
|
@ -136,24 +136,25 @@ module.exports = React.createClass({
|
||||||
return ObjectUtils.getKeyValueArrayDiffs(oldAliases, this.state.domainToAliases);
|
return ObjectUtils.getKeyValueArrayDiffs(oldAliases, this.state.domainToAliases);
|
||||||
},
|
},
|
||||||
|
|
||||||
onAliasAdded: function(alias) {
|
onNewAliasChanged: function(value) {
|
||||||
|
this.setState({newAlias: value});
|
||||||
|
},
|
||||||
|
|
||||||
|
onLocalAliasAdded: function(alias) {
|
||||||
if (!alias || alias.length === 0) return; // ignore attempts to create blank aliases
|
if (!alias || alias.length === 0) return; // ignore attempts to create blank aliases
|
||||||
|
|
||||||
if (this.isAliasValid(alias)) {
|
const localDomain = MatrixClientPeg.get().getDomain();
|
||||||
// add this alias to the domain to aliases dict
|
if (this.isAliasValid(alias) && alias.endsWith(localDomain)) {
|
||||||
var domain = alias.replace(/^.*?:/, '');
|
this.state.domainToAliases[localDomain] = this.state.domainToAliases[localDomain] || [];
|
||||||
// XXX: do we need to deep copy aliases before editing it?
|
this.state.domainToAliases[localDomain].push(alias);
|
||||||
this.state.domainToAliases[domain] = this.state.domainToAliases[domain] || [];
|
|
||||||
this.state.domainToAliases[domain].push(alias);
|
|
||||||
this.setState({
|
|
||||||
domainToAliases: this.state.domainToAliases
|
|
||||||
});
|
|
||||||
|
|
||||||
// reset the add field
|
this.setState({
|
||||||
this.refs.add_alias.setValue(''); // FIXME
|
domainToAliases: this.state.domainToAliases,
|
||||||
}
|
// Reset the add field
|
||||||
else {
|
newAlias: "",
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
});
|
||||||
|
} else {
|
||||||
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
Modal.createTrackedDialog('Invalid alias format', '', ErrorDialog, {
|
Modal.createTrackedDialog('Invalid alias format', '', ErrorDialog, {
|
||||||
title: _t('Invalid alias format'),
|
title: _t('Invalid alias format'),
|
||||||
description: _t('\'%(alias)s\' is not a valid format for an alias', { alias: alias }),
|
description: _t('\'%(alias)s\' is not a valid format for an alias', { alias: alias }),
|
||||||
|
@ -161,15 +162,13 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onAliasChanged: function(domain, index, alias) {
|
onLocalAliasChanged: function(alias, index) {
|
||||||
if (alias === "") return; // hit the delete button to delete please
|
if (alias === "") return; // hit the delete button to delete please
|
||||||
var oldAlias;
|
const localDomain = MatrixClientPeg.get().getDomain();
|
||||||
if (this.isAliasValid(alias)) {
|
if (this.isAliasValid(alias) && alias.endsWith(localDomain)) {
|
||||||
oldAlias = this.state.domainToAliases[domain][index];
|
this.state.domainToAliases[localDomain][index] = alias;
|
||||||
this.state.domainToAliases[domain][index] = alias;
|
} else {
|
||||||
}
|
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||||
else {
|
|
||||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
|
||||||
Modal.createTrackedDialog('Invalid address format', '', ErrorDialog, {
|
Modal.createTrackedDialog('Invalid address format', '', ErrorDialog, {
|
||||||
title: _t('Invalid address format'),
|
title: _t('Invalid address format'),
|
||||||
description: _t('\'%(alias)s\' is not a valid format for an address', { alias: alias }),
|
description: _t('\'%(alias)s\' is not a valid format for an address', { alias: alias }),
|
||||||
|
@ -177,15 +176,16 @@ module.exports = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onAliasDeleted: function(domain, index) {
|
onLocalAliasDeleted: function(index) {
|
||||||
|
const localDomain = MatrixClientPeg.get().getDomain();
|
||||||
// It's a bit naughty to directly manipulate this.state, and React would
|
// It's a bit naughty to directly manipulate this.state, and React would
|
||||||
// normally whine at you, but it can't see us doing the splice. Given we
|
// normally whine at you, but it can't see us doing the splice. Given we
|
||||||
// promptly setState anyway, it's just about acceptable. The alternative
|
// promptly setState anyway, it's just about acceptable. The alternative
|
||||||
// would be to arbitrarily deepcopy to a temp variable and then setState
|
// would be to arbitrarily deepcopy to a temp variable and then setState
|
||||||
// that, but why bother when we can cut this corner.
|
// that, but why bother when we can cut this corner.
|
||||||
var alias = this.state.domainToAliases[domain].splice(index, 1);
|
this.state.domainToAliases[localDomain].splice(index, 1);
|
||||||
this.setState({
|
this.setState({
|
||||||
domainToAliases: this.state.domainToAliases
|
domainToAliases: this.state.domainToAliases,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -198,6 +198,7 @@ module.exports = React.createClass({
|
||||||
render: function() {
|
render: function() {
|
||||||
var self = this;
|
var self = this;
|
||||||
var EditableText = sdk.getComponent("elements.EditableText");
|
var EditableText = sdk.getComponent("elements.EditableText");
|
||||||
|
var EditableItemList = sdk.getComponent("elements.EditableItemList");
|
||||||
var localDomain = MatrixClientPeg.get().getDomain();
|
var localDomain = MatrixClientPeg.get().getDomain();
|
||||||
|
|
||||||
var canonical_alias_section;
|
var canonical_alias_section;
|
||||||
|
@ -257,58 +258,25 @@ module.exports = React.createClass({
|
||||||
<div className="mx_RoomSettings_aliasLabel">
|
<div className="mx_RoomSettings_aliasLabel">
|
||||||
{ _t('The main address for this room is') }: { canonical_alias_section }
|
{ _t('The main address for this room is') }: { canonical_alias_section }
|
||||||
</div>
|
</div>
|
||||||
<div className="mx_RoomSettings_aliasLabel">
|
|
||||||
{ (this.state.domainToAliases[localDomain] &&
|
|
||||||
this.state.domainToAliases[localDomain].length > 0)
|
|
||||||
? _t('Local addresses for this room:')
|
|
||||||
: _t('This room has no local addresses') }
|
|
||||||
</div>
|
|
||||||
<div className="mx_RoomSettings_aliasesTable">
|
|
||||||
{ (this.state.domainToAliases[localDomain] || []).map((alias, i) => {
|
|
||||||
var deleteButton;
|
|
||||||
if (this.props.canSetAliases) {
|
|
||||||
deleteButton = (
|
|
||||||
<img src="img/cancel-small.svg" width="14" height="14"
|
|
||||||
alt={ _t('Delete') } onClick={ self.onAliasDeleted.bind(self, localDomain, i) } />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div className="mx_RoomSettings_aliasesTableRow" key={ i }>
|
|
||||||
<EditableText
|
|
||||||
className="mx_RoomSettings_alias mx_RoomSettings_editable"
|
|
||||||
placeholderClassName="mx_RoomSettings_aliasPlaceholder"
|
|
||||||
placeholder={ _t('New address (e.g. #foo:%(localDomain)s)', { localDomain: localDomain}) }
|
|
||||||
blurToCancel={ false }
|
|
||||||
onValueChanged={ self.onAliasChanged.bind(self, localDomain, i) }
|
|
||||||
editable={ self.props.canSetAliases }
|
|
||||||
initialValue={ alias } />
|
|
||||||
<div className="mx_RoomSettings_deleteAlias mx_filterFlipColor">
|
|
||||||
{ deleteButton }
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
|
|
||||||
{ this.props.canSetAliases ?
|
<EditableItemList
|
||||||
<div className="mx_RoomSettings_aliasesTableRow" key="new">
|
className={"mx_RoomSettings_localAliases"}
|
||||||
<EditableText
|
items={this.state.domainToAliases[localDomain] || []}
|
||||||
ref="add_alias"
|
newItem={this.state.newAlias}
|
||||||
className="mx_RoomSettings_alias mx_RoomSettings_editable"
|
onNewItemChanged={onNewAliasChanged}
|
||||||
placeholderClassName="mx_RoomSettings_aliasPlaceholder"
|
onItemAdded={this.onLocalAliasAdded}
|
||||||
placeholder={ _t('New address (e.g. #foo:%(localDomain)s)', { localDomain: localDomain}) }
|
onItemEdited={this.onLocalAliasChanged}
|
||||||
blurToCancel={ false }
|
onItemRemoved={this.onLocalAliasDeleted}
|
||||||
onValueChanged={ self.onAliasAdded } />
|
itemsLabel={_t('Local addresses for this room:')}
|
||||||
<div className="mx_RoomSettings_addAlias mx_filterFlipColor">
|
noItemsLabel={_t('This room has no local addresses')}
|
||||||
<img src="img/plus.svg" width="14" height="14" alt="Add"
|
placeholder={_t(
|
||||||
onClick={ self.onAliasAdded.bind(self, undefined) }/>
|
'New address (e.g. #foo:%(localDomain)s)', {localDomain: localDomain},
|
||||||
</div>
|
)}
|
||||||
</div> : ""
|
/>
|
||||||
}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{ remote_aliases_section }
|
{ remote_aliases_section }
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue