astal: removed notif module
This commit is contained in:
@@ -1,125 +0,0 @@
|
|||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
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 = {
|
|
||||||
setup(self: EventBox): void
|
|
||||||
onHoverLost(self: EventBox): void
|
|
||||||
notification: Notifd.Notification
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function Notification(props: Props) {
|
|
||||||
const { notification: n, onHoverLost, setup } = 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={() => n.dismiss()}>
|
|
||||||
<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>
|
|
||||||
}
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
import { Astal, Gtk, Gdk } from "astal/gtk3"
|
|
||||||
import Notifd from "gi://AstalNotifd"
|
|
||||||
import Notification from "./Notification"
|
|
||||||
import { type Subscribable } from "astal/binding"
|
|
||||||
import { Variable, bind, timeout } from "astal"
|
|
||||||
|
|
||||||
// see comment below in constructor
|
|
||||||
const TIMEOUT_DELAY = 5000
|
|
||||||
|
|
||||||
// The purpose if this class is to replace Variable<Array<Widget>>
|
|
||||||
// with a Map<number, Widget> type in order to track notification widgets
|
|
||||||
// by their id, while making it conviniently bindable as an array
|
|
||||||
class NotifiationMap implements Subscribable {
|
|
||||||
// the underlying map to keep track of id widget pairs
|
|
||||||
private map: Map<number, Gtk.Widget> = new Map()
|
|
||||||
|
|
||||||
// it makes sense to use a Variable under the hood and use its
|
|
||||||
// reactivity implementation instead of keeping track of subscribers ourselves
|
|
||||||
private var: Variable<Array<Gtk.Widget>> = Variable([])
|
|
||||||
|
|
||||||
// notify subscribers to rerender when state changes
|
|
||||||
private notifiy() {
|
|
||||||
this.var.set([...this.map.values()].reverse())
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
const notifd = Notifd.get_default()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* uncomment this if you want to
|
|
||||||
* ignore timeout by senders and enforce our own timeout
|
|
||||||
* note that if the notification has any actions
|
|
||||||
* they might not work, since the sender already treats them as resolved
|
|
||||||
*/
|
|
||||||
// notifd.ignoreTimeout = true
|
|
||||||
|
|
||||||
notifd.connect("notified", (_, id) => {
|
|
||||||
this.set(id, Notification({
|
|
||||||
notification: notifd.get_notification(id)!,
|
|
||||||
|
|
||||||
// once hovering over the notification is done
|
|
||||||
// destroy the widget without calling notification.dismiss()
|
|
||||||
// so that it acts as a "popup" and we can still display it
|
|
||||||
// in a notification center like widget
|
|
||||||
// but clicking on the close button will close it
|
|
||||||
onHoverLost: () => this.delete(id),
|
|
||||||
|
|
||||||
// notifd by default does not close notifications
|
|
||||||
// until user input or the timeout specified by sender
|
|
||||||
// which we set to ignore above
|
|
||||||
setup: () => timeout(TIMEOUT_DELAY, () => {
|
|
||||||
/**
|
|
||||||
* uncomment this if you want to "hide" the notifications
|
|
||||||
* after TIMEOUT_DELAY
|
|
||||||
*/
|
|
||||||
// this.delete(id)
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
|
|
||||||
// notifications can be closed by the outside before
|
|
||||||
// any user input, which have to be handled too
|
|
||||||
notifd.connect("resolved", (_, id) => {
|
|
||||||
this.delete(id)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private set(key: number, value: Gtk.Widget) {
|
|
||||||
// in case of replacecment destroy previous widget
|
|
||||||
this.map.get(key)?.destroy()
|
|
||||||
this.map.set(key, value)
|
|
||||||
this.notifiy()
|
|
||||||
}
|
|
||||||
|
|
||||||
private delete(key: number) {
|
|
||||||
this.map.get(key)?.destroy()
|
|
||||||
this.map.delete(key)
|
|
||||||
this.notifiy()
|
|
||||||
}
|
|
||||||
|
|
||||||
// needed by the Subscribable interface
|
|
||||||
get() {
|
|
||||||
return this.var.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
// needed by the Subscribable interface
|
|
||||||
subscribe(callback: (list: Array<Gtk.Widget>) => void) {
|
|
||||||
return this.var.subscribe(callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function NotificationPopups(gdkmonitor: Gdk.Monitor) {
|
|
||||||
const { TOP, RIGHT } = Astal.WindowAnchor
|
|
||||||
const notifs = new NotifiationMap()
|
|
||||||
|
|
||||||
return <window
|
|
||||||
className="NotificationPopups"
|
|
||||||
gdkmonitor={gdkmonitor}
|
|
||||||
exclusivity={Astal.Exclusivity.EXCLUSIVE}
|
|
||||||
anchor={TOP | RIGHT}>
|
|
||||||
<box vertical noImplicitDestroy>
|
|
||||||
{bind(notifs)}
|
|
||||||
</box>
|
|
||||||
</window>
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user