[AGS] Redo basic setup
This commit is contained in:
parent
a9c7b7d7ee
commit
78e472beb8
2
config/astal/.gitignore
vendored
Normal file
2
config/astal/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
@girs/
|
39
config/astal/app.ts
Normal file
39
config/astal/app.ts
Normal file
@ -0,0 +1,39 @@
|
||||
import { App } from "astal/gtk3"
|
||||
import style from "./style.scss"
|
||||
|
||||
import notifications from "./components/notifications/handler";
|
||||
|
||||
App.start({
|
||||
instanceName: "runner",
|
||||
css: style,
|
||||
main() {
|
||||
notifications.startNotificationHandler( 0, App.get_monitors()[0] )
|
||||
},
|
||||
requestHandler(request, res) {
|
||||
const args = request.trimStart().split( ' ' );
|
||||
|
||||
// Notifications
|
||||
if ( args[ 0 ] === 'notifier' ) {
|
||||
if ( args[ 1 ] == 'show' ) {
|
||||
notifications.openNotificationMenu( 0 );
|
||||
res( 'Showing all open notifications' );
|
||||
} else if ( args[ 1 ] == 'hide' ) {
|
||||
notifications.closeNotificationMenu( 0 );
|
||||
res( 'Hid all notifications' );
|
||||
} else if ( args[ 1 ] == 'clear' ) {
|
||||
notifications.clearAllNotifications( 0 );
|
||||
res( 'Cleared all notifications' );
|
||||
} else if ( args[ 1 ] == 'clear-newest' ) {
|
||||
notifications.clearNewestNotifications( 0 );
|
||||
res( 'Cleared newest notification' );
|
||||
} else if ( args[ 1 ] == 'toggle' ) {
|
||||
notifications.toggleNotificationMenu( 0 );
|
||||
res( 'Toggled notifications' );
|
||||
} else {
|
||||
res( 'Unknown command!' );
|
||||
}
|
||||
} else if ( args[ 0 ] === 'bar' ) {
|
||||
|
||||
}
|
||||
},
|
||||
})
|
23
config/astal/components/bar/ui/Bar.tsx
Normal file
23
config/astal/components/bar/ui/Bar.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { createQuickActionsMenu } from "./QuickActions";
|
||||
import { GLib } from "astal";
|
||||
import { Astal, Gdk, Gtk } from "astal/gtk3";
|
||||
|
||||
const Bar = (gdkmonitor: Gdk.Monitor) => {
|
||||
|
||||
return (
|
||||
<window gdkmonitor={gdkmonitor}
|
||||
cssClasses={["Bar"]}>
|
||||
<box orientation={Gtk.Orientation.HORIZONTAL} spacing={10}>
|
||||
<box>
|
||||
</box>
|
||||
<label>{windowTitle}</label>
|
||||
<box>
|
||||
<tray />
|
||||
<button icon="quickaction" menu={quickActionMenu} />
|
||||
</box>
|
||||
</box>
|
||||
</window>
|
||||
);
|
||||
}
|
||||
|
||||
export default Bar;
|
76
config/astal/components/bar/ui/QuickActions.tsx
Normal file
76
config/astal/components/bar/ui/QuickActions.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
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 = (
|
||||
<menu>
|
||||
<item label="📶 WiFi" onActivate={toggleWiFi} />
|
||||
<item label="→ Pick Network" onActivate={pickWiFi} />
|
||||
|
||||
<item label="🔵 Bluetooth" onActivate={toggleBluetooth} />
|
||||
<item label="→ Pick Device" onActivate={pickBluetooth} />
|
||||
|
||||
<item label="🔊 Volume" onActivate={adjustVolume} />
|
||||
<item label="🎙️ Microphone" onActivate={adjustMic} />
|
||||
<item label="🔌 Power" onActivate={showPowerMenu} />
|
||||
</menu>
|
||||
);
|
||||
|
||||
return quickActionsMenu;
|
||||
}
|
||||
|
||||
// Export the function
|
||||
export { createQuickActionsMenu };
|
0
config/astal/components/bar/ui/modules/Calendar.tsx
Normal file
0
config/astal/components/bar/ui/modules/Calendar.tsx
Normal file
57
config/astal/components/bar/ui/modules/Hyprland.tsx
Normal file
57
config/astal/components/bar/ui/modules/Hyprland.tsx
Normal file
@ -0,0 +1,57 @@
|
||||
import AstalTray from "gi://AstalTray";
|
||||
import { bind } from "astal";
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
|
||||
const SysTray = () => {
|
||||
const tray = AstalTray.get_default();
|
||||
|
||||
return <box className="SysTray">
|
||||
{bind(tray, "items").as( items => items.map( item => (
|
||||
<button
|
||||
tooltipMarkup={bind(item, "tooltipMarkup")}
|
||||
usePopover={false}
|
||||
actionGroup={bind(item, "actionGroup").as(ag => ["dbusmenu", ag])}
|
||||
menuModel={bind(item, "menuModel")}>
|
||||
<icon gicon={bind(item, "gicon")} />
|
||||
</button>
|
||||
) ) ) }
|
||||
</box>
|
||||
}
|
||||
|
||||
|
||||
const HyprlandWorkspace = () => {
|
||||
const hypr = AstalHyprland.get_default()
|
||||
|
||||
return <box className={"HyprlandWorkspaces"}>
|
||||
{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 => (
|
||||
<button
|
||||
className={bind(hypr, "focusedWorkspace").as(fw =>
|
||||
ws === fw ? "HyprlandFocusedWorkspace" : "")}
|
||||
onClicked={() => ws.focus()}>
|
||||
{ws.id}
|
||||
</button>
|
||||
))
|
||||
)}
|
||||
</box>
|
||||
}
|
||||
|
||||
|
||||
const HyprlandActiveWindow = () => {
|
||||
const hypr = AstalHyprland.get_default();
|
||||
const focused = bind( hypr, "focusedClient" );
|
||||
|
||||
return <box className={"HyprlandFocusedClients"} visible={focused.as(Boolean)}>
|
||||
{focused.as( client => (
|
||||
client && <label label={bind( client, "title" ).as( String )} />
|
||||
))}
|
||||
</box>
|
||||
}
|
||||
|
||||
export default {
|
||||
HyprlandWorkspace,
|
||||
HyprlandActiveWindow,
|
||||
SysTray
|
||||
}
|
37
config/astal/components/bar/ui/quickactions.scss
Normal file
37
config/astal/components/bar/ui/quickactions.scss
Normal file
@ -0,0 +1,37 @@
|
||||
@import "colors";
|
||||
|
||||
.toggle-row {
|
||||
background-color: $bg;
|
||||
border-radius: 12px;
|
||||
margin: 6px 0;
|
||||
overflow: hidden;
|
||||
border: 1px solid $border;
|
||||
|
||||
button {
|
||||
padding: 10px 16px;
|
||||
font-size: 14px;
|
||||
transition: background 0.2s ease;
|
||||
border: none;
|
||||
background: transparent;
|
||||
|
||||
&:hover {
|
||||
background-color: $hover;
|
||||
}
|
||||
}
|
||||
|
||||
.toggle {
|
||||
flex: 1;
|
||||
background-color: transparent;
|
||||
text-align: left;
|
||||
&.active {
|
||||
background-color: $accent;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.arrow {
|
||||
width: 40px;
|
||||
background-color: transparent;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
68
config/astal/components/bar/util/hyprland.ts
Normal file
68
config/astal/components/bar/util/hyprland.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
|
||||
const getAvailableWorkspaces = (): number[] => {
|
||||
const workspaces: number[] = [];
|
||||
AstalHyprland.get_default().get_workspaces().forEach( val => {
|
||||
workspaces.push( val.get_id() );
|
||||
} )
|
||||
return workspaces;
|
||||
}
|
||||
|
||||
const getCurrentWorkspaceID = (): number => {
|
||||
return AstalHyprland.get_default().get_focused_workspace().get_id();
|
||||
}
|
||||
|
||||
const getCurrentWindowTitle = (): string => {
|
||||
return AstalHyprland.get_default().get_focused_client().get_title();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the workspace ID of a window by its address
|
||||
* @param address - The address of the window
|
||||
* @returns The workspace ID
|
||||
*/
|
||||
const getWorkspaceIDOfWindowByAddress = ( address: string ): number => {
|
||||
AstalHyprland.get_default().get_clients().forEach( client => {
|
||||
if ( client.get_address() === address ) {
|
||||
return client.get_workspace().get_id();
|
||||
}
|
||||
} );
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
interface HyprlandSubscriptions {
|
||||
[key: string]: ( data: string ) => void;
|
||||
}
|
||||
const hooks: HyprlandSubscriptions = {};
|
||||
|
||||
/**
|
||||
* Add an event listener for Hyprland events.
|
||||
* @param event - A hyprland IPC event. See https://wiki.hyprland.org/IPC/. Useful events include: urgent, windowtitlev2, workspace, createworkspacev2, destroyworkspacev2, activewindowv2
|
||||
* @param cb - The callback function
|
||||
*/
|
||||
const subscribeToUpdates = ( event: string, cb: ( data: string ) => void ): void => {
|
||||
hooks[ event ] = cb;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to events. Must be called at some point if events are to be listened for
|
||||
*/
|
||||
const listen = () => {
|
||||
AstalHyprland.get_default().connect( "event", ( name: string, data: string ) => {
|
||||
if ( hooks[ name ] ) {
|
||||
hooks[ name ]( data );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
getAvailableWorkspaces,
|
||||
getCurrentWorkspaceID,
|
||||
getCurrentWindowTitle,
|
||||
getWorkspaceIDOfWindowByAddress,
|
||||
subscribeToUpdates,
|
||||
listen
|
||||
}
|
0
config/astal/components/bar/util/sysinfo.ts
Normal file
0
config/astal/components/bar/util/sysinfo.ts
Normal file
206
config/astal/components/notifications/handler.tsx
Normal file
206
config/astal/components/notifications/handler.tsx
Normal file
@ -0,0 +1,206 @@
|
||||
/*
|
||||
* dotfiles - handler.ts
|
||||
*
|
||||
* Created by Janis Hutz 03/21/2025, Licensed under the GPL V3 License
|
||||
* https://janishutz.com, development@janishutz.com
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
import { Astal, Gtk, Gdk } from "astal/gtk3"
|
||||
import Notifd from "gi://AstalNotifd";
|
||||
import Notification from "./notifications";
|
||||
import { type Subscribable } from "astal/binding";
|
||||
import { Variable, bind, timeout } from "astal"
|
||||
|
||||
// Config
|
||||
const TIMEOUT_DELAY = 5000;
|
||||
|
||||
class Notifier implements Subscribable {
|
||||
private display: Map<number, Gtk.Widget> = new Map();
|
||||
private notifications: Map<number, Notifd.Notification> = new Map();
|
||||
|
||||
private notifd: Notifd.Notifd;
|
||||
private subscriberData: Variable<Gtk.Widget[]> = Variable( [] );
|
||||
private instanceID: number;
|
||||
private menuOpen: boolean;
|
||||
|
||||
/**
|
||||
* Sets up the notifier
|
||||
*/
|
||||
constructor( id: number ) {
|
||||
this.instanceID = id;
|
||||
this.menuOpen = false;
|
||||
this.notifd = Notifd.get_default();
|
||||
this.notifd.ignoreTimeout = true;
|
||||
|
||||
this.notifd.connect( 'notified', ( _, id ) => {
|
||||
this.add( id );
|
||||
} );
|
||||
|
||||
this.notifd.connect( 'resolved', ( _, id ) => {
|
||||
this.delete( id );
|
||||
} );
|
||||
}
|
||||
|
||||
private notify () {
|
||||
this.subscriberData.set( [ ...this.display.values() ].reverse() );
|
||||
}
|
||||
|
||||
private add ( id: number ) {
|
||||
this.notifications.set( id, this.notifd.get_notification( id )! );
|
||||
this.show( id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an element on screen
|
||||
* @param id - The id of the element to be shown
|
||||
*/
|
||||
private show ( id: number ) {
|
||||
this.set( id, Notification( {
|
||||
notification: this.notifications.get( id )!,
|
||||
onHoverLost: () => {
|
||||
if ( !this.menuOpen ) {
|
||||
this.hide( id );
|
||||
}
|
||||
},
|
||||
setup: () => timeout( TIMEOUT_DELAY, () => {
|
||||
if ( !this.menuOpen ) {
|
||||
this.hide( id );
|
||||
}
|
||||
} ),
|
||||
id: id,
|
||||
delete: deleteHelper,
|
||||
instanceID: this.instanceID
|
||||
} ) )
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a selected widget to be shown
|
||||
* @param id - The id of the element to be referenced for later
|
||||
* @param widget - A GTK widget instance
|
||||
*/
|
||||
private set ( id: number, widget: Gtk.Widget ) {
|
||||
this.display.get( id )?.destroy();
|
||||
this.display.set( id, widget );
|
||||
this.notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide, not delete notification (= send to notification centre)
|
||||
* @param id - The id of the notification to hide
|
||||
*/
|
||||
private hide ( id: number ) {
|
||||
this.display.get( id )?.destroy();
|
||||
this.display.delete( id );
|
||||
this.notify();
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a notification (from notification centre too)
|
||||
* @param id - The id of the notification to hide
|
||||
*/
|
||||
delete ( id: number ) {
|
||||
this.hide( id );
|
||||
this.notifications.get( id )?.dismiss();
|
||||
this.notifications.delete( id );
|
||||
if ( this.notifications.size == 0 ) {
|
||||
this.menuOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
openNotificationMenu () {
|
||||
// Show all notifications that have not been cleared
|
||||
if ( this.notifications.size > 0 ) {
|
||||
this.menuOpen = true;
|
||||
this.notifications.forEach( ( _, id ) => {
|
||||
this.show( id );
|
||||
} )
|
||||
}
|
||||
}
|
||||
|
||||
hideNotifications () {
|
||||
this.menuOpen = false;
|
||||
this.notifications.forEach( ( _, id ) => {
|
||||
this.hide( id );
|
||||
} );
|
||||
}
|
||||
|
||||
toggleNotificationMenu () {
|
||||
if ( this.menuOpen ) {
|
||||
this.hideNotifications();
|
||||
} else {
|
||||
this.openNotificationMenu();
|
||||
}
|
||||
}
|
||||
|
||||
clearAllNotifications () {
|
||||
this.menuOpen = false;
|
||||
this.notifications.forEach( ( _, id ) => {
|
||||
this.delete( id );
|
||||
} )
|
||||
}
|
||||
|
||||
clearNewestNotification () {
|
||||
this.delete( [ ...this.notifications.keys() ][0] );
|
||||
}
|
||||
|
||||
subscribe(callback: (value: unknown) => void): () => void {
|
||||
return this.subscriberData.subscribe( callback );
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.subscriberData.get();
|
||||
}
|
||||
}
|
||||
|
||||
const notifiers: Map<number, Notifier> = new Map();
|
||||
const deleteHelper = ( id: number, instanceID: number ) => {
|
||||
notifiers.get( instanceID )?.delete( id );
|
||||
}
|
||||
|
||||
const openNotificationMenu = ( id: number ) => {
|
||||
notifiers.get( id )?.openNotificationMenu();
|
||||
}
|
||||
|
||||
const closeNotificationMenu = ( id: number ) => {
|
||||
notifiers.get( id )?.hideNotifications();
|
||||
}
|
||||
|
||||
const toggleNotificationMenu = ( id: number ) => {
|
||||
notifiers.get( id )?.toggleNotificationMenu();
|
||||
}
|
||||
|
||||
const clearAllNotifications = ( id: number ) => {
|
||||
notifiers.get( id )?.clearAllNotifications();
|
||||
}
|
||||
|
||||
const clearNewestNotifications = ( id: number ) => {
|
||||
notifiers.get( id )?.clearNewestNotification();
|
||||
}
|
||||
|
||||
const startNotificationHandler = (id: number, gdkmonitor: Gdk.Monitor) => {
|
||||
const { TOP, RIGHT } = Astal.WindowAnchor
|
||||
const notifier: Notifier = new Notifier( id );
|
||||
notifiers.set( id, notifier );
|
||||
|
||||
return <window
|
||||
className="NotificationHandler"
|
||||
gdkmonitor={gdkmonitor}
|
||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
||||
anchor={TOP | RIGHT}>
|
||||
<box vertical noImplicitDestroy>
|
||||
{bind(notifier)}
|
||||
</box>
|
||||
</window>
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
startNotificationHandler,
|
||||
openNotificationMenu,
|
||||
closeNotificationMenu,
|
||||
clearAllNotifications,
|
||||
clearNewestNotifications,
|
||||
toggleNotificationMenu
|
||||
}
|
125
config/astal/components/notifications/notifications.scss
Normal file
125
config/astal/components/notifications/notifications.scss
Normal file
@ -0,0 +1,125 @@
|
||||
@use "sass:string";
|
||||
|
||||
@function gtkalpha($c, $a) {
|
||||
@return string.unquote("alpha(#{$c},#{$a})");
|
||||
}
|
||||
|
||||
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
|
||||
$fg-color: #{"@theme_fg_color"};
|
||||
$bg-color: #{"@theme_bg_color"};
|
||||
$error: red;
|
||||
|
||||
window.NotificationPopups {
|
||||
all: unset;
|
||||
}
|
||||
|
||||
eventbox.Notification {
|
||||
|
||||
&:first-child>box {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
&:last-child>box {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
// eventboxes can not take margins so we style its inner box instead
|
||||
>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>box {
|
||||
border: 1pt solid gtkalpha($error, .4);
|
||||
|
||||
.header {
|
||||
|
||||
.app-name {
|
||||
color: gtkalpha($error, .8);
|
||||
|
||||
}
|
||||
|
||||
.app-icon {
|
||||
color: gtkalpha($error, .6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: .5rem;
|
||||
color: gtkalpha($fg-color, 0.5);
|
||||
|
||||
.app-icon {
|
||||
margin: 0 .4rem;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
margin-right: .3rem;
|
||||
font-weight: bold;
|
||||
|
||||
&:first-child {
|
||||
margin-left: .4rem;
|
||||
}
|
||||
}
|
||||
|
||||
.time {
|
||||
margin: 0 .4rem;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: .2rem;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
separator {
|
||||
margin: 0 .4rem;
|
||||
background-color: gtkalpha($fg-color, .1);
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 1rem;
|
||||
margin-top: .5rem;
|
||||
|
||||
.summary {
|
||||
font-size: 1.2em;
|
||||
color: $fg-color;
|
||||
}
|
||||
|
||||
.body {
|
||||
color: gtkalpha($fg-color, 0.8);
|
||||
}
|
||||
|
||||
.image {
|
||||
border: 1px solid gtkalpha($fg-color, .02);
|
||||
margin-right: .5rem;
|
||||
border-radius: 9px;
|
||||
min-width: 100px;
|
||||
min-height: 100px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
margin: 1rem;
|
||||
margin-top: 0;
|
||||
|
||||
button {
|
||||
margin: 0 .3rem;
|
||||
|
||||
&:first-child {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
112
config/astal/components/notifications/notifications.tsx
Normal file
112
config/astal/components/notifications/notifications.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
// From astal examples
|
||||
|
||||
import { GLib } from "astal"
|
||||
import { Gtk, Astal } from "astal/gtk3"
|
||||
import { type EventBox } from "astal/gtk3/widget"
|
||||
import Notifd from "gi://AstalNotifd"
|
||||
|
||||
const isIcon = (icon: string) =>
|
||||
!!Astal.Icon.lookup_icon(icon)
|
||||
|
||||
const fileExists = (path: string) =>
|
||||
GLib.file_test(path, GLib.FileTest.EXISTS)
|
||||
|
||||
const time = (time: number, format = "%H:%M") => GLib.DateTime
|
||||
.new_from_unix_local(time)
|
||||
.format(format)!
|
||||
|
||||
const urgency = (n: Notifd.Notification) => {
|
||||
const { LOW, NORMAL, CRITICAL } = Notifd.Urgency
|
||||
// match operator when?
|
||||
switch (n.urgency) {
|
||||
case LOW: return "low"
|
||||
case CRITICAL: return "critical"
|
||||
case NORMAL:
|
||||
default: return "normal"
|
||||
}
|
||||
}
|
||||
|
||||
type Props = {
|
||||
delete( id: number, instanceID: number ): void
|
||||
setup(self: EventBox): void
|
||||
onHoverLost(self: EventBox): void
|
||||
notification: Notifd.Notification
|
||||
id: number
|
||||
instanceID: number
|
||||
}
|
||||
|
||||
export default function Notification(props: Props) {
|
||||
const { notification: n, onHoverLost, setup, id: id, delete: del, instanceID: instance } = props
|
||||
const { START, CENTER, END } = Gtk.Align
|
||||
|
||||
return <eventbox
|
||||
className={`Notification ${urgency(n)}`}
|
||||
setup={setup}
|
||||
onHoverLost={onHoverLost}>
|
||||
<box vertical>
|
||||
<box className="header">
|
||||
{(n.appIcon || n.desktopEntry) && <icon
|
||||
className="app-icon"
|
||||
visible={Boolean(n.appIcon || n.desktopEntry)}
|
||||
icon={n.appIcon || n.desktopEntry}
|
||||
/>}
|
||||
<label
|
||||
className="app-name"
|
||||
halign={START}
|
||||
truncate
|
||||
label={n.appName || "Unknown"}
|
||||
/>
|
||||
<label
|
||||
className="time"
|
||||
hexpand
|
||||
halign={END}
|
||||
label={time(n.time)}
|
||||
/>
|
||||
<button onClicked={() => del( id, instance )}>
|
||||
<icon icon="window-close-symbolic" />
|
||||
</button>
|
||||
</box>
|
||||
<Gtk.Separator visible />
|
||||
<box className="content">
|
||||
{n.image && fileExists(n.image) && <box
|
||||
valign={START}
|
||||
className="image"
|
||||
css={`background-image: url('${n.image}')`}
|
||||
/>}
|
||||
{n.image && isIcon(n.image) && <box
|
||||
expand={false}
|
||||
valign={START}
|
||||
className="icon-image">
|
||||
<icon icon={n.image} expand halign={CENTER} valign={CENTER} />
|
||||
</box>}
|
||||
<box vertical>
|
||||
<label
|
||||
className="summary"
|
||||
halign={START}
|
||||
xalign={0}
|
||||
label={n.summary}
|
||||
truncate
|
||||
/>
|
||||
{n.body && <label
|
||||
className="body"
|
||||
wrap
|
||||
useMarkup
|
||||
halign={START}
|
||||
xalign={0}
|
||||
justifyFill
|
||||
label={n.body}
|
||||
/>}
|
||||
</box>
|
||||
</box>
|
||||
{n.get_actions().length > 0 && <box className="actions">
|
||||
{n.get_actions().map(({ label, id }) => (
|
||||
<button
|
||||
hexpand
|
||||
onClicked={() => n.invoke(id)}>
|
||||
<label label={label} halign={CENTER} hexpand />
|
||||
</button>
|
||||
))}
|
||||
</box>}
|
||||
</box>
|
||||
</eventbox>
|
||||
}
|
21
config/astal/env.d.ts
vendored
Normal file
21
config/astal/env.d.ts
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
declare const SRC: string
|
||||
|
||||
declare module "inline:*" {
|
||||
const content: string
|
||||
export default content
|
||||
}
|
||||
|
||||
declare module "*.scss" {
|
||||
const content: string
|
||||
export default content
|
||||
}
|
||||
|
||||
declare module "*.blp" {
|
||||
const content: string
|
||||
export default content
|
||||
}
|
||||
|
||||
declare module "*.css" {
|
||||
const content: string
|
||||
export default content
|
||||
}
|
6
config/astal/package.json
Normal file
6
config/astal/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "astal-shell",
|
||||
"dependencies": {
|
||||
"astal": "/usr/share/astal/gjs"
|
||||
}
|
||||
}
|
20
config/astal/style.scss
Normal file
20
config/astal/style.scss
Normal file
@ -0,0 +1,20 @@
|
||||
// https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gtk/theme/Adwaita/_colors-public.scss
|
||||
$fg-color: #{"@theme_fg_color"};
|
||||
$bg-color: #{"@theme_bg_color"};
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
14
config/astal/tsconfig.json
Normal file
14
config/astal/tsconfig.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"compilerOptions": {
|
||||
"experimentalDecorators": true,
|
||||
"strict": true,
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"moduleResolution": "Bundler",
|
||||
// "checkJs": true,
|
||||
// "allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "astal/gtk3",
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user