import App from 'resource:///com/github/Aylur/ags/app.js'; import Network from 'resource:///com/github/Aylur/ags/service/network.js'; import Variable from 'resource:///com/github/Aylur/ags/variable.js'; import { Box, Icon, Label, ListBox, Overlay, Revealer, Scrollable } from 'resource:///com/github/Aylur/ags/widget.js'; import { execAsync } from 'resource:///com/github/Aylur/ags/utils.js'; import CursorBox from '../misc/cursorbox.ts'; const SCROLL_THRESH_H = 200; const SCROLL_THRESH_N = 7; // Types import AgsBox from 'types/widgets/box.ts'; import AgsScrollable from 'types/widgets/scrollable.ts'; type ListBoxRow = typeof imports.gi.Gtk.ListBoxRow; type APType = { bssid: string address: string lastSeen: number ssid: string active: boolean strength: number iconName: string }; const AccessPoint = (ap: APType) => { const widget = Box({ class_name: 'menu-item', attribute: { ap: Variable(ap), }, }); const child = Box({ hexpand: true, children: [ Icon().hook(widget.attribute.ap, (self) => { self.icon = widget.attribute.ap.value.iconName; }), Label().hook(widget.attribute.ap, (self) => { self.label = widget.attribute.ap.value.ssid || ''; }), Icon({ icon: 'object-select-symbolic', hexpand: true, hpack: 'end', setup: (self) => { self.hook(Network, () => { self.setCss( `opacity: ${ widget.attribute.ap.value.ssid === Network.wifi.ssid ? '1' : '0' }; `, ); }); }, }), ], }); widget.add(Revealer({ reveal_child: true, transition: 'slide_down', child: CursorBox({ on_primary_click_release: () => { execAsync(`nmcli device wifi connect ${widget.attribute.ap.value.bssid}`).catch(print); }, child, }), })); return widget; }; export const NetworkMenu = () => { const APList = new Map(); const topArrow = Revealer({ transition: 'slide_down', child: Icon({ icon: `${App.configDir }/icons/down-large.svg`, class_name: 'scrolled-indicator', size: 16, css: '-gtk-icon-transform: rotate(180deg);', }), }); const bottomArrow = Revealer({ transition: 'slide_up', child: Icon({ icon: `${App.configDir }/icons/down-large.svg`, class_name: 'scrolled-indicator', size: 16, }), }); return Overlay({ pass_through: true, overlays: [ Box({ vpack: 'start', hpack: 'center', css: 'margin-top: 12px', children: [topArrow], }), Box({ vpack: 'end', hpack: 'center', css: 'margin-bottom: 12px', children: [bottomArrow], }), ], child: Box({ class_name: 'menu', child: Scrollable({ hscroll: 'never', vscroll: 'never', setup: (self) => { self.on('edge-reached', (_, pos) => { // Manage scroll indicators if (pos === 2) { topArrow.reveal_child = false; bottomArrow.reveal_child = true; } else if (pos === 3) { topArrow.reveal_child = true; bottomArrow.reveal_child = false; } }); }, child: ListBox({ setup: (self) => { self.set_sort_func((a, b) => { const bState = (b.get_children()[0] as AgsBox) .attribute.ap.value.strength; const aState = (a.get_children()[0] as AgsBox) .attribute.ap.value.strength; return bState - aState; }); self.hook(Network, () => { // Add missing APs const currentAPs = Network.wifi ?.access_points as Array; currentAPs.forEach((ap) => { if (ap.ssid !== 'Unknown') { if (APList.has(ap.ssid)) { const accesPoint = APList.get(ap.ssid) .attribute.ap.value; if (accesPoint.strength < ap.strength) { APList.get(ap.ssid).attribute .ap.value = ap; } } else { APList.set(ap.ssid, AccessPoint(ap)); self.add(APList.get(ap.ssid)); self.show_all(); } } }); // Delete ones that don't exist anymore const difference = Array.from(APList.keys()) .filter((ssid) => !Network.wifi.access_points .find((ap) => ap.ssid === ssid) && ssid !== 'Unknown'); difference.forEach((ssid) => { const apWidget = APList.get(ssid); if (apWidget) { if (apWidget.toDestroy) { apWidget.get_parent().destroy(); APList.delete(ssid); } else { apWidget.child.reveal_child = false; apWidget.toDestroy = true; } } }); // Start scrolling after a specified height // is reached by the children const height = Math.max( self.get_parent()?.get_allocated_height() || 0, SCROLL_THRESH_H, ); const scroll = (self.get_parent() as ListBoxRow) ?.get_parent() as AgsScrollable; if (scroll) { const n_child = self.get_children().length; if (n_child > SCROLL_THRESH_N) { scroll.vscroll = 'always'; scroll.setCss(`min-height: ${height}px;`); // Make bottom scroll indicator appear only // when first getting overflowing children if (!(bottomArrow.reveal_child === true || topArrow.reveal_child === true)) { bottomArrow.reveal_child = true; } } else { scroll.vscroll = 'never'; scroll.setCss(''); topArrow.reveal_child = false; bottomArrow.reveal_child = false; } } // Trigger sort_func (self.get_children() as Array) .forEach((ch) => { ch.changed(); }); }); }, }), }), }), }); };