Extract list selection into a mixin

master
Vitaliy Filippov 2016-06-23 17:27:12 +03:00
parent f4374ec5f6
commit 3cd3685448
1 changed files with 143 additions and 98 deletions

241
mail.js
View File

@ -736,7 +736,131 @@ function formatDate(dt)
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({
mixins: [ ListWithSelection ],
getInitialState: function()
{
return {
@ -758,108 +882,29 @@ var AttachList = React.createClass({
{
this.setState({ attachScroll: ev.target.scrollTop });
},
attachKeyDown: function(ev)
deleteSelected: function()
{
if (!this.state.attachments.length)
return;
if (ev.keyCode == 46) // delete
{
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;
}
for (var i = this.state.attachments.length-1; i >= 0; i--)
if (this.state.selected[i])
this.state.attachments.splice(i, 1);
this.setState({ attachments: this.state.attachments });
this.curSel = ns;
},
selectOne: function(ns)
getTotalItems: function()
{
for (var i = 0; i < this.state.attachments.length; i++)
this.state.attachments[i].selected = (i == ns);
this.setState({ attachments: this.state.attachments });
this.lastSel = ns;
this.curSel = ns;
return this.state.attachments.length;
},
selectAttachment: function(ev)
getPageSize: function()
{
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.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);
}
return this.refs.a0 ? Math.round(this.refs.scroll.offsetHeight / this.refs.a0.offsetHeight) : 1;
},
getItemOffset: function(index)
{
var item = this.refs['a'+index];
return [ item.offsetTop, item.offsetHeight ];
},
getScrollPaddingTop: function()
{
return this.refs.title.offsetHeight;
},
render: function()
{
@ -867,7 +912,7 @@ var AttachList = React.createClass({
<div className="no-attach" style={this.state.attachments.length ? { display: 'none' } : null}>
<input type="file" multiple="multiple" onChange={this.addAttachments} />
</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' }}>
<div ref="title" className="title" style={{ top: this.state.attachScroll }}>
<div className="name">Attachment <a className="button">
@ -878,7 +923,7 @@ var AttachList = React.createClass({
</div>
{this.state.attachments.map((a, 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="size">{formatBytes(a.size)}</div>
</div>