Skip to content

Commit

Permalink
fix: crash when fast-switching channels (timer)
Browse files Browse the repository at this point in the history
perf(ChannelsList): menu generated once, costly operations under if
style: remove i18n todo comment
chore(vscode): remove inspect-brk
  • Loading branch information
RuzikNF committed Oct 20, 2020
1 parent 9ad6c4c commit 6efa976
Show file tree
Hide file tree
Showing 9 changed files with 109 additions and 75 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"request": "launch",
"name": "Debug DQt",
"runtimeExecutable": "${workspaceFolder}/node_modules/.bin/qode",
"runtimeArgs": ["--inspect", "--inspect-brk", "--trace-warnings"],
"runtimeArgs": ["--inspect", "--trace-warnings"],
"skipFiles": [
"<node_internals>/**"
],
Expand Down
30 changes: 3 additions & 27 deletions src/components/GuildPanel/ChannelButton.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import {
ContextMenuPolicy,
QAction,
QApplication,
QClipboardMode,
QLabel,
QMenu,
QPixmap,
QPoint,
} from '@nodegui/nodegui';
import { GuildChannel, TextChannel, VoiceChannel } from 'discord.js';
import { __ } from 'i18n';
import { join } from 'path';
import { app } from '../..';
import { Events } from '../../utilities/Events';
Expand All @@ -26,10 +20,6 @@ export class ChannelButton extends DChannelButton {

private chlabel = new QLabel(this);

private clipboard = QApplication.clipboard();

private channelMenu = new QMenu(this);

private unreadIcon = new QLabel(this);

channel?: GuildChannel;
Expand All @@ -39,13 +29,12 @@ export class ChannelButton extends DChannelButton {
this.initComponent();
this.setContextMenuPolicy(ContextMenuPolicy.CustomContextMenu);
this.addEventListener('clicked', this.handleClick.bind(this));
this.setInlineStyle('margin-left: 8px');
this.layout.setContentsMargins(12, 4, 12, 4);
}

private handleClick() {
const { channel } = this;
if (!channel) return;
if (!channel || this.activated()) return;
switch (channel.type) {
case 'news':
case 'text':
Expand Down Expand Up @@ -85,24 +74,11 @@ export class ChannelButton extends DChannelButton {
}

loadChannel(channel: GuildChannel) {
const { channelMenu, chicon } = this;
const { chicon } = this;
this.channel = channel;
this.chlabel.setText(channel.name);
const pixmap = ChannelButton.Icons.get(channel.type);
if (pixmap) chicon.setPixmap(pixmap);
if (channel instanceof TextChannel) {
this.setUnread(!channel.acknowledged);
}
// channelMenu.setInlineStyle('background: #18191c');
const copyId = new QAction();
copyId.setText(__('COPY_ID'));
copyId.addEventListener('triggered', () => {
this.clipboard.setText(channel.id, QClipboardMode.Clipboard);
});
channelMenu.addAction(copyId);
this.addEventListener('customContextMenuRequested', (pos) => {
channelMenu.repolish();
channelMenu.popup(this.mapToGlobal(new QPoint(pos.x, pos.y)));
});
if (channel instanceof TextChannel && !channel.acknowledged) this.setUnread(true);
}
}
14 changes: 4 additions & 10 deletions src/components/GuildPanel/ChannelMembers.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
import {
Direction, QBoxLayout, QLabel, QListWidget, QListWidgetItem, QPixmap, QPoint, WidgetEventTypes,
Direction, QBoxLayout, QLabel, QListWidget, QListWidgetItem, QPixmap, QPoint,
} from '@nodegui/nodegui';
import {
Constants, DQConstants, GuildMember, Snowflake, VoiceChannel, VoiceState,
GuildMember, Snowflake, VoiceChannel, VoiceState,
} from 'discord.js';
import { app } from '../..';
import { Events as AppEvents } from '../../utilities/Events';
import { pictureWorker } from '../../utilities/PictureWorker';
import { DChannelButton } from '../DChannelButton/DChannelButton';

const { Events } = Constants as unknown as DQConstants;

export class ChannelMembers extends QListWidget {
layout = new QBoxLayout(Direction.TopToBottom);

Expand All @@ -28,14 +26,10 @@ export class ChannelMembers extends QListWidget {
this.setUniformItemSizes(true);
this.setFrameShape(0);
this.setHorizontalScrollBarPolicy(1);
this.setInlineStyle('margin-left: 32px;margin-bottom:8px;background-color: transparent;');
this.addEventListener(WidgetEventTypes.DeferredDelete, () => {
app.client.off(Events.VOICE_STATE_UPDATE, this.handleVoiceStateUpdate.bind(this));
});
app.client.on(Events.VOICE_STATE_UPDATE, this.handleVoiceStateUpdate.bind(this));
this.setObjectName('ChannelMembers');
}

private handleVoiceStateUpdate(o: VoiceState, n: VoiceState) {
handleVoiceStateUpdate(o: VoiceState, n: VoiceState) {
if (this.native.destroyed || !o.member) return;
if (o.channel === this.channel && n.channel !== this.channel) {
try {
Expand Down
53 changes: 43 additions & 10 deletions src/components/GuildPanel/ChannelsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
QLabel,
QListWidget,
QListWidgetItem,
QPoint,
QSize,
ScrollBarPolicy,
Shape,
Expand All @@ -18,14 +19,15 @@ import {
DQConstants,
Guild,
GuildChannel,
Permissions, VoiceChannel,
Permissions, VoiceChannel, VoiceState,
} from 'discord.js';
import { app } from '../..';
import { Events as AppEvents } from '../../utilities/Events';
import { createLogger } from '../../utilities/Console';
import { Events as AppEvents } from '../../utilities/Events';
import { ViewOptions } from '../../views/ViewOptions';
import { ChannelButton } from './ChannelButton';
import { ChannelMembers } from './ChannelMembers';
import { ChannelsListMenu } from './ChannelsListMenu';

const { debug } = createLogger('ChannelsList');

Expand All @@ -36,6 +38,14 @@ export class ChannelsList extends QListWidget {

private buttons: Set<ChannelButton> = new Set();

private vcMembers = new Map<string, ChannelMembers>();

private menu = new ChannelsListMenu(this);

private ratelimit = false;

private rateTimer?: any;

constructor() {
super();
this.setFrameShape(Shape.NoFrame);
Expand All @@ -59,6 +69,14 @@ export class ChannelsList extends QListWidget {
.find((btn) => btn.channel?.id === message.channel.id);
if (button) button.setUnread(true);
});
client.on(Events.VOICE_STATE_UPDATE, this.handleVoiceStateUpdate.bind(this));
}

private handleVoiceStateUpdate(o: VoiceState, n: VoiceState) {
for (const state of [o, n]) {
const component = this.vcMembers.get(state.channelID || '');
if (component) component.handleVoiceStateUpdate(o, n);
}
}

private async handleSwitchView(view: string, options?: ViewOptions) {
Expand All @@ -68,11 +86,10 @@ export class ChannelsList extends QListWidget {
else if (options.channel) newGuild = options.channel.guild;
else return;
if (newGuild.id !== this.guild?.id) {
this.guild = newGuild;
await this.loadChannels();
this.loadChannels(newGuild);
}
if (options.channel) {
const chan = ([...this.nodeChildren.values()] as ChannelButton[])
const chan = ([...this.buttons.values()] as ChannelButton[])
.find((a) => a.channel === options.channel);
this.active?.setActivated(false);
chan?.setActivated(true);
Expand All @@ -82,14 +99,20 @@ export class ChannelsList extends QListWidget {

private minSize = new QSize(150, 36);

async loadChannels() {
const { guild, buttons } = this;
private loadChannels(guild: Guild) {
this.guild = guild;
if (this.ratelimit) return;
this.ratelimit = true;
if (this.rateTimer) clearTimeout(this.rateTimer);
const { buttons } = this;
if (!guild) return;

debug(`Loading channels of guild ${guild.name} (${guild.id})...`);

this.clear();
this.nodeChildren.clear();
this.items.clear();
this.buttons.clear();
this.vcMembers.clear();

const [categories, channels] = guild.channels.cache
.filter((c) => c.can(Permissions.FLAGS.VIEW_CHANNEL))
.partition((a) => a.type === 'category') as [
Expand Down Expand Up @@ -134,22 +157,32 @@ export class ChannelsList extends QListWidget {
item.setFlags(~ItemFlag.ItemIsEnabled);
btn.loadChannel(channel);
btn.setFixedSize(232, 32);
btn.setMuted(!!channel.muted);
if (channel.muted) btn.setMuted(true);
item.setSizeHint(this.minSize);
item.setText(channel.id);
this.insertItem(row, item);
this.setItemWidget(item, btn);
buttons.add(btn);
btn.addEventListener(WidgetEventTypes.DeferredDelete, () => buttons.delete(btn));
btn.addEventListener('customContextMenuRequested', (pos) => {
this.menu.setChannel(channel);
this.menu.popup(btn.mapToGlobal(new QPoint(pos.x, pos.y)));
});
if (channel.type === 'voice') {
const members = new ChannelMembers(this);
members.loadChannel(channel as VoiceChannel);
const memitem = new QListWidgetItem();
memitem.setText(channel.id);
this.insertItem(row + 1, memitem);
this.setItemWidget(memitem, members);
this.vcMembers.set(channel.id, members);
members.setItem(memitem);
}
}

this.rateTimer = setTimeout(() => {
this.ratelimit = false;
if (guild !== this.guild && this.guild) this.loadChannels(this.guild);
}, 500);
}
}
31 changes: 31 additions & 0 deletions src/components/GuildPanel/ChannelsListMenu.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {
QAction, QClipboardMode, QMenu, WidgetAttribute,
} from '@nodegui/nodegui';
import { GuildChannel } from 'discord.js';
import { __ } from 'i18n';
import { app } from '../..';

export class ChannelsListMenu extends QMenu {
channel?: GuildChannel;

constructor(parent?: any) {
super(parent);

this.setInlineStyle('border-radius: 4px');
this.setAttribute(WidgetAttribute.WA_TranslucentBackground, true);
this.initMenu();
}

private initMenu() {
const copyId = new QAction();
copyId.setText(__('COPY_ID'));
copyId.addEventListener('triggered', () => {
app.clipboard.setText(this.channel?.id || '', QClipboardMode.Clipboard);
});
this.addAction(copyId);
}

setChannel(channel: GuildChannel) {
this.channel = channel;
}
}
16 changes: 8 additions & 8 deletions src/components/GuildPanel/GuildPanel.scss
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,14 @@
border: none;
outline: none;
}
*::item {
color: transparent;
border: none;
outline: none;
}
}
#DChannelButton {
margin-left: 8px;
}
#UnreadIndicator {
border-radius: 4px;
background: $header-primary;
}
QMenu {
background: $background-floating;
}
#DTitleBar:hover {
background-color: lighten($background-secondary, 2%);
}
Expand All @@ -52,4 +47,9 @@
#ActionsMenu {
border-bottom: 1px solid $background-floating;
}
#ChannelMembers {
margin-left: 32px;
margin-bottom:8px;
background-color: transparent;
}
}
16 changes: 9 additions & 7 deletions src/components/MembersList/MembersList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,16 @@ export class MembersList extends QListWidget {

private rateTimer?: any;

private async loadList(channel: GuildChannel) {
if (this.ratelimit || this.channel === channel) return;
private loadList(channel: GuildChannel) {
this.channel = channel as TextChannel | NewsChannel;
if (this.ratelimit || !['text', 'news'].includes(channel.type)) return;
this.ratelimit = true;
if (this.rateTimer) clearTimeout(this.rateTimer);

debug(`Loading members list for #${channel.name} (${channel.id})...`);
if (channel.type !== 'text' && channel.type !== 'news') return;
this.channel = channel as TextChannel | NewsChannel;

this.clear();
this.nodeChildren.clear();
this.items.clear();

for (const member of channel.members.values()) {
const btn = UserButton.createInstance(this, member);
const item = new QListWidgetItem();
Expand All @@ -66,6 +65,9 @@ export class MembersList extends QListWidget {
btn.loadAvatar();
}
debug('Finished loading members list.');
this.rateTimer = setTimeout(() => { this.ratelimit = false; }, 500);
this.rateTimer = setTimeout(() => {
this.ratelimit = false;
if (channel !== this.channel && this.channel) this.loadList(this.channel);
}, 500);
}
}
20 changes: 9 additions & 11 deletions src/components/MessagesPanel/MessagesPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,27 +135,22 @@ export class MessagesPanel extends QScrollArea {
private ackTimer?: any;

private async handleChannelOpen(channel: DMChannel | GuildChannel) {
this.channel = channel as TextChannel | NewsChannel | DMChannel;
if (this.ratelimit
|| this.isLoading
|| this.channel === channel
|| !['news', 'text', 'dm'].includes(channel.type)
) return;

this.isLoading = true;
this.ratelimit = true;
this.hide();
if (this.rateTimer) clearTimeout(this.rateTimer);
if (this.ackTimer) clearTimeout(this.ackTimer);
this.rateTimer = setTimeout(() => { this.ratelimit = false; }, 1000);
debug(`Opening channel ${channel.id}...`);
this.initRoot();

const textChannel = channel as TextChannel | NewsChannel | DMChannel;
this.channel = textChannel;

if (textChannel.messages.cache.size < 30) await textChannel.messages.fetch({ limit: 30 });
debug(`Total of ${textChannel.messages.cache.size} messages are available.`);
const messages = textChannel.messages.cache.array()
if (this.channel.messages.cache.size < 30) await this.channel.messages.fetch({ limit: 30 });
debug(`Total of ${this.channel.messages.cache.size} messages are available.`);
const messages = this.channel.messages.cache.array()
.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime())
.reverse();
messages.length = Math.min(messages.length, 30);
Expand All @@ -168,7 +163,6 @@ export class MessagesPanel extends QScrollArea {
debug(`Waiting for ${promises.length} widgets to be loaded...`);
await Promise.all(promises);
debug('Widgets finished loading.');
this.show();
setTimeout(() => {
this.isLoading = false;
this.handleWheel(true);
Expand All @@ -177,6 +171,10 @@ export class MessagesPanel extends QScrollArea {
this.ackTimer = setTimeout(() => {
if (this.channel) this.channel.acknowledge();
this.ackTimer = undefined;
}, 1000);
}, 2000);
this.rateTimer = setTimeout(() => {
this.ratelimit = false;
if (channel !== this.channel && this.channel) this.handleChannelOpen(this.channel);
}, 1500);
}
}
Loading

0 comments on commit 6efa976

Please sign in to comment.