mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-04-16 06:07:06 +02:00
Skip refocus InputField::Inner if field unfocused.
I hope this fixes #26223.
This commit is contained in:
parent
f671897a4d
commit
2fb7bdc803
13 changed files with 152 additions and 130 deletions
|
@ -80,11 +80,13 @@ public:
|
|||
explicit Show(not_null<Panel*> panel);
|
||||
~Show();
|
||||
|
||||
void showBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options
|
||||
= Ui::LayerOption::KeepOther) const override;
|
||||
void hideLayer() const override;
|
||||
void showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<Ui::BoxContent>,
|
||||
std::unique_ptr<Ui::LayerWidget>> &&layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated) const override;
|
||||
[[nodiscard]] not_null<QWidget*> toastParent() const override;
|
||||
[[nodiscard]] bool valid() const override;
|
||||
operator bool() const override;
|
||||
|
@ -102,19 +104,27 @@ Show::Show(not_null<Panel*> panel)
|
|||
|
||||
Show::~Show() = default;
|
||||
|
||||
void Show::showBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options) const {
|
||||
if (const auto panel = _panel.get()) {
|
||||
panel->showBox(std::move(content), options);
|
||||
void Show::showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<Ui::BoxContent>,
|
||||
std::unique_ptr<Ui::LayerWidget>> &&layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated) const {
|
||||
using UniqueLayer = std::unique_ptr<Ui::LayerWidget>;
|
||||
using ObjectBox = object_ptr<Ui::BoxContent>;
|
||||
if (auto layerWidget = std::get_if<UniqueLayer>(&layer)) {
|
||||
if (const auto panel = _panel.get()) {
|
||||
panel->showLayer(std::move(*layerWidget), options, animated);
|
||||
}
|
||||
} else if (auto box = std::get_if<ObjectBox>(&layer)) {
|
||||
if (const auto panel = _panel.get()) {
|
||||
panel->showBox(std::move(*box), options, animated);
|
||||
}
|
||||
} else if (const auto panel = _panel.get()) {
|
||||
panel->hideLayer(animated);
|
||||
}
|
||||
}
|
||||
|
||||
void Show::hideLayer() const {
|
||||
if (const auto panel = _panel.get()) {
|
||||
panel->hideLayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
not_null<QWidget*> Show::toastParent() const {
|
||||
const auto panel = _panel.get();
|
||||
|
@ -1527,6 +1537,20 @@ void Panel::showBox(
|
|||
_layerBg->showBox(std::move(box), options, animated);
|
||||
}
|
||||
|
||||
void Panel::showLayer(
|
||||
std::unique_ptr<Ui::LayerWidget> layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated) {
|
||||
hideStickedTooltip(StickedTooltipHide::Unavailable);
|
||||
if (window()->width() < st::groupCallWidth
|
||||
|| window()->height() < st::groupCallWidth) {
|
||||
window()->resize(
|
||||
std::max(window()->width(), st::groupCallWidth),
|
||||
std::max(window()->height(), st::groupCallWidth));
|
||||
}
|
||||
_layerBg->showLayer(std::move(layer), options, animated);
|
||||
}
|
||||
|
||||
void Panel::hideLayer(anim::type animated) {
|
||||
_layerBg->hideAll(animated);
|
||||
}
|
||||
|
|
|
@ -34,6 +34,7 @@ class GroupCall;
|
|||
|
||||
namespace Ui {
|
||||
class BoxContent;
|
||||
class LayerWidget;
|
||||
enum class LayerOption;
|
||||
using LayerOptions = base::flags<LayerOption>;
|
||||
class AbstractButton;
|
||||
|
@ -106,6 +107,10 @@ public:
|
|||
object_ptr<Ui::BoxContent> box,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated = anim::type::normal);
|
||||
void showLayer(
|
||||
std::unique_ptr<Ui::LayerWidget> layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated = anim::type::normal);
|
||||
void hideLayer(anim::type animated = anim::type::normal);
|
||||
[[nodiscard]] bool isLayerShown() const;
|
||||
|
||||
|
|
|
@ -156,7 +156,7 @@ PanelController::~PanelController() {
|
|||
saveSettings();
|
||||
}
|
||||
if (_panel) {
|
||||
_panel->destroyLayer();
|
||||
_panel->hideLayer(anim::type::instant);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,11 +16,13 @@ public:
|
|||
std::shared_ptr<Ui::Show> show,
|
||||
not_null<Session*> session);
|
||||
|
||||
void showBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options
|
||||
= Ui::LayerOption::KeepOther) const override;
|
||||
void hideLayer() const override;
|
||||
void showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<Ui::BoxContent>,
|
||||
std::unique_ptr<Ui::LayerWidget>> &&layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated) const override;
|
||||
not_null<QWidget*> toastParent() const override;
|
||||
bool valid() const override;
|
||||
operator bool() const override;
|
||||
|
@ -40,14 +42,14 @@ SimpleSessionShow::SimpleSessionShow(
|
|||
, _session(session) {
|
||||
}
|
||||
|
||||
void SimpleSessionShow::showBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options) const {
|
||||
_show->showBox(std::move(content), options);
|
||||
}
|
||||
|
||||
void SimpleSessionShow::hideLayer() const {
|
||||
_show->hideLayer();
|
||||
void SimpleSessionShow::showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<Ui::BoxContent>,
|
||||
std::unique_ptr<Ui::LayerWidget>> &&layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated) const {
|
||||
_show->showOrHideBoxOrLayer(std::move(layer), options, animated);
|
||||
}
|
||||
|
||||
not_null<QWidget*> SimpleSessionShow::toastParent() const {
|
||||
|
|
|
@ -407,7 +407,7 @@ MainWidget *MainWindow::sessionContent() const {
|
|||
return _main.data();
|
||||
}
|
||||
|
||||
void MainWindow::showBoxOrLayer(
|
||||
void MainWindow::showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<Ui::BoxContent>,
|
||||
|
@ -419,7 +419,7 @@ void MainWindow::showBoxOrLayer(
|
|||
if (auto layerWidget = std::get_if<UniqueLayer>(&layer)) {
|
||||
ensureLayerCreated();
|
||||
_layer->showLayer(std::move(*layerWidget), options, animated);
|
||||
} else if (auto box = std::get_if<ObjectBox>(&layer); *box != nullptr) {
|
||||
} else if (auto box = std::get_if<ObjectBox>(&layer)) {
|
||||
ensureLayerCreated();
|
||||
_layer->showBox(std::move(*box), options, animated);
|
||||
} else {
|
||||
|
@ -435,20 +435,6 @@ void MainWindow::showBoxOrLayer(
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::ui_showBox(
|
||||
object_ptr<Ui::BoxContent> box,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated) {
|
||||
showBoxOrLayer(std::move(box), options, animated);
|
||||
}
|
||||
|
||||
void MainWindow::showLayer(
|
||||
std::unique_ptr<Ui::LayerWidget> &&layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated) {
|
||||
showBoxOrLayer(std::move(layer), options, animated);
|
||||
}
|
||||
|
||||
bool MainWindow::ui_isLayerShown() const {
|
||||
return _layer != nullptr;
|
||||
}
|
||||
|
|
|
@ -83,8 +83,11 @@ public:
|
|||
|
||||
[[nodiscard]] QPixmap grabForSlideAnimation();
|
||||
|
||||
void showLayer(
|
||||
std::unique_ptr<Ui::LayerWidget> &&layer,
|
||||
void showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<Ui::BoxContent>,
|
||||
std::unique_ptr<Ui::LayerWidget>> &&layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated);
|
||||
void showSpecialLayer(
|
||||
|
@ -93,10 +96,6 @@ public:
|
|||
bool showSectionInExistingLayer(
|
||||
not_null<Window::SectionMemento*> memento,
|
||||
const Window::SectionShow ¶ms);
|
||||
void ui_showBox(
|
||||
object_ptr<Ui::BoxContent> box,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated);
|
||||
void ui_hideSettingsAndLayer(anim::type animated);
|
||||
void ui_removeLayerBlackout();
|
||||
[[nodiscard]] bool ui_isLayerShown() const;
|
||||
|
@ -122,14 +121,6 @@ private:
|
|||
void ensureLayerCreated();
|
||||
void destroyLayer();
|
||||
|
||||
void showBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<Ui::BoxContent>,
|
||||
std::unique_ptr<Ui::LayerWidget>> &&layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated);
|
||||
|
||||
void themeUpdated(const Window::Theme::BackgroundUpdate &data);
|
||||
|
||||
QPoint _lastMousePosition;
|
||||
|
|
|
@ -487,7 +487,7 @@ bool Panel::showWebview(
|
|||
return false;
|
||||
}
|
||||
showWebviewProgress();
|
||||
_widget->destroyLayer();
|
||||
_widget->hideLayer(anim::type::instant);
|
||||
_webview->window.navigate(url);
|
||||
_widget->setBackAllowed(allowBack);
|
||||
if (bottomText) {
|
||||
|
|
|
@ -510,7 +510,7 @@ bool Panel::showWebview(
|
|||
}
|
||||
const auto allowBack = false;
|
||||
showWebviewProgress();
|
||||
_widget->destroyLayer();
|
||||
_widget->hideLayer(anim::type::instant);
|
||||
updateThemeParams(params);
|
||||
_webview->window.navigate(url);
|
||||
_widget->setBackAllowed(allowBack);
|
||||
|
|
|
@ -45,11 +45,13 @@ class Show final : public Ui::Show {
|
|||
public:
|
||||
explicit Show(not_null<Controller*> window);
|
||||
|
||||
void showBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options
|
||||
= Ui::LayerOption::KeepOther) const override;
|
||||
void hideLayer() const override;
|
||||
void showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<Ui::BoxContent>,
|
||||
std::unique_ptr<Ui::LayerWidget>> &&layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated) const override;
|
||||
[[nodiscard]] not_null<QWidget*> toastParent() const override;
|
||||
[[nodiscard]] bool valid() const override;
|
||||
operator bool() const override;
|
||||
|
@ -63,19 +65,18 @@ Show::Show(not_null<Controller*> window)
|
|||
: _window(base::make_weak(window)) {
|
||||
}
|
||||
|
||||
void Show::showBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options) const {
|
||||
void Show::showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<Ui::BoxContent>,
|
||||
std::unique_ptr<Ui::LayerWidget>> &&layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated) const {
|
||||
if (const auto window = _window.get()) {
|
||||
window->show(std::move(content), options);
|
||||
}
|
||||
}
|
||||
|
||||
void Show::hideLayer() const {
|
||||
if (const auto window = _window.get()) {
|
||||
window->show(
|
||||
object_ptr<Ui::BoxContent>{ nullptr },
|
||||
Ui::LayerOption::CloseOther);
|
||||
window->widget()->showOrHideBoxOrLayer(
|
||||
std::move(layer),
|
||||
options,
|
||||
animated);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -408,14 +409,14 @@ void Controller::showLayer(
|
|||
std::unique_ptr<Ui::LayerWidget> &&layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated) {
|
||||
_widget.showLayer(std::move(layer), options, animated);
|
||||
_widget.showOrHideBoxOrLayer(std::move(layer), options, animated);
|
||||
}
|
||||
|
||||
void Controller::showBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated) {
|
||||
_widget.ui_showBox(std::move(content), options, animated);
|
||||
_widget.showOrHideBoxOrLayer(std::move(content), options, animated);
|
||||
}
|
||||
|
||||
void Controller::showRightColumn(object_ptr<TWidget> widget) {
|
||||
|
@ -423,7 +424,7 @@ void Controller::showRightColumn(object_ptr<TWidget> widget) {
|
|||
}
|
||||
|
||||
void Controller::hideLayer(anim::type animated) {
|
||||
_widget.ui_showBox({ nullptr }, Ui::LayerOption::CloseOther, animated);
|
||||
_widget.showOrHideBoxOrLayer(v::null, Ui::LayerOption::CloseOther, animated);
|
||||
}
|
||||
|
||||
void Controller::hideSettingsAndLayer(anim::type animated) {
|
||||
|
|
|
@ -86,31 +86,38 @@ public:
|
|||
|
||||
[[nodiscard]] int verticalShadowTop() const;
|
||||
|
||||
template <typename BoxType>
|
||||
QPointer<BoxType> show(
|
||||
object_ptr<BoxType> content,
|
||||
Ui::LayerOptions options = Ui::LayerOption::KeepOther,
|
||||
anim::type animated = anim::type::normal) {
|
||||
const auto result = QPointer<BoxType>(content.data());
|
||||
showBox(std::move(content), options, animated);
|
||||
return result;
|
||||
}
|
||||
|
||||
void showToast(Ui::Toast::Config &&config);
|
||||
void showToast(TextWithEntities &&text, crl::time duration = 0);
|
||||
void showToast(const QString &text, crl::time duration = 0);
|
||||
|
||||
void showRightColumn(object_ptr<TWidget> widget);
|
||||
|
||||
void showBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated);
|
||||
void showLayer(
|
||||
std::unique_ptr<Ui::LayerWidget> &&layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated = anim::type::normal);
|
||||
|
||||
void showRightColumn(object_ptr<TWidget> widget);
|
||||
|
||||
void hideLayer(anim::type animated = anim::type::normal);
|
||||
void hideSettingsAndLayer(anim::type animated = anim::type::normal);
|
||||
[[nodiscard]] bool isLayerShown() const;
|
||||
|
||||
template <
|
||||
typename BoxType,
|
||||
typename = std::enable_if_t<
|
||||
std::is_base_of_v<Ui::BoxContent, BoxType>>>
|
||||
QPointer<BoxType> show(
|
||||
object_ptr<BoxType> content,
|
||||
Ui::LayerOptions options = Ui::LayerOption::KeepOther,
|
||||
anim::type animated = anim::type()) {
|
||||
auto result = QPointer<BoxType>(content.data());
|
||||
showBox(std::move(content), options, animated);
|
||||
return result;
|
||||
}
|
||||
|
||||
void activate();
|
||||
void reActivate();
|
||||
void updateIsActiveFocus();
|
||||
|
@ -162,10 +169,6 @@ private:
|
|||
void setupSideBar();
|
||||
void sideBarChanged();
|
||||
|
||||
void showBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated);
|
||||
void checkThemeEditor();
|
||||
void checkLockByTerms();
|
||||
void showTermsDecline();
|
||||
|
|
|
@ -111,13 +111,16 @@ constexpr auto kMaxChatEntryHistorySize = 50;
|
|||
|
||||
class MainWindowShow final : public ChatHelpers::Show {
|
||||
public:
|
||||
explicit MainWindowShow(not_null<SessionNavigation*> navigation);
|
||||
explicit MainWindowShow(not_null<SessionController*> controller);
|
||||
|
||||
void showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<Ui::BoxContent>,
|
||||
std::unique_ptr<Ui::LayerWidget>> &&layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated) const override;
|
||||
|
||||
void showBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options
|
||||
= Ui::LayerOption::KeepOther) const override;
|
||||
void hideLayer() const override;
|
||||
not_null<QWidget*> toastParent() const override;
|
||||
bool valid() const override;
|
||||
operator bool() const override;
|
||||
|
@ -144,24 +147,22 @@ private:
|
|||
|
||||
};
|
||||
|
||||
MainWindowShow::MainWindowShow(
|
||||
not_null<SessionNavigation*> navigation)
|
||||
: _window(base::make_weak(navigation->parentController())) {
|
||||
MainWindowShow::MainWindowShow(not_null<SessionController*> controller)
|
||||
: _window(base::make_weak(controller)) {
|
||||
}
|
||||
|
||||
void MainWindowShow::showBox(
|
||||
object_ptr<Ui::BoxContent> content,
|
||||
Ui::LayerOptions options) const {
|
||||
void MainWindowShow::showOrHideBoxOrLayer(
|
||||
std::variant<
|
||||
v::null_t,
|
||||
object_ptr<Ui::BoxContent>,
|
||||
std::unique_ptr<Ui::LayerWidget>> &&layer,
|
||||
Ui::LayerOptions options,
|
||||
anim::type animated) const {
|
||||
if (const auto window = _window.get()) {
|
||||
window->show(std::move(content), options);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindowShow::hideLayer() const {
|
||||
if (const auto window = _window.get()) {
|
||||
window->show(
|
||||
object_ptr<Ui::BoxContent>{ nullptr },
|
||||
Ui::LayerOption::CloseOther);
|
||||
window->window().widget()->showOrHideBoxOrLayer(
|
||||
std::move(layer),
|
||||
options,
|
||||
animated);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -395,8 +396,7 @@ void SessionNavigation::resolveChannelById(
|
|||
return;
|
||||
}
|
||||
const auto fail = crl::guard(this, [=] {
|
||||
MainWindowShow(this).showToast(
|
||||
tr::lng_error_post_link_invalid(tr::now));
|
||||
uiShow()->showToast(tr::lng_error_post_link_invalid(tr::now));
|
||||
});
|
||||
_api.request(base::take(_resolveRequestId)).cancel();
|
||||
_resolveRequestId = _api.request(MTPchannels_GetChannels(
|
||||
|
@ -594,8 +594,7 @@ void SessionNavigation::joinVoiceChatFromLink(
|
|||
Expects(info.voicechatHash.has_value());
|
||||
|
||||
const auto bad = crl::guard(this, [=] {
|
||||
MainWindowShow(this).showToast(
|
||||
tr::lng_group_invite_bad_link(tr::now));
|
||||
uiShow()->showToast(tr::lng_group_invite_bad_link(tr::now));
|
||||
});
|
||||
const auto hash = *info.voicechatHash;
|
||||
_api.request(base::take(_resolveRequestId)).cancel();
|
||||
|
@ -824,23 +823,23 @@ void SessionNavigation::showPollResults(
|
|||
|
||||
auto SessionNavigation::showToast(Ui::Toast::Config &&config)
|
||||
-> base::weak_ptr<Ui::Toast::Instance> {
|
||||
return MainWindowShow(this).showToast(std::move(config));
|
||||
return uiShow()->showToast(std::move(config));
|
||||
}
|
||||
|
||||
auto SessionNavigation::showToast(const QString &text, crl::time duration)
|
||||
-> base::weak_ptr<Ui::Toast::Instance> {
|
||||
return MainWindowShow(this).showToast(text);
|
||||
return uiShow()->showToast(text);
|
||||
}
|
||||
|
||||
auto SessionNavigation::showToast(
|
||||
TextWithEntities &&text,
|
||||
crl::time duration)
|
||||
-> base::weak_ptr<Ui::Toast::Instance> {
|
||||
return MainWindowShow(this).showToast(std::move(text));
|
||||
return uiShow()->showToast(std::move(text));
|
||||
}
|
||||
|
||||
std::shared_ptr<ChatHelpers::Show> SessionNavigation::uiShow() {
|
||||
return std::make_shared<MainWindowShow>(this);
|
||||
return parentController()->uiShow();
|
||||
}
|
||||
|
||||
struct SessionController::CachedThemeKey {
|
||||
|
@ -2485,6 +2484,13 @@ bool SessionController::contentOverlapped(QWidget *w, QPaintEvent *e) {
|
|||
return widget()->contentOverlapped(w, e);
|
||||
}
|
||||
|
||||
std::shared_ptr<ChatHelpers::Show> SessionController::uiShow() {
|
||||
if (!_cachedShow) {
|
||||
_cachedShow = std::make_shared<MainWindowShow>(this);
|
||||
}
|
||||
return _cachedShow;
|
||||
}
|
||||
|
||||
SessionController::~SessionController() {
|
||||
resetFakeUnreadWhileOpened();
|
||||
}
|
||||
|
|
|
@ -278,7 +278,7 @@ public:
|
|||
const QString &text,
|
||||
crl::time duration = 0);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow();
|
||||
[[nodiscard]] virtual std::shared_ptr<ChatHelpers::Show> uiShow();
|
||||
|
||||
private:
|
||||
void resolvePhone(
|
||||
|
@ -591,6 +591,8 @@ public:
|
|||
|
||||
[[nodiscard]] bool contentOverlapped(QWidget *w, QPaintEvent *e);
|
||||
|
||||
[[nodiscard]] std::shared_ptr<ChatHelpers::Show> uiShow() override;
|
||||
|
||||
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||
return _lifetime;
|
||||
}
|
||||
|
@ -641,6 +643,8 @@ private:
|
|||
const std::unique_ptr<ChatHelpers::EmojiInteractions> _emojiInteractions;
|
||||
const bool _isPrimary = false;
|
||||
|
||||
mutable std::shared_ptr<ChatHelpers::Show> _cachedShow;
|
||||
|
||||
QString _authedName;
|
||||
|
||||
using SendingAnimation = Ui::MessageSendingAnimationController;
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 303a07f9461524dc6debf86ae04d0ee6d3134eb6
|
||||
Subproject commit a7d503188918904d12a069bca7d3f6c92b9fb553
|
Loading…
Add table
Reference in a new issue