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);
|
||||
},
|
||||
|
||||
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 (this.isAliasValid(alias)) {
|
||||
// add this alias to the domain to aliases dict
|
||||
var domain = alias.replace(/^.*?:/, '');
|
||||
// XXX: do we need to deep copy aliases before editing it?
|
||||
this.state.domainToAliases[domain] = this.state.domainToAliases[domain] || [];
|
||||
this.state.domainToAliases[domain].push(alias);
|
||||
this.setState({
|
||||
domainToAliases: this.state.domainToAliases
|
||||
});
|
||||
const localDomain = MatrixClientPeg.get().getDomain();
|
||||
if (this.isAliasValid(alias) && alias.endsWith(localDomain)) {
|
||||
this.state.domainToAliases[localDomain] = this.state.domainToAliases[localDomain] || [];
|
||||
this.state.domainToAliases[localDomain].push(alias);
|
||||
|
||||
// reset the add field
|
||||
this.refs.add_alias.setValue(''); // FIXME
|
||||
}
|
||||
else {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
this.setState({
|
||||
domainToAliases: this.state.domainToAliases,
|
||||
// Reset the add field
|
||||
newAlias: "",
|
||||
});
|
||||
} else {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Invalid alias format', '', ErrorDialog, {
|
||||
title: _t('Invalid alias format'),
|
||||
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
|
||||
var oldAlias;
|
||||
if (this.isAliasValid(alias)) {
|
||||
oldAlias = this.state.domainToAliases[domain][index];
|
||||
this.state.domainToAliases[domain][index] = alias;
|
||||
}
|
||||
else {
|
||||
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
const localDomain = MatrixClientPeg.get().getDomain();
|
||||
if (this.isAliasValid(alias) && alias.endsWith(localDomain)) {
|
||||
this.state.domainToAliases[localDomain][index] = alias;
|
||||
} else {
|
||||
const ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
|
||||
Modal.createTrackedDialog('Invalid address format', '', ErrorDialog, {
|
||||
title: _t('Invalid address format'),
|
||||
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
|
||||
// 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
|
||||
// would be to arbitrarily deepcopy to a temp variable and then setState
|
||||
// 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({
|
||||
domainToAliases: this.state.domainToAliases
|
||||
domainToAliases: this.state.domainToAliases,
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -198,6 +198,7 @@ module.exports = React.createClass({
|
|||
render: function() {
|
||||
var self = this;
|
||||
var EditableText = sdk.getComponent("elements.EditableText");
|
||||
var EditableItemList = sdk.getComponent("elements.EditableItemList");
|
||||
var localDomain = MatrixClientPeg.get().getDomain();
|
||||
|
||||
var canonical_alias_section;
|
||||
|
@ -257,58 +258,25 @@ module.exports = React.createClass({
|
|||
<div className="mx_RoomSettings_aliasLabel">
|
||||
{ _t('The main address for this room is') }: { canonical_alias_section }
|
||||
</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 ?
|
||||
<div className="mx_RoomSettings_aliasesTableRow" key="new">
|
||||
<EditableText
|
||||
ref="add_alias"
|
||||
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.onAliasAdded } />
|
||||
<div className="mx_RoomSettings_addAlias mx_filterFlipColor">
|
||||
<img src="img/plus.svg" width="14" height="14" alt="Add"
|
||||
onClick={ self.onAliasAdded.bind(self, undefined) }/>
|
||||
</div>
|
||||
</div> : ""
|
||||
}
|
||||
</div>
|
||||
<EditableItemList
|
||||
className={"mx_RoomSettings_localAliases"}
|
||||
items={this.state.domainToAliases[localDomain] || []}
|
||||
newItem={this.state.newAlias}
|
||||
onNewItemChanged={onNewAliasChanged}
|
||||
onItemAdded={this.onLocalAliasAdded}
|
||||
onItemEdited={this.onLocalAliasChanged}
|
||||
onItemRemoved={this.onLocalAliasDeleted}
|
||||
itemsLabel={_t('Local addresses for this room:')}
|
||||
noItemsLabel={_t('This room has no local addresses')}
|
||||
placeholder={_t(
|
||||
'New address (e.g. #foo:%(localDomain)s)', {localDomain: localDomain},
|
||||
)}
|
||||
/>
|
||||
|
||||
{ remote_aliases_section }
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue