mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-09-22 08:16:09 +08:00
fix(emoji): Re-use img tag to avoid running out of file descriptors
Summary: On my machine the new emoji picker was causing "too many open file descriptor" errors. I think that this was because it was creating 1300 image tags in 50msec. I refactored this code so that it uses a single image tag and only loads one image at a time. This could make it slower on some people's machines, but eliminates the possibility of it breaking the app! Test Plan: Run tests Reviewers: jackie Differential Revision: https://phab.nylas.com/D2878
This commit is contained in:
parent
5e49962cfb
commit
370edc40e9
|
@ -56,9 +56,7 @@ export default categorizedEmojiList = {
|
||||||
'cold_sweat',
|
'cold_sweat',
|
||||||
'hushed',
|
'hushed',
|
||||||
'frowning',
|
'frowning',
|
||||||
'anguished'
|
'anguished',
|
||||||
],
|
|
||||||
'More People': [
|
|
||||||
'cry',
|
'cry',
|
||||||
'disappointed_relieved',
|
'disappointed_relieved',
|
||||||
'sleepy',
|
'sleepy',
|
||||||
|
|
|
@ -27,10 +27,13 @@ class EmojiButtonPopover extends React.Component {
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
this._mounted = true;
|
this._mounted = true;
|
||||||
|
this._emojiPreloadImage = new Image();
|
||||||
this.renderCanvas();
|
this.renderCanvas();
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
this._emojiPreloadImage.onload = null;
|
||||||
|
this._emojiPreloadImage = null;
|
||||||
this._mounted = false;
|
this._mounted = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,16 +55,12 @@ class EmojiButtonPopover extends React.Component {
|
||||||
if (this.state.categoryPositions.hasOwnProperty(category)) {
|
if (this.state.categoryPositions.hasOwnProperty(category)) {
|
||||||
if (emojiContainer.scrollTop >= this.state.categoryPositions[category].top &&
|
if (emojiContainer.scrollTop >= this.state.categoryPositions[category].top &&
|
||||||
emojiContainer.scrollTop <= this.state.categoryPositions[category].bottom) {
|
emojiContainer.scrollTop <= this.state.categoryPositions[category].bottom) {
|
||||||
if (category === 'More People') {
|
|
||||||
this.setState({activeTab: 'People'});
|
|
||||||
} else {
|
|
||||||
this.setState({activeTab: category});
|
this.setState({activeTab: category});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
onHover = (event) => {
|
onHover = (event) => {
|
||||||
const emojiName = this.calcEmojiByPosition(this.calcPosition(event));
|
const emojiName = this.calcEmojiByPosition(this.calcPosition(event));
|
||||||
|
@ -108,7 +107,6 @@ class EmojiButtonPopover extends React.Component {
|
||||||
const categoryPositions = {};
|
const categoryPositions = {};
|
||||||
let categoryNames = [
|
let categoryNames = [
|
||||||
'People',
|
'People',
|
||||||
'More People',
|
|
||||||
'Nature',
|
'Nature',
|
||||||
'Food and Drink',
|
'Food and Drink',
|
||||||
'Activity',
|
'Activity',
|
||||||
|
@ -212,7 +210,6 @@ class EmojiButtonPopover extends React.Component {
|
||||||
renderTabs() {
|
renderTabs() {
|
||||||
const tabs = [];
|
const tabs = [];
|
||||||
this.state.categoryNames.forEach((category) => {
|
this.state.categoryNames.forEach((category) => {
|
||||||
if (category !== 'More People') {
|
|
||||||
let className = `emoji-tab ${(category.replace(/ /g, '-')).toLowerCase()}`
|
let className = `emoji-tab ${(category.replace(/ /g, '-')).toLowerCase()}`
|
||||||
if (category === this.state.activeTab) {
|
if (category === this.state.activeTab) {
|
||||||
className += " active";
|
className += " active";
|
||||||
|
@ -227,7 +224,6 @@ class EmojiButtonPopover extends React.Component {
|
||||||
onMouseDown={() => this.scrollToCategory(category)} />
|
onMouseDown={() => this.scrollToCategory(category)} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return tabs;
|
return tabs;
|
||||||
}
|
}
|
||||||
|
@ -237,51 +233,69 @@ class EmojiButtonPopover extends React.Component {
|
||||||
const keys = Object.keys(this.state.categoryPositions);
|
const keys = Object.keys(this.state.categoryPositions);
|
||||||
canvas.height = this.state.categoryPositions[keys[keys.length - 1]].bottom * 2;
|
canvas.height = this.state.categoryPositions[keys[keys.length - 1]].bottom * 2;
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext("2d");
|
||||||
|
ctx.font = "24px Nylas-Pro";
|
||||||
|
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
const position = {
|
const position = {
|
||||||
x: 15,
|
x: 15,
|
||||||
y: 45,
|
y: 45,
|
||||||
}
|
}
|
||||||
Object.keys(this.state.categorizedEmoji).forEach((category, i) => {
|
|
||||||
if (i > 0) {
|
let idx = 0;
|
||||||
setTimeout(() => this.renderCategory(category, i, ctx, position), i * 50);
|
const categoryNames = Object.keys(this.state.categorizedEmoji);
|
||||||
} else {
|
const renderNextCategory = () => {
|
||||||
this.renderCategory(category, i, ctx, position);
|
if (!categoryNames[idx]) return;
|
||||||
|
if (!this._mounted) return;
|
||||||
|
this.renderCategory(categoryNames[idx], idx, ctx, position, renderNextCategory);
|
||||||
|
idx += 1;
|
||||||
}
|
}
|
||||||
});
|
renderNextCategory();
|
||||||
}
|
}
|
||||||
|
|
||||||
renderCategory(category, i, ctx, position) {
|
renderCategory(category, i, ctx, position, callback) {
|
||||||
if (!this._mounted) return;
|
|
||||||
if (category !== "More People") {
|
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
position.x = 18;
|
position.x = 18;
|
||||||
position.y += 48;
|
position.y += 48;
|
||||||
}
|
}
|
||||||
ctx.font = "24px Nylas-Pro";
|
|
||||||
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
|
|
||||||
ctx.fillText(category, position.x, position.y);
|
ctx.fillText(category, position.x, position.y);
|
||||||
}
|
|
||||||
position.x = 18;
|
position.x = 18;
|
||||||
position.y += 48;
|
position.y += 48;
|
||||||
ctx.font = "32px Arial";
|
|
||||||
ctx.fillStyle = 'black';
|
const emoji = this.state.categorizedEmoji[category];
|
||||||
if (this.state.categorizedEmoji[category].length === 0) return;
|
if (!emoji || emoji.length === 0) return;
|
||||||
this.state.categorizedEmoji[category].forEach((emojiName, j) => {
|
|
||||||
const img = new Image();
|
const remaining = emoji.map((emojiName, j) => {
|
||||||
img.src = EmojiStore.getImagePath(emojiName);
|
|
||||||
const x = position.x;
|
const x = position.x;
|
||||||
const y = position.y;
|
const y = position.y;
|
||||||
img.onload = () => {
|
const src = EmojiStore.getImagePath(emojiName);
|
||||||
ctx.drawImage(img, x, y - 30, 32, 32);
|
|
||||||
}
|
|
||||||
if (position.x > 325 && j < this.state.categorizedEmoji[category].length - 1) {
|
if (position.x > 325 && j < this.state.categorizedEmoji[category].length - 1) {
|
||||||
position.x = 18;
|
position.x = 18;
|
||||||
position.y += 48;
|
position.y += 48;
|
||||||
} else {
|
} else {
|
||||||
position.x += 50;
|
position.x += 50;
|
||||||
}
|
}
|
||||||
})
|
|
||||||
|
return {src, x, y};
|
||||||
|
});
|
||||||
|
|
||||||
|
const drawEmojiAt = ({src, x, y} = {}) => {
|
||||||
|
if (!src) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._emojiPreloadImage.onload = () => {
|
||||||
|
this._emojiPreloadImage.onload = null;
|
||||||
|
ctx.drawImage(this._emojiPreloadImage, x, y - 30, 32, 32);
|
||||||
|
if (remaining.length === 0) {
|
||||||
|
callback();
|
||||||
|
} else {
|
||||||
|
drawEmojiAt(remaining.shift());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._emojiPreloadImage.src = src;
|
||||||
|
}
|
||||||
|
|
||||||
|
drawEmojiAt(remaining.shift());
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
|
Loading…
Reference in a new issue