Extract list selection into a mixin
parent
f4374ec5f6
commit
3cd3685448
241
mail.js
241
mail.js
|
@ -736,7 +736,131 @@ function formatDate(dt)
|
||||||
return (h < 10 ? '0' : '')+h+':'+(m < 10 ? '0' : '')+m;
|
return (h < 10 ? '0' : '')+h+':'+(m < 10 ? '0' : '')+m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Common selection mixin
|
||||||
|
var ListWithSelection = {
|
||||||
|
// requires to override methods: this.deleteSelected(), this.getPageSize(), this.getItemOffset(index), this.getTotalItems()
|
||||||
|
getInitialState: function()
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
selected: {}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
onListKeyDown: function(ev)
|
||||||
|
{
|
||||||
|
if (!this.getTotalItems())
|
||||||
|
return;
|
||||||
|
if (ev.keyCode == 46) // delete
|
||||||
|
{
|
||||||
|
this.deleteSelected();
|
||||||
|
this.setState({ selected: {} });
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
else if (ev.keyCode == 38 || ev.keyCode == 40 || ev.keyCode == 33 || ev.keyCode == 34) // up, down, pgup, pgdown
|
||||||
|
{
|
||||||
|
var sel = this.curSel, dir;
|
||||||
|
if (ev.keyCode < 35)
|
||||||
|
dir = (ev.keyCode == 34 ? 1 : -1) * this.getPageSize();
|
||||||
|
else
|
||||||
|
dir = (ev.keyCode == 40 ? 1 : -1);
|
||||||
|
if (sel !== null)
|
||||||
|
{
|
||||||
|
var nsel = sel+dir;
|
||||||
|
if (nsel < 0)
|
||||||
|
nsel = 0;
|
||||||
|
if (nsel >= this.getTotalItems())
|
||||||
|
nsel = this.getTotalItems()-1;
|
||||||
|
if (sel != nsel)
|
||||||
|
{
|
||||||
|
if (ev.shiftKey)
|
||||||
|
this.selectTo(nsel);
|
||||||
|
else
|
||||||
|
this.selectOne(nsel);
|
||||||
|
var pos = this.getItemOffset(nsel);
|
||||||
|
if (pos[0] + pos[1] > this.refs.scroll.scrollTop + this.refs.scroll.offsetHeight)
|
||||||
|
this.refs.scroll.scrollTop = pos[0] + pos[1] - this.refs.scroll.offsetHeight;
|
||||||
|
else if (pos[0] < this.refs.scroll.scrollTop + this.getScrollPaddingTop())
|
||||||
|
this.refs.scroll.scrollTop = pos[0] - this.getScrollPaddingTop();
|
||||||
|
ev.stopPropagation();
|
||||||
|
}
|
||||||
|
ev.preventDefault(); // prevent scroll
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ev.keyCode == 36) // home
|
||||||
|
{
|
||||||
|
if (ev.shiftKey)
|
||||||
|
{
|
||||||
|
this.selectTo(0);
|
||||||
|
this.refs.scroll.scrollTop = pos[0] - this.getScrollPaddingTop();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.selectOne(0);
|
||||||
|
}
|
||||||
|
else if (ev.keyCode == 35) // end
|
||||||
|
{
|
||||||
|
var nsel = this.getTotalItems()-1;
|
||||||
|
if (ev.shiftKey)
|
||||||
|
{
|
||||||
|
this.selectTo(nsel);
|
||||||
|
var pos = this.getItemOffset(nsel);
|
||||||
|
this.refs.scroll.scrollTop = pos[0] + pos[1] - this.refs.scroll.offsetHeight;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.selectOne(nsel);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectTo: function(ns)
|
||||||
|
{
|
||||||
|
if (this.lastSel === undefined)
|
||||||
|
return this.selectOne(ns);
|
||||||
|
var sel = { ns: 1 };
|
||||||
|
if (this.lastSel >= this.getTotalItems())
|
||||||
|
this.lastSel = this.getTotalItems()-1;
|
||||||
|
if (ns < this.lastSel)
|
||||||
|
{
|
||||||
|
for (var i = ns; i <= this.lastSel; i++)
|
||||||
|
sel[i] = true;
|
||||||
|
}
|
||||||
|
else if (ns > this.lastSel)
|
||||||
|
{
|
||||||
|
for (var i = this.lastSel; i <= ns; i++)
|
||||||
|
sel[i] = true;
|
||||||
|
}
|
||||||
|
this.setState({ selected: sel });
|
||||||
|
this.curSel = ns;
|
||||||
|
},
|
||||||
|
selectOne: function(ns)
|
||||||
|
{
|
||||||
|
var sel = {};
|
||||||
|
sel[ns] = true;
|
||||||
|
this.setState({ selected: sel });
|
||||||
|
this.lastSel = ns;
|
||||||
|
this.curSel = ns;
|
||||||
|
},
|
||||||
|
onListItemClick: function(ev)
|
||||||
|
{
|
||||||
|
var t = ev.target;
|
||||||
|
while (t && !t.getAttribute('data-i'))
|
||||||
|
t = t.parentNode;
|
||||||
|
if (t)
|
||||||
|
{
|
||||||
|
var ns = parseInt(t.getAttribute('data-i'));
|
||||||
|
if (ev.shiftKey)
|
||||||
|
this.selectTo(ns);
|
||||||
|
else if (ev.ctrlKey)
|
||||||
|
{
|
||||||
|
this.state.selected[ns] = true;
|
||||||
|
this.curSel = ns;
|
||||||
|
this.lastSel = this.lastSel === undefined ? ns : this.lastSel;
|
||||||
|
this.setState({ selected: this.state.selected });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.selectOne(ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
var AttachList = React.createClass({
|
var AttachList = React.createClass({
|
||||||
|
mixins: [ ListWithSelection ],
|
||||||
getInitialState: function()
|
getInitialState: function()
|
||||||
{
|
{
|
||||||
return {
|
return {
|
||||||
|
@ -758,108 +882,29 @@ var AttachList = React.createClass({
|
||||||
{
|
{
|
||||||
this.setState({ attachScroll: ev.target.scrollTop });
|
this.setState({ attachScroll: ev.target.scrollTop });
|
||||||
},
|
},
|
||||||
attachKeyDown: function(ev)
|
deleteSelected: function()
|
||||||
{
|
{
|
||||||
if (!this.state.attachments.length)
|
for (var i = this.state.attachments.length-1; i >= 0; i--)
|
||||||
return;
|
if (this.state.selected[i])
|
||||||
if (ev.keyCode == 46) // delete
|
this.state.attachments.splice(i, 1);
|
||||||
{
|
|
||||||
for (var i = this.state.attachments.length-1; i >= 0; i--)
|
|
||||||
if (this.state.attachments[i].selected)
|
|
||||||
this.state.attachments.splice(i, 1);
|
|
||||||
this.setState({ attachments: this.state.attachments });
|
|
||||||
ev.stopPropagation();
|
|
||||||
}
|
|
||||||
else if (ev.keyCode == 38 || ev.keyCode == 40 || ev.keyCode == 33 || ev.keyCode == 34) // up, down, pgup, pgdown
|
|
||||||
{
|
|
||||||
var sel = this.curSel, dir;
|
|
||||||
if (ev.keyCode < 35)
|
|
||||||
dir = (ev.keyCode == 34 ? 1 : -1) * Math.round(this.refs.scroll.offsetHeight / this.refs.a0.offsetHeight);
|
|
||||||
else
|
|
||||||
dir = (ev.keyCode == 40 ? 1 : -1);
|
|
||||||
if (sel !== null)
|
|
||||||
{
|
|
||||||
var nsel = sel+dir;
|
|
||||||
if (nsel < 0)
|
|
||||||
nsel = 0;
|
|
||||||
if (nsel >= this.state.attachments.length)
|
|
||||||
nsel = this.state.attachments.length-1;
|
|
||||||
if (sel != nsel)
|
|
||||||
{
|
|
||||||
if (ev.shiftKey)
|
|
||||||
this.selectTo(nsel);
|
|
||||||
else
|
|
||||||
this.selectOne(nsel);
|
|
||||||
var item = this.refs['a'+nsel];
|
|
||||||
if (item.offsetTop + item.offsetHeight > this.refs.scroll.scrollTop + this.refs.scroll.offsetHeight)
|
|
||||||
this.refs.scroll.scrollTop = item.offsetTop + item.offsetHeight - this.refs.scroll.offsetHeight;
|
|
||||||
else if (item.offsetTop < this.refs.scroll.scrollTop+this.refs.title.offsetHeight)
|
|
||||||
this.refs.scroll.scrollTop = item.offsetTop-this.refs.title.offsetHeight;
|
|
||||||
ev.stopPropagation();
|
|
||||||
ev.preventDefault(); // prevent scroll
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (ev.keyCode == 36) // home
|
|
||||||
{
|
|
||||||
if (ev.shiftKey)
|
|
||||||
this.selectTo(0);
|
|
||||||
else
|
|
||||||
this.selectOne(0);
|
|
||||||
}
|
|
||||||
else if (ev.keyCode == 35) // end
|
|
||||||
{
|
|
||||||
if (ev.shiftKey)
|
|
||||||
this.selectTo(this.state.attachments.length-1);
|
|
||||||
else
|
|
||||||
this.selectOne(this.state.attachments.length-1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
selectTo: function(ns)
|
|
||||||
{
|
|
||||||
if (this.lastSel === undefined)
|
|
||||||
return this.selectOne(ns);
|
|
||||||
if (ns < this.lastSel)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < this.state.attachments.length; i++)
|
|
||||||
this.state.attachments[i].selected = i >= ns && i <= this.lastSel;
|
|
||||||
}
|
|
||||||
else if (ns > this.lastSel)
|
|
||||||
{
|
|
||||||
for (var i = 0; i < this.state.attachments.length; i++)
|
|
||||||
this.state.attachments[i].selected = i >= this.lastSel && i <= ns;
|
|
||||||
}
|
|
||||||
this.setState({ attachments: this.state.attachments });
|
this.setState({ attachments: this.state.attachments });
|
||||||
this.curSel = ns;
|
|
||||||
},
|
},
|
||||||
selectOne: function(ns)
|
getTotalItems: function()
|
||||||
{
|
{
|
||||||
for (var i = 0; i < this.state.attachments.length; i++)
|
return this.state.attachments.length;
|
||||||
this.state.attachments[i].selected = (i == ns);
|
|
||||||
this.setState({ attachments: this.state.attachments });
|
|
||||||
this.lastSel = ns;
|
|
||||||
this.curSel = ns;
|
|
||||||
},
|
},
|
||||||
selectAttachment: function(ev)
|
getPageSize: function()
|
||||||
{
|
{
|
||||||
var t = ev.target;
|
return this.refs.a0 ? Math.round(this.refs.scroll.offsetHeight / this.refs.a0.offsetHeight) : 1;
|
||||||
while (t && !t.getAttribute('data-i'))
|
},
|
||||||
t = t.parentNode;
|
getItemOffset: function(index)
|
||||||
if (t)
|
{
|
||||||
{
|
var item = this.refs['a'+index];
|
||||||
var ns = parseInt(t.getAttribute('data-i'));
|
return [ item.offsetTop, item.offsetHeight ];
|
||||||
if (ev.shiftKey)
|
},
|
||||||
this.selectTo(ns);
|
getScrollPaddingTop: function()
|
||||||
else if (ev.ctrlKey)
|
{
|
||||||
{
|
return this.refs.title.offsetHeight;
|
||||||
this.state.attachments[ns].selected = true;
|
|
||||||
this.curSel = ns;
|
|
||||||
this.lastSel = this.lastSel === undefined ? ns : this.lastSel;
|
|
||||||
this.setState({ attachments: this.state.attachments });
|
|
||||||
}
|
|
||||||
else
|
|
||||||
this.selectOne(ns);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
render: function()
|
render: function()
|
||||||
{
|
{
|
||||||
|
@ -867,7 +912,7 @@ var AttachList = React.createClass({
|
||||||
<div className="no-attach" style={this.state.attachments.length ? { display: 'none' } : null}>
|
<div className="no-attach" style={this.state.attachments.length ? { display: 'none' } : null}>
|
||||||
<input type="file" multiple="multiple" onChange={this.addAttachments} />
|
<input type="file" multiple="multiple" onChange={this.addAttachments} />
|
||||||
</div>
|
</div>
|
||||||
<div ref="scroll" className="attach-list" tabIndex="1" onScroll={this.scrollAttachList} onKeyDown={this.attachKeyDown}
|
<div ref="scroll" className="attach-list" tabIndex="1" onScroll={this.scrollAttachList} onKeyDown={this.onListKeyDown}
|
||||||
style={this.state.attachments.length ? null : { display: 'none' }}>
|
style={this.state.attachments.length ? null : { display: 'none' }}>
|
||||||
<div ref="title" className="title" style={{ top: this.state.attachScroll }}>
|
<div ref="title" className="title" style={{ top: this.state.attachScroll }}>
|
||||||
<div className="name">Attachment <a className="button">
|
<div className="name">Attachment <a className="button">
|
||||||
|
@ -878,7 +923,7 @@ var AttachList = React.createClass({
|
||||||
</div>
|
</div>
|
||||||
{this.state.attachments.map((a, i) =>
|
{this.state.attachments.map((a, i) =>
|
||||||
<div ref={'a'+i} title={a.name+' ('+formatBytes(a.size)+')'} key={'a'+i} data-i={i}
|
<div ref={'a'+i} title={a.name+' ('+formatBytes(a.size)+')'} key={'a'+i} data-i={i}
|
||||||
className={'attachment'+(a.selected ? ' selected' : '')} onMouseDown={this.selectAttachment}>
|
className={'attachment'+(this.state.selected[i] ? ' selected' : '')} onMouseDown={this.onListItemClick}>
|
||||||
<div className="name">{a.name}</div>
|
<div className="name">{a.name}</div>
|
||||||
<div className="size">{formatBytes(a.size)}</div>
|
<div className="size">{formatBytes(a.size)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue