diff --git a/build/build.js b/build/build.js
index d775909..10218bc 100644
--- a/build/build.js
+++ b/build/build.js
@@ -6,6 +6,10 @@ const os = require( 'os' );
const render = require( './render' );
const { treeWalker } = require('./util');
+// TODO: Better comments
+// TODO: Split up configs and buildables
+// TODO: Make user choose if to use borders or not (otherwise border colour = background colour)
+
// Prompt user to select a wallpaper (if no path is passed as argument)
const wallpapers = treeWalker( path.join( os.homedir(), '/NextCloud/Wallpapers' ), '*' );
diff --git a/config/astal/app.ts b/config/astal/app.ts
index b0f7fed..d711b7c 100644
--- a/config/astal/app.ts
+++ b/config/astal/app.ts
@@ -2,7 +2,7 @@ import { App } from "astal/gtk4"
import style from "./style.scss"
import notifications from "./components/notifications/handler";
-// import Bar from "./components/bar/ui/Bar";
+import Bar from "./components/bar/ui/Bar";
App.start({
instanceName: "runner",
@@ -10,7 +10,7 @@ App.start({
main() {
notifications.startNotificationHandler( App.get_monitors()[0] );
// TODO: Monitor handling
- // Bar.Bar( App.get_monitors()[0] );
+ Bar.Bar( App.get_monitors()[0] );
},
requestHandler(request, res) {
const args = request.trimStart().split( ' ' );
@@ -19,7 +19,7 @@ App.start({
if ( args[ 0 ] === 'notifier' ) {
res( notifications.cliHandler( args ) );
} else if ( args[ 0 ] === 'bar' ) {
- // res( Bar.cliHandler( args ) );
+ res( Bar.cliHandler( args ) );
}
},
})
diff --git a/config/astal/components/bar/ui/Bar.tsx b/config/astal/components/bar/ui/Bar.tsx
index 7c9c7da..8970216 100644
--- a/config/astal/components/bar/ui/Bar.tsx
+++ b/config/astal/components/bar/ui/Bar.tsx
@@ -1,4 +1,4 @@
-import { Astal, Gdk, Gtk } from "astal/gtk3";
+import { App, Astal, Gdk, Gtk } from "astal/gtk4";
import Hyprland from "./modules/Hyprland";
import Calendar from "./modules/Calendar";
import QuickView from "./modules/QuickView";
@@ -11,19 +11,23 @@ const Bar = (gdkmonitor: Gdk.Monitor) => {
-
-
+ anchor={TOP | LEFT | RIGHT}
+ visible
+ application={App}
+ child={
+
+
-
+
+ }>
);
}
diff --git a/config/astal/components/bar/ui/QuickActions.tsx b/config/astal/components/bar/ui/QuickActions.tsx
deleted file mode 100644
index 052ce48..0000000
--- a/config/astal/components/bar/ui/QuickActions.tsx
+++ /dev/null
@@ -1,76 +0,0 @@
-import Gtk from "gi://Gtk";
-import AstalNetwork from "gi://AstalNetwork";
-import AstalBluetooth from "gi://AstalBluetooth";
-import { exec } from "gi://GLib";
-
-let quickActionsMenu;
-
-// Toggle WiFi connection
-function toggleWiFi() {
- const network = AstalNetwork.get_default();
- const wifi = network.get_wifi()!;
- const state = wifi.get_state();
-
- if (state === AstalNetwork.WifiState.DISCONNECTED) {
- wifi.connect();
- } else {
- wifi.disconnect();
- }
-}
-
-// Toggle Bluetooth power state
-function toggleBluetooth() {
- const bluetooth = AstalBluetooth.get_default();
- const adapter = bluetooth.get_adapter();
- const powered = adapter?.get_powered();
-
- adapter?.set_powered(!powered);
-}
-
-// Adjust volume or microphone (opens pavucontrol)
-function adjustVolume() {
- exec("pavucontrol");
-}
-
-function adjustMic() {
- exec("pavucontrol");
-}
-
-// Power menu
-function showPowerMenu() {
- exec("power-menu");
-}
-
-// Show WiFi network picker
-function pickWiFi() {
- const wifi = AstalNetwork.get_default().get_wifi();
- wifi.show_picker();
-}
-
-// Show Bluetooth device picker
-function pickBluetooth() {
- const bluetooth = AstalBluetooth.get_default();
- bluetooth.show_picker();
-}
-
-// Create menu using JSX
-function createQuickActionsMenu() {
- quickActionsMenu = (
-
- );
-
- return quickActionsMenu;
-}
-
-// Export the function
-export { createQuickActionsMenu };
diff --git a/config/astal/components/bar/ui/QuickActions/QuickActions.tsx b/config/astal/components/bar/ui/QuickActions/QuickActions.tsx
new file mode 100644
index 0000000..d08cec9
--- /dev/null
+++ b/config/astal/components/bar/ui/QuickActions/QuickActions.tsx
@@ -0,0 +1,31 @@
+import { Astal, App } from "astal/gtk4";
+import PowerProfiles from "gi://AstalPowerProfiles";
+import { Variable } from "astal";
+import { Sliders } from "./modules/Sliders";
+import { Toggles } from "./modules/Toggles";
+import { PowerProfileBox } from "./modules/PowerProfileBox";
+import { BatteryBox } from "./modules/BatteryBox";
+
+export default function QuickActions() {
+ const powerprofiles = PowerProfiles.get_default();
+ const hasProfiles = powerprofiles?.get_profiles()?.length > 0;
+ const { TOP, RIGHT } = Astal.WindowAnchor;
+ const visible = Variable(false);
+ return (
+
+
+
+ {hasProfiles && }
+
+
+
+
+ );
+}
diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking/Networking.tsx b/config/astal/components/bar/ui/QuickActions/modules/Networking/Networking.tsx
new file mode 100644
index 0000000..0b6acd0
--- /dev/null
+++ b/config/astal/components/bar/ui/QuickActions/modules/Networking/Networking.tsx
@@ -0,0 +1,221 @@
+import { execAsync, bind } from "astal";
+import Network from "gi://AstalNetwork";
+import { App, Gtk } from "astal/gtk4";
+import { NetworkItem } from "./modules/NetworkItem";
+import { PasswordDialog } from "./modules/PasswordDialog";
+import {
+ availableNetworks,
+ savedNetworks,
+ activeNetwork,
+ showPasswordDialog,
+ scanNetworks,
+ getSavedNetworks,
+ disconnectNetwork,
+ forgetNetwork,
+ isExpanded,
+ refreshIntervalId,
+} from "./networkinghelper";
+
+// Main WiFi Box component
+export const WiFiBox = () => {
+ const network = Network.get_default();
+
+ // Initial scan when component is first used
+ setTimeout(() => {
+ scanNetworks();
+ getSavedNetworks();
+ }, 100);
+
+ return (
+
+ {/* WiFi Toggle Header */}
+
+
+
+
+
+ {/* Networks List Revealer */}
+ {
+ const clearScanInterval = () => {
+ if (refreshIntervalId.get()) {
+ clearInterval( parseInt( '' + refreshIntervalId.get() ));
+ refreshIntervalId.set(null);
+ }
+ };
+ bind(isExpanded).subscribe((expanded) => {
+ // Clear existing interval
+ clearScanInterval();
+
+ if (expanded) {
+ // Scan networks
+ network.wifi?.scan();
+
+ // Set up new interval if WiFi is enabled
+ if (network.wifi?.enabled) {
+ refreshIntervalId.set(
+ setInterval(() => {
+ scanNetworks();
+ getSavedNetworks();
+ print("updated");
+ }, 10000),
+ );
+ }
+ } else {
+ // Apply revealer bug fix when collapsed
+ App.toggle_window("system-menu");
+ App.toggle_window("system-menu");
+ }
+ });
+
+ // Monitor window toggling
+ const windowListener = App.connect("window-toggled", (_, window) => {
+ if (window.name === "system-menu" && isExpanded.get()) {
+ isExpanded.set(false);
+ }
+ });
+
+ // Clean up resources when component is destroyed
+ return () => {
+ App.disconnect(windowListener);
+ clearScanInterval();
+ };
+ }}
+ >
+
+ v )}>
+
+
+
+
+
+
+
+ );
+};
diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking/README.md b/config/astal/components/bar/ui/QuickActions/modules/Networking/README.md
new file mode 100644
index 0000000..ad95173
--- /dev/null
+++ b/config/astal/components/bar/ui/QuickActions/modules/Networking/README.md
@@ -0,0 +1,2 @@
+# Source
+This is a modified version from [MatShell](https://github.com/Neurian/matshell)
diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking/network.d.ts b/config/astal/components/bar/ui/QuickActions/modules/Networking/network.d.ts
new file mode 100644
index 0000000..4be5e37
--- /dev/null
+++ b/config/astal/components/bar/ui/QuickActions/modules/Networking/network.d.ts
@@ -0,0 +1,14 @@
+import AstalNetwork from "gi://AstalNetwork?version=0.1";
+
+interface CurrentWiFi {
+ ssid: string;
+ strength: number;
+ secured: boolean;
+}
+
+interface WiFiDetails extends CurrentWiFi {
+ active: boolean;
+ accessPoint: AstalNetwork.AccessPoint;
+ iconName: string;
+}
+
diff --git a/config/astal/components/bar/ui/QuickActions/modules/Networking/networkinghelper.ts b/config/astal/components/bar/ui/QuickActions/modules/Networking/networkinghelper.ts
new file mode 100644
index 0000000..b4bd6aa
--- /dev/null
+++ b/config/astal/components/bar/ui/QuickActions/modules/Networking/networkinghelper.ts
@@ -0,0 +1,161 @@
+// From https://github.com/Neurarian/matshell/blob/master/utils/wifi.ts
+
+import { execAsync, Variable } from "astal";
+import Network from "gi://AstalNetwork";
+import { CurrentWiFi, WiFiDetails } from "./network";
+
+// State trackers
+export const availableNetworks: Variable = Variable([]);
+export const savedNetworks: Variable = Variable([]);
+export const activeNetwork: Variable = Variable(null);
+export const isConnecting: Variable = Variable(false);
+export const showPasswordDialog: Variable = Variable(false);
+export const errorMessage: Variable = Variable("");
+export const isExpanded: Variable = Variable(false);
+export const passwordInput: Variable = Variable("");
+export const selectedNetwork: Variable = Variable(null);
+export const refreshIntervalId: Variable<
+ number | null | ReturnType
+> = Variable(null);
+
+// Function to scan for available networks
+export const scanNetworks = () => {
+ const network = Network.get_default();
+ if (network && network.wifi) {
+ network.wifi.scan();
+
+ // Get available networks from access points
+ const networks: WiFiDetails[] = network.wifi.accessPoints
+ .map(ap => ({
+ ssid: ap.ssid,
+ strength: ap.strength,
+ secured: ap.flags !== 0,
+ active: network.wifi.activeAccessPoint?.ssid === ap.ssid,
+ accessPoint: ap,
+ iconName: ap.iconName,
+ }))
+ .filter(n => n.ssid);
+
+ // Sort by signal strength
+ networks.sort((a, b) => b.strength - a.strength);
+
+ // Remove duplicates (same SSID)
+ const uniqueNetworks: WiFiDetails[] = [];
+ const seen = new Set();
+ networks.forEach(network => {
+ if (!seen.has(network.ssid)) {
+ seen.add(network.ssid);
+ uniqueNetworks.push(network);
+ }
+ });
+
+ availableNetworks.set(uniqueNetworks);
+
+ // Update active network
+ if (network.wifi.activeAccessPoint) {
+ activeNetwork.set({
+ ssid: network.wifi.activeAccessPoint.ssid,
+ strength: network.wifi.activeAccessPoint.strength,
+ secured: network.wifi.activeAccessPoint.flags !== 0,
+ });
+ } else {
+ activeNetwork.set(null);
+ }
+ }
+};
+
+// Function to list saved networks
+export const getSavedNetworks = () => {
+ execAsync(["bash", "-c", "nmcli -t -f NAME,TYPE connection show"])
+ .then(output => {
+ if (typeof output === "string") {
+ const savedWifiNetworks = output
+ .split("\n")
+ .filter(line => line.includes("802-11-wireless"))
+ .map(line => line.split(":")[0].trim());
+ savedNetworks.set(savedWifiNetworks);
+ }
+ })
+ .catch(error => console.error("Error fetching saved networks:", error));
+};
+
+// Function to connect to a network
+
+export const connectToNetwork = (
+ ssid: string,
+ password: null | string = null,
+) => {
+ isConnecting.set(true);
+ errorMessage.set("");
+ const network = Network.get_default();
+ const currentSsid = network.wifi.ssid;
+
+ // Function to perform the actual connection
+ const performConnection = () => {
+ let command = "";
+ if (password) {
+ // Connect with password
+ command = `echo '${password}' | nmcli device wifi connect "${ssid}" --ask`;
+ } else {
+ // Connect without password (saved or open network)
+ command = `nmcli connection up "${ssid}" || nmcli device wifi connect "${ssid}"`;
+ }
+
+ execAsync(["bash", "-c", command])
+ .then(() => {
+ showPasswordDialog.set(false);
+ isConnecting.set(false);
+ scanNetworks(); // Refresh network list
+ })
+ .catch(error => {
+ console.error("Connection error:", error);
+ errorMessage.set("Failed to connect. Check password.");
+ isConnecting.set(false);
+ });
+ };
+
+ // If already connected to a network, disconnect first
+ if (currentSsid && currentSsid !== ssid) {
+ console.log(
+ `Disconnecting from ${currentSsid} before connecting to ${ssid}`,
+ );
+ execAsync(["bash", "-c", `nmcli connection down "${currentSsid}"`])
+ .then(() => {
+ // Wait a moment for the disconnection to complete fully
+ setTimeout(() => {
+ performConnection();
+ }, 500); // 500ms delay for clean disconnection
+ })
+ .catch(error => {
+ console.error("Disconnect error:", error);
+ // Continue with connection attempt even if disconnect fails
+ performConnection();
+ });
+ } else {
+ // No active connection or connecting to same network (reconnect case)
+ performConnection();
+ }
+};
+
+// Function to disconnect from a network
+export const disconnectNetwork = (ssid: string) => {
+ execAsync(["bash", "-c", `nmcli connection down "${ssid}"`])
+ .then(() => {
+ scanNetworks(); // Refresh network list
+ })
+ .catch(error => {
+ console.error("Disconnect error:", error);
+ });
+};
+
+// Function to forget a saved network
+export const forgetNetwork = (ssid: string) => {
+ execAsync(["bash", "-c", `nmcli connection delete "${ssid}"`])
+ .then(() => {
+ getSavedNetworks(); // Refresh saved networks list
+ scanNetworks(); // Refresh network list
+ })
+ .catch(error => {
+ console.error("Forget network error:", error);
+ });
+};
diff --git a/config/astal/components/bar/ui/quickactions.scss b/config/astal/components/bar/ui/QuickActions/quickactions.scss
similarity index 100%
rename from config/astal/components/bar/ui/quickactions.scss
rename to config/astal/components/bar/ui/QuickActions/quickactions.scss
diff --git a/config/astal/components/bar/ui/modules/Calendar.tsx b/config/astal/components/bar/ui/modules/Calendar.tsx
index 9784b95..abe16c7 100644
--- a/config/astal/components/bar/ui/modules/Calendar.tsx
+++ b/config/astal/components/bar/ui/modules/Calendar.tsx
@@ -1,14 +1,16 @@
import { GLib, Variable } from "astal"
+import { Gtk } from "astal/gtk4"
const Time = ({ format = "%a, %e.%m %H:%M:%S" }) => {
const time = Variable("").poll(1000, () =>
GLib.DateTime.new_now_local().format(format)!)
- return time.drop()}
- label={time()}
- />
+ return
+ time.drop()} label={time()}>
+
+
+
+
}
const Calendar = () => {
diff --git a/config/astal/components/bar/ui/modules/Hyprland.tsx b/config/astal/components/bar/ui/modules/Hyprland.tsx
index f1c820b..0933a78 100644
--- a/config/astal/components/bar/ui/modules/Hyprland.tsx
+++ b/config/astal/components/bar/ui/modules/Hyprland.tsx
@@ -1,38 +1,62 @@
+
import AstalTray from "gi://AstalTray";
-import { bind, Variable } from "astal";
+import { bind, GObject } from "astal";
import AstalHyprland from "gi://AstalHyprland";
import { Gtk } from "astal/gtk4";
+const SYNC = GObject.BindingFlags.SYNC_CREATE;
+
const SysTray = () => {
+ const trayBox = new Gtk.Box();
const tray = AstalTray.get_default();
- return
- {bind(tray, "items").as( items => items.map( item => (
-
- ) ) ) }
-
+ const trayItems = new Map();
+ const trayAddedHandler = tray.connect("item-added", (_, id) => {
+ const item = tray.get_item(id);
+ const popover = Gtk.PopoverMenu.new_from_model(item.menu_model);
+ const icon = new Gtk.Image();
+ const button = new Gtk.MenuButton({ popover, child: icon });
+
+ item.bind_property("gicon", icon, "gicon", SYNC);
+ popover.insert_action_group("dbusmenu", item.action_group);
+ item.connect("notify::action-group", () => {
+ popover.insert_action_group("dbusmenu", item.action_group);
+ });
+
+ trayItems.set(id, button);
+ trayBox.append(button);
+ })
+
+ const trayRemovedHandler = tray.connect("item-removed", (_, id) => {
+ const button = trayItems.get(id);
+ if (button) {
+ trayBox.remove(button);
+ button.run_dispose();
+ trayItems.delete(id);
+ }
+ });
+
+ trayBox.connect("destroy", () => {
+ tray.disconnect(trayAddedHandler);
+ tray.disconnect(trayRemovedHandler);
+ });
+
+ return trayBox;
}
const Workspace = () => {
const hypr = AstalHyprland.get_default()
- return
+ return
{bind(hypr, "workspaces").as(wss => wss
.filter(ws => !(ws.id >= -99 && ws.id <= -2)) // filter out special workspaces
.sort((a, b) => a.id - b.id)
.map(ws => (
))
)}
@@ -40,24 +64,88 @@ const Workspace = () => {
}
+/**
+ * Displays the name of the currently active window and provides a popover for
+ * displaying all available clients
+ */
const ActiveWindow = () => {
const hypr = AstalHyprland.get_default();
const focused = bind( hypr, "focusedClient" );
- let visible = Variable( false );
- const toggleOverlay = () => {
- visible.set( !visible.get() );
+ const WindowPopover = (): Gtk.Popover => {
+ // Set up boxes + Popover
+ const clients = new Map();
+ const popover = new Gtk.Popover();
+ const popoverBox = new Gtk.Box( { orientation: Gtk.Orientation.VERTICAL } );
+
+ const addClient = ( client: AstalHyprland.Client ) => {
+ const clientBox = new Gtk.Box();
+
+ // Workspace description
+ const descWS = new Gtk.Label( { label: '(WS ' } );
+
+ // Workpsace information
+ const workspace = new Gtk.Label();
+ client.workspace.bind_property( 'name', workspace, 'label', SYNC );
+
+ const windowClassDesc = new Gtk.Label( { label: ') [' } );
+
+ const windowClass = new Gtk.Label();
+ windowClass.label = client.get_initial_class();
+
+ const titleDesc = new Gtk.Label( { label: '] ' } );
+ titleDesc.set_margin_end( 2 );
+
+ const title = new Gtk.Label();
+ client.bind_property( 'title', title, 'label', SYNC );
+
+ clientBox.append( descWS );
+ clientBox.append( workspace );
+ clientBox.append( windowClassDesc );
+ clientBox.append( windowClass );
+ clientBox.append( titleDesc );
+ clientBox.append( title );
+
+ popoverBox.append( clientBox );
+
+ clients.set( client.get_address(), clientBox );
+ }
+
+ // Populate with already added clients
+ const c = hypr.get_clients();
+ for ( let index = 0; index < c.length; index++ ) {
+ addClient( c[ index ] );
+ }
+
+ hypr.connect( 'client-added', ( _, client ) => {
+ addClient( client );
+ } );
+
+ hypr.connect( 'client-removed', ( _, client ) => {
+ const c = clients.get( client );
+ if ( c ) {
+ popoverBox.remove( c );
+ c.run_dispose();
+ clients.delete( client );
+ }
+ } );
+
+ popover.set_child( popoverBox );
+ return popover;
}
+ const windowPopover = WindowPopover();
+
+ // ───────────────────────────────────────────────────────────────────
+ // Return fully assembled HyprlandFocusedClient box
+ // ───────────────────────────────────────────────────────────────────
return
-
+
- v )} name="popover-container">
-
-
+
+ { windowPopover }
}
diff --git a/config/astal/components/bar/ui/modules/QuickView.tsx b/config/astal/components/bar/ui/modules/QuickView.tsx
index 66c071a..8871d12 100644
--- a/config/astal/components/bar/ui/modules/QuickView.tsx
+++ b/config/astal/components/bar/ui/modules/QuickView.tsx
@@ -1,10 +1,10 @@
import { bind } from "astal";
-import AstalBattery from "gi://AstalBattery?version=0.1";
-import AstalBluetooth from "gi://AstalBluetooth?version=0.1";
-import AstalNetwork from "gi://AstalNetwork?version=0.1"
-import AstalWp from "gi://AstalWp?version=0.1";
+import AstalBattery from "gi://AstalBattery";
+import AstalBluetooth from "gi://AstalBluetooth";
+import AstalNetwork from "gi://AstalNetwork"
+import AstalWp from "gi://AstalWp";
import Brightness from "../../util/brightness";
-import { Gtk } from "astal/gtk3";
+import { Gtk } from "astal/gtk4";
const QuickView = () => {
return
diff --git a/config/astal/components/bar/ui/modules/SystemInfo.tsx b/config/astal/components/bar/ui/modules/SystemInfo.tsx
index 404c010..53b4646 100644
--- a/config/astal/components/bar/ui/modules/SystemInfo.tsx
+++ b/config/astal/components/bar/ui/modules/SystemInfo.tsx
@@ -18,7 +18,6 @@ const featureTest = () => {
printerr( '[ SysInfo ] Feature Test for CPU info failed. mpstat from the sysstat package missing!' );
}
- // Screen brightness... CTL might be available, but no screen controllable
// Battery... acpi might be present, but potentially no bat
}
@@ -28,8 +27,6 @@ const availableFeatures = {
cpu: true,
ram: true,
bat: true,
- net: false,
- brightness: true,
}
const sysInfoFetcher = () => {
@@ -44,17 +41,10 @@ const sysInfoFetcher = () => {
const acpi = exec( `acpi -i | grep 'Battery'` );
// TODO: Parse acpi output
}
- // if ( availableFeatures.net ) {
- // // Using ifstat or vnstat probably, get current network de
- // }
- if ( availableFeatures.brightness ) {
-
- }
}
}
const SystemInfo = () => {
-
return
}
diff --git a/config/astal/components/notifications/handler.tsx b/config/astal/components/notifications/handler.tsx
index b824f96..74972f0 100644
--- a/config/astal/components/notifications/handler.tsx
+++ b/config/astal/components/notifications/handler.tsx
@@ -7,7 +7,7 @@
*
*/
-import { Astal, Gdk } from "astal/gtk4"
+import { App, Astal, Gdk } from "astal/gtk4"
import Notifd from "gi://AstalNotifd";
import Notification from "./notifications";
import { timeout, Variable } from "astal"
@@ -31,8 +31,7 @@ interface NotificationDetails {
// │ Handler │
// ╰───────────────────────────────────────────────╯
// ───────────────────────────────────────────────────────────────────
-let ShownNotifications: number[] = [];
-const ShownNotificationsCount: Variable = Variable( 0 );
+let ShownNotifications: Variable = Variable( [] );
let Notifications: NotificationDetails[] = [];
const notifd = Notifd.get_default();
@@ -46,6 +45,9 @@ notifd.ignoreTimeout = true;
const deleteNotification = ( index: number ): void => {
hideNotification( index );
Notifications.splice( index, 1 );
+ if ( Notifications.length === 0 ) {
+ notificationMenuOpen = false;
+ }
}
// ───────────────────────────────────────────────────────────────────
@@ -133,9 +135,9 @@ const hookToNotificationDaemon = (): void => {
*/
const showNotification = ( id: number, removeAgain: boolean = true ) => {
// Add notification to UI for display
- ShownNotifications.reverse().push( id );
- ShownNotifications.reverse();
- ShownNotificationsCount.set( ShownNotifications.length );
+ const not = [...ShownNotifications.get()].reverse();
+ not.push( id );
+ ShownNotifications.set( not.reverse() );
// Set delay to remove the notification again
if ( removeAgain && Notifications[ id ].notification.get_urgency() !== Notifd.Urgency.CRITICAL ) {
@@ -154,7 +156,9 @@ const showNotification = ( id: number, removeAgain: boolean = true ) => {
*/
const hideNotification = ( id: number ) => {
if ( !notificationMenuOpen ) {
- ShownNotifications.splice( ShownNotifications.indexOf( id ), 1 );
+ const not = [...ShownNotifications.get()];
+ not.splice( not.indexOf( id ), 1 );
+ ShownNotifications.set( not );
}
}
@@ -167,10 +171,11 @@ const hideNotification = ( id: number ) => {
const openNotificationMenu = () => {
// Simply show all notifications
notificationMenuOpen = true;
- const ShownNotifications = [];
+ const not = [];
for (let index = 0; index < Notifications.length; index++) {
- ShownNotifications.push( index );
+ not.push( index );
}
+ ShownNotifications.set( not.reverse() );
}
// ───────────────────────────────────────────────────────────────────
@@ -182,9 +187,7 @@ const openNotificationMenu = () => {
const closeNotificationMenu = () => {
// Hide all notifications
notificationMenuOpen = true;
-
- ShownNotifications = [];
- ShownNotificationsCount.set( 0 );
+ ShownNotifications.set( [] );
}
// ───────────────────────────────────────────────────────────────────
@@ -193,11 +196,13 @@ const closeNotificationMenu = () => {
/**
* Toggle the notification menu (i.e. show all notifications)
*/
-const toggleNotificationMenu = () => {
+const toggleNotificationMenu = (): string => {
if ( notificationMenuOpen ) {
closeNotificationMenu();
+ return 'Toggle notification menu closed';
} else {
openNotificationMenu();
+ return 'Toggled notification menu open';
}
}
@@ -209,8 +214,9 @@ const toggleNotificationMenu = () => {
*/
const clearAllNotifications = () => {
Notifications = [];
- ShownNotifications = [];
- ShownNotificationsCount.set( 0 );
+ ShownNotifications.set( [] );
+ // TODO: Hiding for each individual deleteNotification
+ notificationMenuOpen = false;
}
// ───────────────────────────────────────────────────────────────────
@@ -220,7 +226,9 @@ const clearAllNotifications = () => {
* Delete the newest notifications
*/
const clearNewestNotifications = () => {
- ShownNotifications.splice( 0, 1 );
+ const not = [...ShownNotifications.get()];
+ not.splice( 0, 1 );
+ ShownNotifications.set( not );
Notifications.splice( Notifications.length - 1, 1 );
}
@@ -236,16 +244,16 @@ const startNotificationHandler = (gdkmonitor: Gdk.Monitor) => {
const { TOP, RIGHT } = Astal.WindowAnchor
hookToNotificationDaemon();
- const range = (n: number) => [...Array(n).keys()];
return
-
- {ShownNotificationsCount( n => range( n ).map( i => {
- print( 'Rendering' );
+ anchor={TOP | RIGHT}
+ visible={ShownNotifications( list => list.length > 0 )}
+ application={App}>
+
+ {ShownNotifications( list => list.map( i => {
// i is index in ShownNotifications array
return
} ) ) }
@@ -267,8 +275,19 @@ const cliHandler = ( args: string[] ): string => {
clearNewestNotifications();
return 'Cleared newest notification';
} else if ( args[ 1 ] == 'toggle' ) {
- toggleNotificationMenu();
- return 'Toggled notifications';
+ return toggleNotificationMenu();
+ } else if ( args[ 1 ] == 'list' ){
+ if ( Notifications.length > 0 ) {
+ let list = 'Currently unviewed notifications: ';
+ for (let index = 0; index < Notifications.length; index++) {
+ const element = Notifications[index];
+
+ list += `\n - (${element.notifdID}) ${element.notification.get_app_name()}: ${element.notification.get_summary()}`;
+ }
+ return list;
+ } else {
+ return 'No currently unviewed notifications'
+ }
} else {
return 'Unknown command!';
}
diff --git a/config/astal/components/notifications/notifications.scss b/config/astal/components/notifications/notifications.scss
index 6acbd53..3922722 100644
--- a/config/astal/components/notifications/notifications.scss
+++ b/config/astal/components/notifications/notifications.scss
@@ -14,22 +14,25 @@ window.NotificationHandler {
}
box.Notification {
- min-width: 400px;
- border-radius: 13px;
- background-color: $bg-color;
- margin: .5rem 1rem .5rem 1rem;
- box-shadow: 2px 3px 8px 0 gtkalpha(black, .4);
- border: 1pt solid gtkalpha($fg-color, .03);
- &:first-child>box {
+ &:first-child {
margin-top: 1rem;
}
- &:last-child>box {
+ &:last-child {
margin-bottom: 1rem;
}
- &.critical>box {
+ & {
+ min-width: 400px;
+ border-radius: 13px;
+ background-color: $bg-color;
+ margin: .5rem 1rem .5rem 1rem;
+ box-shadow: 2px 3px 8px 0 gtkalpha(black, .4);
+ border: 1pt solid gtkalpha($fg-color, .03);
+ }
+
+ &.critical {
border: 1pt solid gtkalpha($error, .4);
.header {
diff --git a/config/astal/components/notifications/notifications.tsx b/config/astal/components/notifications/notifications.tsx
index a3bd26f..039374c 100644
--- a/config/astal/components/notifications/notifications.tsx
+++ b/config/astal/components/notifications/notifications.tsx
@@ -24,9 +24,9 @@ const urgency = (n: Notifd.Notification) => {
}
type Props = {
- delete( id: number ): void
+ delete: ( id: number ) => void;
notification: Notifd.Notification
- id: number
+ id: number,
}
export default function Notification(props: Props) {
@@ -34,54 +34,54 @@ export default function Notification(props: Props) {
const { START, CENTER, END } = Gtk.Align
return
-
+ cssClasses={['Notification', `${urgency(n)}`]}>
+
{(n.appIcon || n.desktopEntry) ? : }
+ /> : }
-
-
+
{n.image && fileExists(n.image) ?
-
+ cssClasses={["image"]}>
+
: }
{n.image ?
-
+ cssClasses={["icon-image"]}>
+
: }
-
{n.body ? : }
- {n.get_actions().length > 0 ?
+ {n.get_actions().length > 0 ?
{n.get_actions().map(({ label, id }) => (
centerbox { */
-/* background: $bg-color; */
-/* border-radius: 10px; */
-/* margin: 8px; */
-/* } */
-/**/
-/* button { */
-/* border-radius: 8px; */
-/* margin: 2px; */
-/* } */
-/* } */
+@use './components/notifications/notifications.scss';
+$fg-color: #{"@theme_fg_color"};
+$bg-color: #{"@theme_bg_color"};
-@use './components/notifications/notifications.scss'
+window.Bar {
+ background: transparent;
+ color: $fg-color;
+ font-weight: bold;
+
+ >centerbox {
+ background: $bg-color;
+ border-radius: 10px;
+ margin: 8px;
+ }
+
+ button {
+ border-radius: 8px;
+ margin: 2px;
+ }
+}
diff --git a/prompts.md b/prompts.md
index 5fb8586..b5a2201 100644
--- a/prompts.md
+++ b/prompts.md
@@ -25,3 +25,19 @@ Now, can you also provide scss for the bar, such that:
- it is very rounded (the corners)
- it has a very sleek, but modern design, with only a single accent colour
- Hovering over something clickable uses a hover colour
+
+
+## Attempt 2
+Using AstalNetwork, can you write a UI (that can be integrated into a popover) and a corresponding backend that allows me to:
+- Turn on/off networking and WiFi
+- Select a WiFi Network
+- Some way of adding a new connection (can be a special UI or simply opens up another tool like nm-applet's network config)
+- WiFi-Auto-Connect
+- Exposes a function (or multiple) through which I can get (as a bindable porperty):
+ - Current up/down speed
+ - Connection type
+ - WiFi SSID (if applicable)
+ - WiFi Signal Strength (if applicable)
+ - Local IP
+
+I would prefer if you could split up the logic from the UI, i.e. have the logic in a separate TS file and have the UI as its own TSX file and simply import the logic