mirror of
https://github.com/Rafostar/clapper.git
synced 2025-08-31 08:21:59 +02:00
headerbar: Merge code into single subclass
We no longer ship the web app that was reusing parts of this code. Combine it into single file without additional subclass.
This commit is contained in:
274
src/headerbar.js
274
src/headerbar.js
@@ -1,11 +1,233 @@
|
|||||||
const { GObject } = imports.gi;
|
const { GObject, Gtk } = imports.gi;
|
||||||
const { HeaderBarBase } = imports.src.headerbarBase;
|
const Debug = imports.src.debug;
|
||||||
|
const Misc = imports.src.misc;
|
||||||
|
|
||||||
|
const { debug } = Debug;
|
||||||
|
|
||||||
var HeaderBar = GObject.registerClass({
|
var HeaderBar = GObject.registerClass({
|
||||||
GTypeName: 'ClapperHeaderBar',
|
GTypeName: 'ClapperHeaderBar',
|
||||||
},
|
},
|
||||||
class ClapperHeaderBar extends HeaderBarBase
|
class ClapperHeaderBar extends Gtk.Box
|
||||||
{
|
{
|
||||||
|
_init()
|
||||||
|
{
|
||||||
|
super._init({
|
||||||
|
can_focus: false,
|
||||||
|
orientation: Gtk.Orientation.HORIZONTAL,
|
||||||
|
spacing: 6,
|
||||||
|
margin_top: 6,
|
||||||
|
margin_start: 6,
|
||||||
|
margin_end: 6,
|
||||||
|
});
|
||||||
|
this.add_css_class('osdheaderbar');
|
||||||
|
|
||||||
|
this.isMaximized = false;
|
||||||
|
this.isMenuOnLeft = true;
|
||||||
|
|
||||||
|
const uiBuilder = Misc.getBuilderForName('clapper.ui');
|
||||||
|
|
||||||
|
this.menuWidget = new Gtk.Box({
|
||||||
|
orientation: Gtk.Orientation.HORIZONTAL,
|
||||||
|
valign: Gtk.Align.CENTER,
|
||||||
|
spacing: 6,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.menuButton = new Gtk.MenuButton({
|
||||||
|
icon_name: 'open-menu-symbolic',
|
||||||
|
valign: Gtk.Align.CENTER,
|
||||||
|
can_focus: false,
|
||||||
|
});
|
||||||
|
const menuToggleButton = this.menuButton.get_first_child();
|
||||||
|
menuToggleButton.add_css_class('osd');
|
||||||
|
const mainMenuModel = uiBuilder.get_object('mainMenu');
|
||||||
|
const mainMenuPopover = new HeaderBarPopover(mainMenuModel);
|
||||||
|
this.menuButton.set_popover(mainMenuPopover);
|
||||||
|
this.menuButton.add_css_class('circular');
|
||||||
|
this.menuWidget.append(this.menuButton);
|
||||||
|
|
||||||
|
this.extraButtonsBox = new Gtk.Box({
|
||||||
|
orientation: Gtk.Orientation.HORIZONTAL,
|
||||||
|
valign: Gtk.Align.CENTER,
|
||||||
|
});
|
||||||
|
this.extraButtonsBox.add_css_class('linked');
|
||||||
|
|
||||||
|
const floatButton = new Gtk.Button({
|
||||||
|
icon_name: 'pip-in-symbolic',
|
||||||
|
can_focus: false,
|
||||||
|
});
|
||||||
|
floatButton.add_css_class('osd');
|
||||||
|
floatButton.add_css_class('circular');
|
||||||
|
floatButton.add_css_class('linkedleft');
|
||||||
|
floatButton.connect('clicked',
|
||||||
|
this._onFloatButtonClicked.bind(this)
|
||||||
|
);
|
||||||
|
this.extraButtonsBox.append(floatButton);
|
||||||
|
|
||||||
|
const separator = new Gtk.Separator({
|
||||||
|
orientation: Gtk.Orientation.VERTICAL,
|
||||||
|
});
|
||||||
|
separator.add_css_class('linkseparator');
|
||||||
|
this.extraButtonsBox.append(separator);
|
||||||
|
|
||||||
|
const fullscreenButton = new Gtk.Button({
|
||||||
|
icon_name: 'view-fullscreen-symbolic',
|
||||||
|
can_focus: false,
|
||||||
|
});
|
||||||
|
fullscreenButton.add_css_class('osd');
|
||||||
|
fullscreenButton.add_css_class('circular');
|
||||||
|
fullscreenButton.add_css_class('linkedright');
|
||||||
|
fullscreenButton.connect('clicked',
|
||||||
|
this._onFullscreenButtonClicked.bind(this)
|
||||||
|
);
|
||||||
|
this.extraButtonsBox.append(fullscreenButton);
|
||||||
|
this.menuWidget.append(this.extraButtonsBox);
|
||||||
|
|
||||||
|
this.spacerWidget = new Gtk.Box({
|
||||||
|
hexpand: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.minimizeWidget = this._getWindowButton('minimize');
|
||||||
|
this.maximizeWidget = this._getWindowButton('maximize');
|
||||||
|
this.closeWidget = this._getWindowButton('close');
|
||||||
|
|
||||||
|
const gtkSettings = Gtk.Settings.get_default();
|
||||||
|
this._onLayoutUpdate(gtkSettings);
|
||||||
|
|
||||||
|
gtkSettings.connect(
|
||||||
|
'notify::gtk-decoration-layout',
|
||||||
|
this._onLayoutUpdate.bind(this)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
setMenuOnLeft(isOnLeft)
|
||||||
|
{
|
||||||
|
if(this.isMenuOnLeft === isOnLeft)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(isOnLeft) {
|
||||||
|
this.menuWidget.reorder_child_after(
|
||||||
|
this.extraButtonsBox, this.menuButton
|
||||||
|
);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.menuWidget.reorder_child_after(
|
||||||
|
this.menuButton, this.extraButtonsBox
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isMenuOnLeft = isOnLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
setMaximized(isMaximized)
|
||||||
|
{
|
||||||
|
if(this.isMaximized === isMaximized)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.maximizeWidget.icon_name = (isMaximized)
|
||||||
|
? 'window-restore-symbolic'
|
||||||
|
: 'window-maximize-symbolic';
|
||||||
|
|
||||||
|
this.isMaximized = isMaximized;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onLayoutUpdate(gtkSettings)
|
||||||
|
{
|
||||||
|
const gtkLayout = gtkSettings.gtk_decoration_layout;
|
||||||
|
|
||||||
|
this._replaceButtons(gtkLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
_replaceButtons(gtkLayout)
|
||||||
|
{
|
||||||
|
const modLayout = gtkLayout.replace(':', ',spacer,');
|
||||||
|
const layoutArr = modLayout.split(',');
|
||||||
|
|
||||||
|
let lastWidget = null;
|
||||||
|
|
||||||
|
let showMinimize = false;
|
||||||
|
let showMaximize = false;
|
||||||
|
let showClose = false;
|
||||||
|
|
||||||
|
let menuAdded = false;
|
||||||
|
let spacerAdded = false;
|
||||||
|
|
||||||
|
debug(`headerbar layout: ${modLayout}`);
|
||||||
|
|
||||||
|
for(let name of layoutArr) {
|
||||||
|
/* Menu might be named "appmenu" */
|
||||||
|
if(!menuAdded && (!name || name === 'appmenu' || name === 'icon'))
|
||||||
|
name = 'menu';
|
||||||
|
|
||||||
|
const widget = this[`${name}Widget`];
|
||||||
|
if(!widget) continue;
|
||||||
|
|
||||||
|
if(!widget.parent)
|
||||||
|
this.append(widget);
|
||||||
|
else
|
||||||
|
this.reorder_child_after(widget, lastWidget);
|
||||||
|
|
||||||
|
switch(name) {
|
||||||
|
case 'spacer':
|
||||||
|
spacerAdded = true;
|
||||||
|
break;
|
||||||
|
case 'minimize':
|
||||||
|
showMinimize = true;
|
||||||
|
break;
|
||||||
|
case 'maximize':
|
||||||
|
showMaximize = true;
|
||||||
|
break;
|
||||||
|
case 'close':
|
||||||
|
showClose = true;
|
||||||
|
break;
|
||||||
|
case 'menu':
|
||||||
|
this.setMenuOnLeft(!spacerAdded);
|
||||||
|
menuAdded = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastWidget = widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.minimizeWidget.visible = showMinimize;
|
||||||
|
this.maximizeWidget.visible = showMaximize;
|
||||||
|
this.closeWidget.visible = showClose;
|
||||||
|
}
|
||||||
|
|
||||||
|
_getWindowButton(name)
|
||||||
|
{
|
||||||
|
const button = new Gtk.Button({
|
||||||
|
icon_name: `window-${name}-symbolic`,
|
||||||
|
valign: Gtk.Align.CENTER,
|
||||||
|
can_focus: false,
|
||||||
|
});
|
||||||
|
button.add_css_class('osd');
|
||||||
|
button.add_css_class('circular');
|
||||||
|
|
||||||
|
if(name === 'maximize')
|
||||||
|
name = 'toggle-maximized';
|
||||||
|
|
||||||
|
button.connect('clicked',
|
||||||
|
this._onWindowButtonActivate.bind(this, name)
|
||||||
|
);
|
||||||
|
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateFloatIcon(isFloating)
|
||||||
|
{
|
||||||
|
const floatButton = this.extraButtonsBox.get_first_child();
|
||||||
|
if(!floatButton) return;
|
||||||
|
|
||||||
|
const iconName = (isFloating)
|
||||||
|
? 'pip-out-symbolic'
|
||||||
|
: 'pip-in-symbolic';
|
||||||
|
|
||||||
|
if(floatButton.icon_name !== iconName)
|
||||||
|
floatButton.icon_name = iconName;
|
||||||
|
}
|
||||||
|
|
||||||
_onWindowButtonActivate(action)
|
_onWindowButtonActivate(action)
|
||||||
{
|
{
|
||||||
this.activate_action(`window.${action}`, null);
|
this.activate_action(`window.${action}`, null);
|
||||||
@@ -29,3 +251,49 @@ class ClapperHeaderBar extends HeaderBarBase
|
|||||||
this.root.fullscreen();
|
this.root.fullscreen();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var HeaderBarPopover = GObject.registerClass({
|
||||||
|
GTypeName: 'ClapperHeaderBarPopover',
|
||||||
|
},
|
||||||
|
class ClapperHeaderBarPopover extends Gtk.PopoverMenu
|
||||||
|
{
|
||||||
|
_init(model)
|
||||||
|
{
|
||||||
|
super._init({
|
||||||
|
menu_model: model,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.connect('map', this._onMap.bind(this));
|
||||||
|
this.connect('closed', this._onClosed.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
_onMap()
|
||||||
|
{
|
||||||
|
const { child } = this.root;
|
||||||
|
|
||||||
|
if(
|
||||||
|
!child
|
||||||
|
|| !child.player
|
||||||
|
|| !child.player.widget
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
child.revealControls();
|
||||||
|
child.isPopoverOpen = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onClosed()
|
||||||
|
{
|
||||||
|
const { child } = this.root;
|
||||||
|
|
||||||
|
if(
|
||||||
|
!child
|
||||||
|
|| !child.player
|
||||||
|
|| !child.player.widget
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
child.revealControls();
|
||||||
|
child.isPopoverOpen = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
@@ -1,288 +0,0 @@
|
|||||||
const { GObject, Gtk } = imports.gi;
|
|
||||||
const Debug = imports.src.debug;
|
|
||||||
const Misc = imports.src.misc;
|
|
||||||
|
|
||||||
const { debug } = Debug;
|
|
||||||
|
|
||||||
var HeaderBarBase = GObject.registerClass({
|
|
||||||
GTypeName: 'ClapperHeaderBarBase',
|
|
||||||
},
|
|
||||||
class ClapperHeaderBarBase extends Gtk.Box
|
|
||||||
{
|
|
||||||
_init()
|
|
||||||
{
|
|
||||||
super._init({
|
|
||||||
can_focus: false,
|
|
||||||
orientation: Gtk.Orientation.HORIZONTAL,
|
|
||||||
spacing: 6,
|
|
||||||
margin_top: 6,
|
|
||||||
margin_start: 6,
|
|
||||||
margin_end: 6,
|
|
||||||
});
|
|
||||||
this.add_css_class('osdheaderbar');
|
|
||||||
|
|
||||||
this.isMaximized = false;
|
|
||||||
this.isMenuOnLeft = true;
|
|
||||||
|
|
||||||
const uiBuilder = Misc.getBuilderForName('clapper.ui');
|
|
||||||
|
|
||||||
this.menuWidget = new Gtk.Box({
|
|
||||||
orientation: Gtk.Orientation.HORIZONTAL,
|
|
||||||
valign: Gtk.Align.CENTER,
|
|
||||||
spacing: 6,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.menuButton = new Gtk.MenuButton({
|
|
||||||
icon_name: 'open-menu-symbolic',
|
|
||||||
valign: Gtk.Align.CENTER,
|
|
||||||
can_focus: false,
|
|
||||||
});
|
|
||||||
const menuToggleButton = this.menuButton.get_first_child();
|
|
||||||
menuToggleButton.add_css_class('osd');
|
|
||||||
const mainMenuModel = uiBuilder.get_object('mainMenu');
|
|
||||||
const mainMenuPopover = new HeaderBarPopover(mainMenuModel);
|
|
||||||
this.menuButton.set_popover(mainMenuPopover);
|
|
||||||
this.menuButton.add_css_class('circular');
|
|
||||||
this.menuWidget.append(this.menuButton);
|
|
||||||
|
|
||||||
this.extraButtonsBox = new Gtk.Box({
|
|
||||||
orientation: Gtk.Orientation.HORIZONTAL,
|
|
||||||
valign: Gtk.Align.CENTER,
|
|
||||||
});
|
|
||||||
this.extraButtonsBox.add_css_class('linked');
|
|
||||||
|
|
||||||
const floatButton = new Gtk.Button({
|
|
||||||
icon_name: 'pip-in-symbolic',
|
|
||||||
can_focus: false,
|
|
||||||
});
|
|
||||||
floatButton.add_css_class('osd');
|
|
||||||
floatButton.add_css_class('circular');
|
|
||||||
floatButton.add_css_class('linkedleft');
|
|
||||||
floatButton.connect('clicked',
|
|
||||||
this._onFloatButtonClicked.bind(this)
|
|
||||||
);
|
|
||||||
this.extraButtonsBox.append(floatButton);
|
|
||||||
|
|
||||||
const separator = new Gtk.Separator({
|
|
||||||
orientation: Gtk.Orientation.VERTICAL,
|
|
||||||
});
|
|
||||||
separator.add_css_class('linkseparator');
|
|
||||||
this.extraButtonsBox.append(separator);
|
|
||||||
|
|
||||||
const fullscreenButton = new Gtk.Button({
|
|
||||||
icon_name: 'view-fullscreen-symbolic',
|
|
||||||
can_focus: false,
|
|
||||||
});
|
|
||||||
fullscreenButton.add_css_class('osd');
|
|
||||||
fullscreenButton.add_css_class('circular');
|
|
||||||
fullscreenButton.add_css_class('linkedright');
|
|
||||||
fullscreenButton.connect('clicked',
|
|
||||||
this._onFullscreenButtonClicked.bind(this)
|
|
||||||
);
|
|
||||||
this.extraButtonsBox.append(fullscreenButton);
|
|
||||||
this.menuWidget.append(this.extraButtonsBox);
|
|
||||||
|
|
||||||
this.spacerWidget = new Gtk.Box({
|
|
||||||
hexpand: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.minimizeWidget = this._getWindowButton('minimize');
|
|
||||||
this.maximizeWidget = this._getWindowButton('maximize');
|
|
||||||
this.closeWidget = this._getWindowButton('close');
|
|
||||||
|
|
||||||
const gtkSettings = Gtk.Settings.get_default();
|
|
||||||
this._onLayoutUpdate(gtkSettings);
|
|
||||||
|
|
||||||
gtkSettings.connect(
|
|
||||||
'notify::gtk-decoration-layout',
|
|
||||||
this._onLayoutUpdate.bind(this)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
setMenuOnLeft(isOnLeft)
|
|
||||||
{
|
|
||||||
if(this.isMenuOnLeft === isOnLeft)
|
|
||||||
return;
|
|
||||||
|
|
||||||
if(isOnLeft) {
|
|
||||||
this.menuWidget.reorder_child_after(
|
|
||||||
this.extraButtonsBox, this.menuButton
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.menuWidget.reorder_child_after(
|
|
||||||
this.menuButton, this.extraButtonsBox
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isMenuOnLeft = isOnLeft;
|
|
||||||
}
|
|
||||||
|
|
||||||
setMaximized(isMaximized)
|
|
||||||
{
|
|
||||||
if(this.isMaximized === isMaximized)
|
|
||||||
return;
|
|
||||||
|
|
||||||
this.maximizeWidget.icon_name = (isMaximized)
|
|
||||||
? 'window-restore-symbolic'
|
|
||||||
: 'window-maximize-symbolic';
|
|
||||||
|
|
||||||
this.isMaximized = isMaximized;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onLayoutUpdate(gtkSettings)
|
|
||||||
{
|
|
||||||
const gtkLayout = gtkSettings.gtk_decoration_layout;
|
|
||||||
|
|
||||||
this._replaceButtons(gtkLayout);
|
|
||||||
}
|
|
||||||
|
|
||||||
_replaceButtons(gtkLayout)
|
|
||||||
{
|
|
||||||
const modLayout = gtkLayout.replace(':', ',spacer,');
|
|
||||||
const layoutArr = modLayout.split(',');
|
|
||||||
|
|
||||||
let lastWidget = null;
|
|
||||||
|
|
||||||
let showMinimize = false;
|
|
||||||
let showMaximize = false;
|
|
||||||
let showClose = false;
|
|
||||||
|
|
||||||
let menuAdded = false;
|
|
||||||
let spacerAdded = false;
|
|
||||||
|
|
||||||
debug(`headerbar layout: ${modLayout}`);
|
|
||||||
|
|
||||||
for(let name of layoutArr) {
|
|
||||||
/* Menu might be named "appmenu" */
|
|
||||||
if(!menuAdded && (!name || name === 'appmenu' || name === 'icon'))
|
|
||||||
name = 'menu';
|
|
||||||
|
|
||||||
const widget = this[`${name}Widget`];
|
|
||||||
if(!widget) continue;
|
|
||||||
|
|
||||||
if(!widget.parent)
|
|
||||||
this.append(widget);
|
|
||||||
else
|
|
||||||
this.reorder_child_after(widget, lastWidget);
|
|
||||||
|
|
||||||
switch(name) {
|
|
||||||
case 'spacer':
|
|
||||||
spacerAdded = true;
|
|
||||||
break;
|
|
||||||
case 'minimize':
|
|
||||||
showMinimize = true;
|
|
||||||
break;
|
|
||||||
case 'maximize':
|
|
||||||
showMaximize = true;
|
|
||||||
break;
|
|
||||||
case 'close':
|
|
||||||
showClose = true;
|
|
||||||
break;
|
|
||||||
case 'menu':
|
|
||||||
this.setMenuOnLeft(!spacerAdded);
|
|
||||||
menuAdded = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastWidget = widget;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.minimizeWidget.visible = showMinimize;
|
|
||||||
this.maximizeWidget.visible = showMaximize;
|
|
||||||
this.closeWidget.visible = showClose;
|
|
||||||
}
|
|
||||||
|
|
||||||
_getWindowButton(name)
|
|
||||||
{
|
|
||||||
const button = new Gtk.Button({
|
|
||||||
icon_name: `window-${name}-symbolic`,
|
|
||||||
valign: Gtk.Align.CENTER,
|
|
||||||
can_focus: false,
|
|
||||||
});
|
|
||||||
button.add_css_class('osd');
|
|
||||||
button.add_css_class('circular');
|
|
||||||
|
|
||||||
if(name === 'maximize')
|
|
||||||
name = 'toggle-maximized';
|
|
||||||
|
|
||||||
button.connect('clicked',
|
|
||||||
this._onWindowButtonActivate.bind(this, name)
|
|
||||||
);
|
|
||||||
|
|
||||||
return button;
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateFloatIcon(isFloating)
|
|
||||||
{
|
|
||||||
const floatButton = this.extraButtonsBox.get_first_child();
|
|
||||||
if(!floatButton) return;
|
|
||||||
|
|
||||||
const iconName = (isFloating)
|
|
||||||
? 'pip-out-symbolic'
|
|
||||||
: 'pip-in-symbolic';
|
|
||||||
|
|
||||||
if(floatButton.icon_name !== iconName)
|
|
||||||
floatButton.icon_name = iconName;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onWindowButtonActivate(action)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
_onFloatButtonClicked(button)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
_onFullscreenButtonClicked(button)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var HeaderBarPopover = GObject.registerClass({
|
|
||||||
GTypeName: 'ClapperHeaderBarPopover',
|
|
||||||
},
|
|
||||||
class ClapperHeaderBarPopover extends Gtk.PopoverMenu
|
|
||||||
{
|
|
||||||
_init(model)
|
|
||||||
{
|
|
||||||
super._init({
|
|
||||||
menu_model: model,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.connect('map', this._onMap.bind(this));
|
|
||||||
this.connect('closed', this._onClosed.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
_onMap()
|
|
||||||
{
|
|
||||||
const { child } = this.root;
|
|
||||||
|
|
||||||
if(
|
|
||||||
!child
|
|
||||||
|| !child.player
|
|
||||||
|| !child.player.widget
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
child.revealControls();
|
|
||||||
child.isPopoverOpen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_onClosed()
|
|
||||||
{
|
|
||||||
const { child } = this.root;
|
|
||||||
|
|
||||||
if(
|
|
||||||
!child
|
|
||||||
|| !child.player
|
|
||||||
|| !child.player.widget
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
child.revealControls();
|
|
||||||
child.isPopoverOpen = false;
|
|
||||||
}
|
|
||||||
});
|
|
Reference in New Issue
Block a user