DEV: implements model/afterModel/deactivate in drawer router (#29879)

These changes allow to load model when loading a screen of the drawer,
the underlying idea is to avoid having to rely on the global
`activeChannel`, this essentially makes each screen responsible for it's
data.

This change is also fixing a bug where clicking on a link routing to the
same screen of the drawer you are already on, would display a blank
drawer.
This commit is contained in:
Joffrey JAFFEUX
2025-01-27 11:03:15 +01:00
committed by GitHub
parent 2f3355695e
commit 83a47c36a5
10 changed files with 204 additions and 209 deletions

View File

@@ -8,8 +8,8 @@
{{#if this.chatStateManager.isDrawerActive}}
<div
data-chat-channel-id={{this.chat.activeChannel.id}}
data-chat-thread-id={{this.chat.activeChannel.activeThread.id}}
data-chat-channel-id={{this.chatDrawerRouter.model.channel.id}}
data-chat-thread-id={{this.chatDrawerRouter.model.channel.activeThread.id}}
class={{concat-class
"chat-drawer"
(if this.chatStateManager.isDrawerExpanded "is-expanded" "is-collapsed")
@@ -29,6 +29,7 @@
<this.chatDrawerRouter.component
@params={{this.chatDrawerRouter.params}}
@model={{this.chatDrawerRouter.model}}
@openURL={{this.openURL}}
@openInFullPage={{this.openInFullPage}}
@toggleExpand={{this.toggleExpand}}

View File

@@ -8,7 +8,6 @@ import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
export default class ChatDrawerRoutesBrowse extends Component {
@service chat;
@service chatStateManager;
@service chatChannelsManager;
@service chatHistory;
<template>

View File

@@ -1,6 +1,4 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { service } from "@ember/service";
import { i18n } from "discourse-i18n";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
@@ -10,42 +8,25 @@ import ChannelInfoNav from "discourse/plugins/chat/discourse/components/chat/rou
export default class ChatDrawerRoutesMembers extends Component {
@service chat;
@service chatStateManager;
@service chatChannelsManager;
get backButton() {
return {
route: "chat.channel",
models: this.chat.activeChannel?.routeModels,
models: this.args.model?.channel?.routeModels,
title: i18n("chat.return_to_channel"),
};
}
@action
async fetchChannel() {
if (!this.args.params?.channelId) {
return;
}
const channel = await this.chatChannelsManager.find(
this.args.params.channelId
);
this.chat.activeChannel = channel;
}
<template>
<div
class="c-drawer-routes --channel-info-members"
{{didInsert this.fetchChannel}}
>
{{#if this.chat.activeChannel}}
<div class="c-drawer-routes --channel-info-members">
{{#if @model.channel}}
<Navbar @onClick={{this.chat.toggleDrawer}} as |navbar|>
<navbar.BackButton
@title={{this.backButton.title}}
@route={{this.backButton.route}}
@routeModels={{this.backButton.models}}
/>
<navbar.ChannelTitle @channel={{this.chat.activeChannel}} />
<navbar.ChannelTitle @channel={{@model.channel}} />
<navbar.Actions as |a|>
<a.ToggleDrawerButton />
<a.FullPageButton />
@@ -55,11 +36,8 @@ export default class ChatDrawerRoutesMembers extends Component {
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content">
<ChannelInfoNav
@channel={{this.chat.activeChannel}}
@tab="members"
/>
<ChannelMembers @channel={{this.chat.activeChannel}} />
<ChannelInfoNav @channel={{@model.channel}} @tab="members" />
<ChannelMembers @channel={{@model.channel}} />
</div>
{{/if}}
{{/if}}

View File

@@ -1,6 +1,4 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { service } from "@ember/service";
import { i18n } from "discourse-i18n";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
@@ -10,42 +8,25 @@ import ChannelSettings from "discourse/plugins/chat/discourse/components/chat/ro
export default class ChatDrawerRoutesSettings extends Component {
@service chat;
@service chatStateManager;
@service chatChannelsManager;
get backButton() {
return {
route: "chat.channel",
models: this.chat.activeChannel?.routeModels,
models: this.args.model?.channel?.routeModels,
title: i18n("chat.return_to_channel"),
};
}
@action
async fetchChannel() {
if (!this.args.params?.channelId) {
return;
}
const channel = await this.chatChannelsManager.find(
this.args.params.channelId
);
this.chat.activeChannel = channel;
}
<template>
<div
class="c-drawer-routes --channel-info-settings"
{{didInsert this.fetchChannel}}
>
{{#if this.chat.activeChannel}}
<div class="c-drawer-routes --channel-info-settings">
{{#if @model.channel}}
<Navbar @onClick={{this.chat.toggleDrawer}} as |navbar|>
<navbar.BackButton
@title={{this.backButton.title}}
@route={{this.backButton.route}}
@routeModels={{this.backButton.models}}
/>
<navbar.ChannelTitle @channel={{this.chat.activeChannel}} />
<navbar.ChannelTitle @channel={{@model.channel}} />
<navbar.Actions as |a|>
<a.ToggleDrawerButton />
<a.FullPageButton />
@@ -55,11 +36,8 @@ export default class ChatDrawerRoutesSettings extends Component {
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content">
<ChannelInfoNav
@channel={{this.chat.activeChannel}}
@tab="settings"
/>
<ChannelSettings @channel={{this.chat.activeChannel}} />
<ChannelInfoNav @channel={{@model.channel}} @tab="settings" />
<ChannelSettings @channel={{@model.channel}} />
</div>
{{/if}}
{{/if}}

View File

@@ -1,10 +1,6 @@
import Component from "@glimmer/component";
import { array } from "@ember/helper";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
import { service } from "@ember/service";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { i18n } from "discourse-i18n";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
import ChatThread from "discourse/plugins/chat/discourse/components/chat-thread";
@@ -12,12 +8,11 @@ import ChatThread from "discourse/plugins/chat/discourse/components/chat-thread"
export default class ChatDrawerRoutesChannelThread extends Component {
@service chat;
@service chatStateManager;
@service chatChannelsManager;
@service chatHistory;
get backButton() {
const link = {
models: this.chat.activeChannel?.routeModels,
models: this.args.model?.channel?.routeModels,
};
if (this.chatHistory.previousRoute?.name === "chat.channel.threads") {
@@ -37,41 +32,13 @@ export default class ChatDrawerRoutesChannelThread extends Component {
get threadTitle() {
return (
this.chat.activeChannel?.activeThread?.title ?? i18n("chat.thread.label")
this.args.model?.channel?.activeThread?.title ?? i18n("chat.thread.label")
);
}
@action
async fetchChannelAndThread() {
if (!this.args.params?.channelId || !this.args.params?.threadId) {
return;
}
try {
const channel = await this.chatChannelsManager.find(
this.args.params.channelId
);
this.chat.activeChannel = channel;
channel.threadsManager
.find(channel.id, this.args.params.threadId)
.then((thread) => {
this.chat.activeChannel.activeThread = thread;
});
} catch (error) {
popupAjaxError(error);
}
}
<template>
<div
class="c-drawer-routes --channel-thread"
{{didInsert this.fetchChannelAndThread}}
{{didUpdate this.fetchChannelAndThread @params.channelId}}
{{didUpdate this.fetchChannelAndThread @params.threadId}}
>
{{#if this.chat.activeChannel}}
<div class="c-drawer-routes --channel-thread">
{{#if @model.channel}}
<Navbar
@onClick={{this.chat.toggleDrawer}}
@showFullTitle={{this.showfullTitle}}
@@ -92,13 +59,11 @@ export default class ChatDrawerRoutesChannelThread extends Component {
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content">
{{#each (array this.chat.activeChannel.activeThread) as |thread|}}
{{#if thread}}
<ChatThread
@thread={{thread}}
@targetMessageId={{@params.messageId}}
/>
{{/if}}
{{#each (array @model.thread) as |thread|}}
<ChatThread
@thread={{thread}}
@targetMessageId={{@params.messageId}}
/>
{{/each}}
</div>
{{/if}}

View File

@@ -1,17 +1,13 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { service } from "@ember/service";
import { htmlSafe } from "@ember/template";
import replaceEmoji from "discourse/helpers/replace-emoji";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { i18n } from "discourse-i18n";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
import ChatThreadList from "discourse/plugins/chat/discourse/components/chat-thread-list";
export default class ChatDrawerRoutesChannelThreads extends Component {
@service chat;
@service chatChannelsManager;
@service chatStateManager;
backLinkTitle = i18n("chat.return_to_list");
@@ -20,34 +16,18 @@ export default class ChatDrawerRoutesChannelThreads extends Component {
return htmlSafe(
i18n("chat.threads.list") +
" - " +
replaceEmoji(this.chat.activeChannel.title)
replaceEmoji(this.args.model.channel.title)
);
}
@action
async fetchChannel() {
if (!this.args.params?.channelId) {
return;
}
try {
const channel = await this.chatChannelsManager.find(
this.args.params.channelId
);
this.chat.activeChannel = channel;
} catch (error) {
popupAjaxError(error);
}
}
<template>
<div class="c-drawer-routes --channel-threads">
{{#if this.chat.activeChannel}}
{{#if @model}}
<Navbar @onClick={{this.chat.toggleDrawer}} as |navbar|>
<navbar.BackButton
@title={{this.backLinkTitle}}
@route="chat.channel"
@routeModels={{this.chat.activeChannel.routeModels}}
@routeModels={{@model.channel.routeModels}}
/>
<navbar.Title @title={{this.title}} @icon="discourse-threads" />
<navbar.Actions as |a|>
@@ -56,17 +36,15 @@ export default class ChatDrawerRoutesChannelThreads extends Component {
<a.CloseDrawerButton />
</navbar.Actions>
</Navbar>
{{/if}}
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content" {{didInsert this.fetchChannel}}>
{{#if this.chat.activeChannel}}
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content">
<ChatThreadList
@channel={{this.chat.activeChannel}}
@channel={{@model.channel}}
@includeHeader={{false}}
/>
{{/if}}
</div>
</div>
{{/if}}
{{/if}}
</div>
</template>

View File

@@ -1,8 +1,5 @@
import Component from "@glimmer/component";
import { array } from "@ember/helper";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import didUpdate from "@ember/render-modifiers/modifiers/did-update";
import { service } from "@ember/service";
import Navbar from "discourse/plugins/chat/discourse/components/chat/navbar";
import ChatChannel from "discourse/plugins/chat/discourse/components/chat-channel";
@@ -16,56 +13,37 @@ export default class ChatDrawerRoutesChannel extends Component {
get backBtnRoute() {
if (this.chatHistory.previousRoute?.name === "chat.browse") {
return "chat.browse";
} else if (this.chat.activeChannel?.isDirectMessageChannel) {
} else if (this.args.model?.channel?.isDirectMessageChannel) {
return "chat.direct-messages";
} else {
return "chat.channels";
}
}
@action
fetchChannel() {
if (!this.args.params?.channelId) {
return;
}
return this.chatChannelsManager
.find(this.args.params.channelId)
.then((channel) => {
this.chat.activeChannel = channel;
});
}
<template>
<div class="c-drawer-routes --channel">
<Navbar @onClick={{this.chat.toggleDrawer}} as |navbar|>
<navbar.BackButton @route={{this.backBtnRoute}} />
<navbar.ChannelTitle @channel={{this.chat.activeChannel}} />
<navbar.Actions as |a|>
<a.ThreadsListButton @channel={{this.chat.activeChannel}} />
<a.ToggleDrawerButton />
<a.FullPageButton />
<a.CloseDrawerButton />
</navbar.Actions>
</Navbar>
{{#if @model.channel}}
<Navbar @onClick={{this.chat.toggleDrawer}} as |navbar|>
<navbar.BackButton @route={{this.backBtnRoute}} />
<navbar.ChannelTitle @channel={{@model.channel}} />
<navbar.Actions as |a|>
<a.ThreadsListButton @channel={{@model.channel}} />
<a.ToggleDrawerButton />
<a.FullPageButton />
<a.CloseDrawerButton />
</navbar.Actions>
</Navbar>
{{#if this.chatStateManager.isDrawerExpanded}}
<div
class="chat-drawer-content"
{{didInsert this.fetchChannel}}
{{didUpdate this.fetchChannel @params.channelId}}
>
{{#if this.chat.activeChannel}}
{{#each (array this.chat.activeChannel) as |channel|}}
{{#if channel}}
<ChatChannel
@targetMessageId={{readonly @params.messageId}}
@channel={{channel}}
/>
{{/if}}
{{#if this.chatStateManager.isDrawerExpanded}}
<div class="chat-drawer-content">
{{#each (array @model.channel) as |channel|}}
<ChatChannel
@targetMessageId={{readonly @params.messageId}}
@channel={{channel}}
/>
{{/each}}
{{/if}}
</div>
</div>
{{/if}}
{{/if}}
</div>
</template>

View File

@@ -30,7 +30,7 @@ export default class ChatRoute extends DiscourseRoute {
if (
transition.from && // don't intercept when directly loading chat
this.chatStateManager.isDrawerPreferred &&
this.chatDrawerRouter.routeNames.includes(transition.targetName)
this.chatDrawerRouter.canHandleRoute(transition.to)
) {
transition.abort();

View File

@@ -1,5 +1,6 @@
import { tracked } from "@glimmer/tracking";
import Service, { service } from "@ember/service";
import { popupAjaxError } from "discourse/lib/ajax-error";
import ChatDrawerRoutesBrowse from "discourse/plugins/chat/discourse/components/chat/drawer-routes/browse";
import ChatDrawerRoutesChannel from "discourse/plugins/chat/discourse/components/chat/drawer-routes/channel";
import ChatDrawerRoutesChannelInfoMembers from "discourse/plugins/chat/discourse/components/chat/drawer-routes/channel-info-members";
@@ -33,16 +34,10 @@ const ROUTES = {
}
},
},
"chat.index": { name: ChatDrawerRoutesChannels },
// order matters, non index before index
"chat.browse": {
name: ChatDrawerRoutesBrowse,
extractParams: () => ({ currentTab: "open" }),
},
"chat.browse.index": {
name: ChatDrawerRoutesBrowse,
extractParams: () => ({ currentTab: "open" }),
},
"chat.browse.open": {
name: ChatDrawerRoutesBrowse,
extractParams: (r) => ({ currentTab: r.localName }),
@@ -60,28 +55,53 @@ const ROUTES = {
extractParams: (r) => ({ currentTab: r.localName }),
},
"chat.channels": { name: ChatDrawerRoutesChannels },
"chat.channel": { name: ChatDrawerRoutesChannel },
"chat.channel.index": { name: ChatDrawerRoutesChannel },
"chat.channel": {
name: ChatDrawerRoutesChannel,
async model(params) {
const channel = await this.chatChannelsManager.find(params.channelId);
return { channel };
},
afterModel(model) {
this.chat.activeChannel = model.channel;
},
deactivate() {
this.chat.activeChannel = null;
},
},
"chat.channel.thread": {
name: ChatDrawerRoutesChannelThread,
extractParams: (route) => {
return {
channelId: route.parent.params.channelId,
threadId: route.params.threadId,
};
},
},
"chat.channel.thread.index": {
name: ChatDrawerRoutesChannelThread,
extractParams: (route) => {
return {
channelId: route.parent.params.channelId,
threadId: route.params.threadId,
};
async model(params) {
const channel = await this.chatChannelsManager.find(params.channelId);
const thread = await channel.threadsManager.find(
channel.id,
params.threadId
);
return { channel, thread };
},
afterModel(model) {
this.chat.activeChannel = model.channel;
},
deactivate() {
this.chat.activeChannel = null;
},
},
"chat.channel.thread.near-message": {
name: ChatDrawerRoutesChannelThread,
extractParams: (route) => {
return {
channelId: route.parent.parent.params.channelId,
@@ -89,14 +109,47 @@ const ROUTES = {
messageId: route.params.messageId,
};
},
async model(params) {
const channel = await this.chatChannelsManager.find(params.channelId);
const thread = await channel.threadsManager.find(
channel.id,
params.threadId
);
return { channel, thread };
},
afterModel(model) {
this.chat.activeChannel = model.channel;
},
deactivate() {
this.chat.activeChannel = null;
},
},
"chat.channel.threads": {
name: ChatDrawerRoutesChannelThreads,
extractParams: (route) => {
return {
channelId: route.parent.params.channelId,
};
},
async model(params) {
const channel = await this.chatChannelsManager.find(params.channelId);
return { channel };
},
afterModel(model) {
this.chat.activeChannel = model.channel;
},
deactivate() {
this.chat.activeChannel = null;
},
},
"chat.direct-messages": {
name: ChatDrawerRoutesDirectMessages,
@@ -106,45 +159,90 @@ const ROUTES = {
},
"chat.channel.near-message": {
name: ChatDrawerRoutesChannel,
extractParams: (route) => {
return {
channelId: route.parent.params.channelId,
messageId: route.params.messageId,
};
},
async model(params) {
const channel = await this.chatChannelsManager.find(params.channelId);
return { channel };
},
afterModel(model) {
this.chat.activeChannel = model.channel;
},
deactivate() {
this.chat.activeChannel = null;
},
},
"chat.channel.near-message-with-thread": {
name: ChatDrawerRoutesChannel,
extractParams: (route) => {
return {
channelId: route.parent.params.channelId,
messageId: route.params.messageId,
};
},
async model(params) {
const channel = await this.chatChannelsManager.find(params.channelId);
return { channel };
},
afterModel(model) {
this.chat.activeChannel = model.channel;
},
deactivate() {
this.chat.activeChannel = null;
},
},
"chat.channel.info.settings": {
name: ChatDrawerRoutesChannelInfoSettings,
extractParams: (route) => {
return {
channelId: route.parent.params.channelId,
};
},
async model(params) {
const channel = await this.chatChannelsManager.find(params.channelId);
return { channel };
},
afterModel(model) {
this.chat.activeChannel = model.channel;
},
deactivate() {
this.chat.activeChannel = null;
},
},
"chat.channel.info.members": {
name: ChatDrawerRoutesChannelInfoMembers,
extractParams: (route) => {
return {
channelId: route.parent.params.channelId,
};
},
},
"chat.channel-legacy": {
name: ChatDrawerRoutesChannel,
extractParams: (route) => {
return {
channelId: route.params.channelId,
messageId: route.queryParams.messageId,
};
async model(params) {
const channel = await this.chatChannelsManager.find(params.channelId);
return { channel };
},
afterModel(model) {
this.chat.activeChannel = model.channel;
},
deactivate() {
this.chat.activeChannel = null;
},
},
};
@@ -161,9 +259,18 @@ export default class ChatDrawerRouter extends Service {
@tracked drawerRoute = null;
@tracked params = null;
@tracked currentRouteName = null;
@tracked model = null;
routeNames = Object.keys(ROUTES);
canHandleRoute(route) {
return !!ROUTES[this.#forceParentRouteForIndex(route).name];
}
get activeChannelId() {
return this.model?.channel?.id;
}
get hasThreads() {
if (!this.siteSettings.chat_threads_enabled) {
return false;
@@ -176,12 +283,20 @@ export default class ChatDrawerRouter extends Service {
return this.chat.userCanAccessDirectMessages;
}
stateFor(route) {
this.drawerRoute?.deactivate?.(this.chatHistory.currentRoute);
async stateFor(route) {
this.drawerRoute?.deactivate?.call(this, this.chatHistory.currentRoute);
this.chatHistory.visit(route);
this.drawerRoute = ROUTES[route.name];
this.params = this.drawerRoute?.extractParams?.(route) || route.params;
this.drawerRoute = ROUTES[this.#forceParentRouteForIndex(route).name];
this.params =
this.drawerRoute?.extractParams?.call(this, route) || route.params;
try {
this.model = await this.drawerRoute?.model?.call(this, this.params);
this.drawerRoute?.afterModel?.call(this, this.model);
} catch (e) {
popupAjaxError(e);
}
this.component = this.drawerRoute?.name || ChatDrawerRoutesChannels;
this.currentRouteName = route.name;
this.drawerRoute.activate?.(route);
@@ -193,11 +308,14 @@ export default class ChatDrawerRouter extends Service {
}
#routeFromURL(url) {
let route = this.router.recognize(url);
const route = this.router.recognize(url);
return this.#forceParentRouteForIndex(route);
}
#forceParentRouteForIndex(route) {
// ember might recognize the index subroute
if (route.localName === "index") {
route = route.parent;
return route.parent;
}
return route;

View File

@@ -140,7 +140,7 @@ export default class ChatSubscriptionsManager extends Service {
@bind
_onKickFromChannel(busData) {
this.chatChannelsManager.find(busData.channel_id).then((channel) => {
if (this.chat.activeChannel.id === channel.id) {
if (this.chat.activeChannel?.id === channel.id) {
this.dialog.alert({
message: i18n("chat.kicked_from_channel"),
didConfirm: () => {