Added ags to hyprland

This commit is contained in:
2024-08-15 09:57:47 -03:00
parent 5fc32bd6b9
commit 791beef7c7
352 changed files with 22897 additions and 7 deletions

View File

@@ -0,0 +1,216 @@
import Audio from 'resource:///com/github/Aylur/ags/service/audio.js';
import Variable from 'resource:///com/github/Aylur/ags/variable.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
const { Box, Button, Icon, Label, Revealer, Scrollable, Slider, Stack } = Widget;
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
import { iconExists } from '../../.miscutils/icons.js';
const AppVolume = (stream) => Box({
className: 'sidebar-volmixer-stream spacing-h-10',
children: [
Icon({
className: 'sidebar-volmixer-stream-appicon',
vpack: 'center',
tooltipText: stream.stream.name,
setup: (self) => {
self.hook(stream, (self) => {
self.icon = stream.stream.name.toLowerCase();
})
},
}),
Box({
hexpand: true,
vpack: 'center',
vertical: true,
className: 'spacing-v-5',
children: [
Label({
xalign: 0,
maxWidthChars: 1,
truncate: 'end',
label: stream.description,
className: 'txt-small',
setup: (self) => self.hook(stream, (self) => {
self.label = `${stream.stream.name}${stream.description}`
})
}),
Slider({
drawValue: false,
hpack: 'fill',
className: 'sidebar-volmixer-stream-slider',
value: stream.volume,
min: 0, max: 1,
onChange: ({ value }) => {
stream.volume = value;
},
setup: (self) => self.hook(stream, (self) => {
self.value = stream.volume;
})
}),
// Box({
// homogeneous: true,
// className: 'test',
// children: [AnimatedSlider({
// className: 'sidebar-volmixer-stream-slider',
// value: stream.volume,
// })],
// })
]
})
]
});
const AudioDevices = (input = false) => {
const dropdownShown = Variable(false);
const DeviceStream = (stream) => Button({
child: Box({
className: 'txt spacing-h-10',
children: [
iconExists(stream.iconName) ? Icon({
className: 'txt-norm symbolic-icon',
icon: stream.iconName,
}) : MaterialIcon(input ? 'mic_external_on' : 'media_output', 'norm'),
Label({
hexpand: true,
xalign: 0,
className: 'txt-small',
truncate: 'end',
maxWidthChars: 1,
label: stream.description,
}),
],
}),
onClicked: (self) => {
if (input) Audio.microphone = stream;
else Audio.speaker = stream;
dropdownShown.value = false;
},
setup: setupCursorHover,
})
const activeDevice = Button({
onClicked: () => { dropdownShown.value = !dropdownShown.value; },
child: Box({
className: 'txt spacing-h-10',
children: [
MaterialIcon(input ? 'mic_external_on' : 'media_output', 'norm'),
Label({
hexpand: true,
xalign: 0,
className: 'txt-small',
truncate: 'end',
maxWidthChars: 1,
label: `${input ? '[In]' : '[Out]'}`,
setup: (self) => self.hook(Audio, (self) => {
self.label = `${input ? '[In]' : '[Out]'} ${input ? Audio.microphone.description : Audio.speaker.description}`;
})
}),
Label({
className: `icon-material txt-norm`,
setup: (self) => self.hook(dropdownShown, (self) => {
self.label = dropdownShown.value ? 'expand_less' : 'expand_more';
})
})
],
}),
setup: setupCursorHover,
});
const deviceSelector = Revealer({
transition: 'slide_down',
revealChild: dropdownShown.bind("value"),
transitionDuration: userOptions.animations.durationSmall,
child: Box({
vertical: true,
children: [
Box({ className: 'separator-line margin-top-5 margin-bottom-5' }),
Box({
vertical: true,
className: 'spacing-v-5 margin-top-5',
attribute: {
'updateStreams': (self) => {
const streams = input ? Audio.microphones : Audio.speakers;
self.children = streams.map(stream => DeviceStream(stream));
},
},
setup: (self) => self
.hook(Audio, self.attribute.updateStreams, 'stream-added')
.hook(Audio, self.attribute.updateStreams, 'stream-removed')
,
}),
]
})
})
return Box({
hpack: 'fill',
className: 'sidebar-volmixer-deviceselector',
vertical: true,
children: [
activeDevice,
deviceSelector,
]
})
}
export default (props) => {
const emptyContent = Box({
homogeneous: true,
children: [Box({
vertical: true,
vpack: 'center',
className: 'txt spacing-v-10',
children: [
Box({
vertical: true,
className: 'spacing-v-5 txt-subtext',
children: [
MaterialIcon('brand_awareness', 'gigantic'),
Label({ label: 'No audio source', className: 'txt-small' }),
]
}),
]
})]
});
const appList = Scrollable({
vexpand: true,
child: Box({
attribute: {
'updateStreams': (self) => {
const streams = Audio.apps;
self.children = streams.map(stream => AppVolume(stream));
},
},
vertical: true,
className: 'spacing-v-5',
setup: (self) => self
.hook(Audio, self.attribute.updateStreams, 'stream-added')
.hook(Audio, self.attribute.updateStreams, 'stream-removed')
,
})
})
const devices = Box({
vertical: true,
className: 'spacing-v-5',
children: [
AudioDevices(false),
AudioDevices(true),
]
})
const mainContent = Stack({
children: {
'empty': emptyContent,
'list': appList,
},
setup: (self) => self.hook(Audio, (self) => {
self.shown = (Audio.apps.length > 0 ? 'list' : 'empty')
}),
})
return Box({
...props,
className: 'spacing-v-5',
vertical: true,
children: [
mainContent,
devices,
]
});
}

View File

@@ -0,0 +1,160 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Bluetooth from 'resource:///com/github/Aylur/ags/service/bluetooth.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Icon, Label, Scrollable, Slider, Stack, Overlay } = Widget;
const { execAsync, exec } = Utils;
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
// can't connect: sync_problem
const USE_SYMBOLIC_ICONS = true;
const BluetoothDevice = (device) => {
// console.log(device);
const deviceIcon = Icon({
className: 'sidebar-bluetooth-appicon',
vpack: 'center',
tooltipText: device.name,
setup: (self) => self.hook(device, (self) => {
self.icon = `${device.iconName}${USE_SYMBOLIC_ICONS ? '-symbolic' : ''}`;
}),
});
const deviceStatus = Box({
hexpand: true,
vpack: 'center',
vertical: true,
children: [
Label({
xalign: 0,
maxWidthChars: 1,
truncate: 'end',
label: device.name,
className: 'txt-small',
setup: (self) => self.hook(device, (self) => {
self.label = device.name;
}),
}),
Label({
xalign: 0,
maxWidthChars: 1,
truncate: 'end',
label: device.connected ? 'Connected' : (device.paired ? 'Paired' : ''),
className: 'txt-subtext',
setup: (self) => self.hook(device, (self) => {
self.label = device.connected ? 'Connected' : (device.paired ? 'Paired' : '');
}),
}),
]
});
const deviceConnectButton = ConfigToggle({
vpack: 'center',
expandWidget: false,
desc: 'Toggle connection',
initValue: device.connected,
onChange: (self, newValue) => {
device.setConnection(newValue);
},
extraSetup: (self) => self.hook(device, (self) => {
Utils.timeout(200, () => self.enabled.value = device.connected);
}),
})
const deviceRemoveButton = Button({
vpack: 'center',
className: 'sidebar-bluetooth-device-remove',
child: MaterialIcon('delete', 'norm'),
tooltipText: 'Remove device',
setup: setupCursorHover,
onClicked: () => execAsync(['bluetoothctl', 'remove', device.address]).catch(print),
});
return Box({
className: 'sidebar-bluetooth-device spacing-h-10',
children: [
deviceIcon,
deviceStatus,
Box({
className: 'spacing-h-5',
children: [
deviceConnectButton,
deviceRemoveButton,
]
})
]
})
}
export default (props) => {
const emptyContent = Box({
homogeneous: true,
children: [Box({
vertical: true,
vpack: 'center',
className: 'txt spacing-v-10',
children: [
Box({
vertical: true,
className: 'spacing-v-5 txt-subtext',
children: [
MaterialIcon('bluetooth_disabled', 'gigantic'),
Label({ label: 'No Bluetooth devices', className: 'txt-small' }),
]
}),
]
})]
});
const deviceList = Overlay({
passThrough: true,
child: Scrollable({
vexpand: true,
child: Box({
attribute: {
'updateDevices': (self) => {
const devices = Bluetooth.devices;
self.children = devices.map(d => BluetoothDevice(d));
},
},
vertical: true,
className: 'spacing-v-5 margin-bottom-15',
setup: (self) => self
.hook(Bluetooth, self.attribute.updateDevices, 'device-added')
.hook(Bluetooth, self.attribute.updateDevices, 'device-removed')
,
})
}),
overlays: [Box({
className: 'sidebar-centermodules-scrollgradient-bottom'
})]
});
const mainContent = Stack({
children: {
'empty': emptyContent,
'list': deviceList,
},
setup: (self) => self.hook(Bluetooth, (self) => {
self.shown = (Bluetooth.devices.length > 0 ? 'list' : 'empty')
}),
})
const bottomBar = Box({
homogeneous: true,
children: [Button({
hpack: 'center',
className: 'txt-small txt sidebar-centermodules-bottombar-button',
onClicked: () => {
execAsync(['bash', '-c', userOptions.apps.bluetooth]).catch(print);
closeEverything();
},
label: 'More',
setup: setupCursorHover,
})],
})
return Box({
...props,
className: 'spacing-v-5',
vertical: true,
children: [
mainContent,
bottomBar
]
});
}

View File

@@ -0,0 +1,130 @@
const { GLib } = imports.gi;
import Hyprland from 'resource:///com/github/Aylur/ags/service/hyprland.js';
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Icon, Label, Scrollable, Slider, Stack } = Widget;
const { execAsync, exec } = Utils;
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
import { ConfigGap, ConfigSpinButton, ConfigToggle } from '../../.commonwidgets/configwidgets.js';
const HyprlandToggle = ({ icon, name, desc = null, option, enableValue = 1, disableValue = 0, extraOnChange = () => { } }) => ConfigToggle({
icon: icon,
name: name,
desc: desc,
initValue: JSON.parse(exec(`hyprctl getoption -j ${option}`))["int"] != 0,
onChange: (self, newValue) => {
execAsync(['hyprctl', 'keyword', option, `${newValue ? enableValue : disableValue}`]).catch(print);
extraOnChange(self, newValue);
}
});
const HyprlandSpinButton = ({ icon, name, desc = null, option, ...rest }) => ConfigSpinButton({
icon: icon,
name: name,
desc: desc,
initValue: Number(JSON.parse(exec(`hyprctl getoption -j ${option}`))["int"]),
onChange: (self, newValue) => {
execAsync(['hyprctl', 'keyword', option, `${newValue}`]).catch(print);
},
...rest,
});
const Subcategory = (children) => Box({
className: 'margin-left-20',
vertical: true,
children: children,
})
export default (props) => {
const ConfigSection = ({ name, children }) => Box({
vertical: true,
className: 'spacing-v-5',
children: [
Label({
hpack: 'center',
className: 'txt txt-large margin-left-10',
label: name,
}),
Box({
className: 'margin-left-10 margin-right-10',
vertical: true,
children: children,
})
]
})
const mainContent = Scrollable({
vexpand: true,
child: Box({
vertical: true,
className: 'spacing-v-10',
children: [
ConfigSection({
name: 'Effects', children: [
ConfigToggle({
icon: 'border_clear',
name: 'Transparency',
desc: '[AGS]\nMake shell elements transparent\nBlur is also recommended if you enable this',
initValue: exec(`bash -c "sed -n \'2p\' ${GLib.get_user_state_dir()}/ags/user/colormode.txt"`) == "transparent",
onChange: (self, newValue) => {
const transparency = newValue == 0 ? "opaque" : "transparent";
console.log(transparency);
execAsync([`bash`, `-c`, `mkdir -p ${GLib.get_user_state_dir()}/ags/user && sed -i "2s/.*/${transparency}/" ${GLib.get_user_state_dir()}/ags/user/colormode.txt`])
.then(execAsync(['bash', '-c', `${App.configDir}/scripts/color_generation/switchcolor.sh`]))
.catch(print);
},
}),
HyprlandToggle({ icon: 'blur_on', name: 'Blur', desc: "[Hyprland]\nEnable blur on transparent elements\nDoesn't affect performance/power consumption unless you have transparent windows.", option: "decoration:blur:enabled" }),
Subcategory([
HyprlandToggle({ icon: 'stack_off', name: 'X-ray', desc: "[Hyprland]\nMake everything behind a window/layer except the wallpaper not rendered on its blurred surface\nRecommended to improve performance (if you don't abuse transparency/blur) ", option: "decoration:blur:xray" }),
HyprlandSpinButton({ icon: 'target', name: 'Size', desc: '[Hyprland]\nAdjust the blur radius. Generally doesn\'t affect performance\nHigher = more color spread', option: 'decoration:blur:size', minValue: 1, maxValue: 1000 }),
HyprlandSpinButton({ icon: 'repeat', name: 'Passes', desc: '[Hyprland] Adjust the number of runs of the blur algorithm\nMore passes = more spread and power consumption\n4 is recommended\n2- would look weird and 6+ would look lame.', option: 'decoration:blur:passes', minValue: 1, maxValue: 10 }),
]),
ConfigGap({}),
HyprlandToggle({
icon: 'animation', name: 'Animations', desc: '[Hyprland] [GTK]\nEnable animations', option: 'animations:enabled',
extraOnChange: (self, newValue) => execAsync(['gsettings', 'set', 'org.gnome.desktop.interface', 'enable-animations', `${newValue}`])
}),
Subcategory([
ConfigSpinButton({
icon: 'clear_all',
name: 'Choreography delay',
desc: 'In milliseconds, the delay between animations of a series',
initValue: userOptions.animations.choreographyDelay,
step: 10, minValue: 0, maxValue: 1000,
onChange: (self, newValue) => {
userOptions.animations.choreographyDelay = newValue
},
})
]),
]
}),
ConfigSection({
name: 'Developer', children: [
HyprlandToggle({ icon: 'speed', name: 'Show FPS', desc: "[Hyprland]\nShow FPS overlay on top-left corner", option: "debug:overlay" }),
HyprlandToggle({ icon: 'sort', name: 'Log to stdout', desc: "[Hyprland]\nPrint LOG, ERR, WARN, etc. messages to the console", option: "debug:enable_stdout_logs" }),
HyprlandToggle({ icon: 'motion_sensor_active', name: 'Damage tracking', desc: "[Hyprland]\nEnable damage tracking\nGenerally, leave it on.\nTurn off only when a shader doesn't work", option: "debug:damage_tracking", enableValue: 2 }),
HyprlandToggle({ icon: 'destruction', name: 'Damage blink', desc: "[Hyprland] [Epilepsy warning!]\nShow screen damage flashes", option: "debug:damage_blink" }),
]
}),
]
})
});
const footNote = Box({
homogeneous: true,
children: [Label({
hpack: 'center',
className: 'txt txt-italic txt-subtext margin-5',
label: 'Not all changes are saved',
})]
})
return Box({
...props,
className: 'spacing-v-5',
vertical: true,
children: [
mainContent,
footNote,
]
});
}

View File

@@ -0,0 +1,176 @@
// This file is for the notification list on the sidebar
// For the popup notifications, see onscreendisplay.js
// The actual widget for each single notification is in ags/modules/.commonwidgets/notification.js
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Notifications from 'resource:///com/github/Aylur/ags/service/notifications.js';
const { Box, Button, Label, Revealer, Scrollable, Stack } = Widget;
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
import Notification from '../../.commonwidgets/notification.js';
import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
export default (props) => {
const notifEmptyContent = Box({
homogeneous: true,
children: [Box({
vertical: true,
vpack: 'center',
className: 'txt spacing-v-10',
children: [
Box({
vertical: true,
className: 'spacing-v-5 txt-subtext',
children: [
MaterialIcon('notifications_active', 'gigantic'),
Label({ label: 'No notifications', className: 'txt-small' }),
]
}),
]
})]
});
const notificationList = Box({
vertical: true,
vpack: 'start',
className: 'spacing-v-5-revealer',
setup: (self) => self
.hook(Notifications, (box, id) => {
if (box.get_children().length == 0) { // On init there's no notif, or 1st notif
Notifications.notifications
.forEach(n => {
box.pack_end(Notification({
notifObject: n,
isPopup: false,
}), false, false, 0)
});
box.show_all();
return;
}
// 2nd or later notif
const notif = Notifications.getNotification(id);
const NewNotif = Notification({
notifObject: notif,
isPopup: false,
});
if (NewNotif) {
box.pack_end(NewNotif, false, false, 0);
box.show_all();
}
}, 'notified')
.hook(Notifications, (box, id) => {
if (!id) return;
for (const ch of box.children) {
if (ch._id === id) {
ch.attribute.destroyWithAnims();
}
}
}, 'closed')
,
});
const ListActionButton = (icon, name, action) => Button({
className: 'sidebar-centermodules-bottombar-button',
onClicked: action,
child: Box({
hpack: 'center',
className: 'spacing-h-5',
children: [
MaterialIcon(icon, 'norm'),
Label({
className: 'txt-small',
label: name,
})
]
}),
setup: setupCursorHover,
});
const silenceButton = ListActionButton('notifications_paused', 'Silence', (self) => {
Notifications.dnd = !Notifications.dnd;
self.toggleClassName('notif-listaction-btn-enabled', Notifications.dnd);
});
// const silenceToggle = ConfigToggle({
// expandWidget: false,
// icon: 'do_not_disturb_on',
// name: 'Do Not Disturb',
// initValue: false,
// onChange: (self, newValue) => {
// Notifications.dnd = newValue;
// },
// })
const clearButton = Revealer({
transition: 'slide_right',
transitionDuration: userOptions.animations.durationSmall,
setup: (self) => self.hook(Notifications, (self) => {
self.revealChild = Notifications.notifications.length > 0;
}),
child: ListActionButton('clear_all', 'Clear', () => {
Notifications.clear();
const kids = notificationList.get_children();
for (let i = 0; i < kids.length; i++) {
const kid = kids[i];
Utils.timeout(userOptions.animations.choreographyDelay * i, () => kid.attribute.destroyWithAnims());
}
})
})
const notifCount = Label({
attribute: {
updateCount: (self) => {
const count = Notifications.notifications.length;
if (count > 0) self.label = `${count} notifications`;
else self.label = '';
},
},
hexpand: true,
xalign: 0,
className: 'txt-small margin-left-10',
label: `${Notifications.notifications.length}`,
setup: (self) => self
.hook(Notifications, (box, id) => self.attribute.updateCount(self), 'notified')
.hook(Notifications, (box, id) => self.attribute.updateCount(self), 'dismissed')
.hook(Notifications, (box, id) => self.attribute.updateCount(self), 'closed')
,
});
const listTitle = Box({
vpack: 'start',
className: 'txt spacing-h-5',
children: [
notifCount,
silenceButton,
// silenceToggle,
// Box({ hexpand: true }),
clearButton,
]
});
const notifList = Scrollable({
hexpand: true,
hscroll: 'never',
vscroll: 'automatic',
child: Box({
vexpand: true,
homogeneous: true,
children: [notificationList],
}),
setup: (self) => {
const vScrollbar = self.get_vscrollbar();
vScrollbar.get_style_context().add_class('sidebar-scrollbar');
}
});
const listContents = Stack({
transition: 'crossfade',
transitionDuration: userOptions.animations.durationLarge,
children: {
'empty': notifEmptyContent,
'list': notifList,
},
setup: (self) => self.hook(Notifications, (self) => {
self.shown = (Notifications.notifications.length > 0 ? 'list' : 'empty')
}),
});
return Box({
...props,
className: 'spacing-v-5',
vertical: true,
children: [
listContents,
listTitle,
]
});
}

View File

@@ -0,0 +1,216 @@
import Widget from 'resource:///com/github/Aylur/ags/widget.js';
import Network from "resource:///com/github/Aylur/ags/service/network.js";
import * as Utils from 'resource:///com/github/Aylur/ags/utils.js';
const { Box, Button, Entry, Icon, Label, Revealer, Scrollable, Slider, Stack, Overlay } = Widget;
const { execAsync, exec } = Utils;
import { MaterialIcon } from '../../.commonwidgets/materialicon.js';
import { setupCursorHover } from '../../.widgetutils/cursorhover.js';
import { ConfigToggle } from '../../.commonwidgets/configwidgets.js';
const MATERIAL_SYMBOL_SIGNAL_STRENGTH = {
'network-wireless-signal-excellent-symbolic': "signal_wifi_4_bar",
'network-wireless-signal-good-symbolic': "network_wifi_3_bar",
'network-wireless-signal-ok-symbolic': "network_wifi_2_bar",
'network-wireless-signal-weak-symbolic': "network_wifi_1_bar",
'network-wireless-signal-none-symbolic': "signal_wifi_0_bar",
}
let connectAttempt = '';
const WifiNetwork = (accessPoint) => {
const networkStrength = MaterialIcon(MATERIAL_SYMBOL_SIGNAL_STRENGTH[accessPoint.iconName], 'hugerass')
const networkName = Box({
vertical: true,
children: [
Label({
hpack: 'start',
label: accessPoint.ssid
}),
accessPoint.active ? Label({
hpack: 'start',
className: 'txt-smaller txt-subtext',
label: "Selected",
}) : null,
]
});
return Button({
onClicked: accessPoint.active ? () => { } : () => execAsync(`nmcli device wifi connect ${accessPoint.bssid}`)
// .catch(e => {
// Utils.notify({
// summary: "Network",
// body: e,
// actions: { "Open network manager": () => execAsync("nm-connection-editor").catch(print) }
// });
// })
.catch(print),
child: Box({
className: 'sidebar-wifinetworks-network spacing-h-10',
children: [
networkStrength,
networkName,
Box({ hexpand: true }),
accessPoint.active ? MaterialIcon('check', 'large') : null,
],
}),
setup: accessPoint.active ? () => { } : setupCursorHover,
})
}
const CurrentNetwork = () => {
let authLock = false;
// console.log(Network.wifi);
const bottomSeparator = Box({
className: 'separator-line',
});
const networkName = Box({
vertical: true,
hexpand: true,
children: [
Label({
hpack: 'start',
className: 'txt-smaller txt-subtext',
label: "Current network",
}),
Label({
hpack: 'start',
label: Network.wifi?.ssid,
setup: (self) => self.hook(Network, (self) => {
if (authLock) return;
self.label = Network.wifi?.ssid;
}),
}),
]
});
const networkStatus = Box({
children: [Label({
vpack: 'center',
className: 'txt-subtext',
setup: (self) => self.hook(Network, (self) => {
if (authLock) return;
self.label = Network.wifi.state;
}),
})]
})
const networkAuth = Revealer({
transition: 'slide_down',
transitionDuration: userOptions.animations.durationLarge,
child: Box({
className: 'margin-top-10 spacing-v-5',
vertical: true,
children: [
Label({
className: 'margin-left-5',
hpack: 'start',
label: "Authentication",
}),
Entry({
className: 'sidebar-wifinetworks-auth-entry',
visibility: false, // Password dots
onAccept: (self) => {
authLock = false;
networkAuth.revealChild = false;
execAsync(`nmcli device wifi connect '${connectAttempt}' password '${self.text}'`)
.catch(print);
}
})
]
}),
setup: (self) => self.hook(Network, (self) => {
if (Network.wifi.state == 'failed' || Network.wifi.state == 'need_auth') {
authLock = true;
connectAttempt = Network.wifi.ssid;
self.revealChild = true;
}
}),
});
const actualContent = Box({
vertical: true,
className: 'spacing-v-10',
children: [
Box({
className: 'sidebar-wifinetworks-network',
vertical: true,
children: [
Box({
className: 'spacing-h-10',
children: [
MaterialIcon('language', 'hugerass'),
networkName,
networkStatus,
]
}),
networkAuth,
]
}),
bottomSeparator,
]
});
return Box({
vertical: true,
children: [Revealer({
transition: 'slide_down',
transitionDuration: userOptions.animations.durationLarge,
revealChild: Network.wifi,
child: actualContent,
})]
})
}
export default (props) => {
const networkList = Box({
vertical: true,
className: 'spacing-v-10',
children: [Overlay({
passThrough: true,
child: Scrollable({
vexpand: true,
child: Box({
attribute: {
'updateNetworks': (self) => {
const accessPoints = Network.wifi?.access_points || [];
self.children = Object.values(accessPoints.reduce((a, accessPoint) => {
// Only keep max strength networks by ssid
if (!a[accessPoint.ssid] || a[accessPoint.ssid].strength < accessPoint.strength) {
a[accessPoint.ssid] = accessPoint;
a[accessPoint.ssid].active |= accessPoint.active;
}
return a;
}, {})).map(n => WifiNetwork(n));
},
},
vertical: true,
className: 'spacing-v-5 margin-bottom-15',
setup: (self) => self.hook(Network, self.attribute.updateNetworks),
})
}),
overlays: [Box({
className: 'sidebar-centermodules-scrollgradient-bottom'
})]
})]
});
const bottomBar = Box({
homogeneous: true,
children: [Button({
hpack: 'center',
className: 'txt-small txt sidebar-centermodules-bottombar-button',
onClicked: () => {
execAsync(['bash', '-c', userOptions.apps.network]).catch(print);
closeEverything();
},
label: 'More',
setup: setupCursorHover,
})],
})
return Box({
...props,
className: 'spacing-v-10',
vertical: true,
children: [
CurrentNetwork(),
networkList,
bottomBar,
]
});
}