Files
TheaninovOS/desktops/hyprland/ags/modules/lib/notification.js
2023-11-15 16:09:26 +01:00

312 lines
9.6 KiB
JavaScript

import Widget from "resource:///com/github/Aylur/ags/widget.js"
import {lookUpIcon} from "resource:///com/github/Aylur/ags/utils.js"
import GLib from "gi://GLib"
import Gtk from "gi://Gtk"
import Gdk from "gi://Gdk?version=3.0"
import {MaterialIcon} from "./materialicon.js"
import {setupCursorHover} from "./cursorhover.js"
/**
* @typedef {{appIcon: string; appEntry: string; image?: string; urgency: 'critical'}} NotificationObject
*/
/**
* @param notificationObject {NotificationObject}
* @returns {import('types/widgets/box').default}
* @constructor
*/
const NotificationIcon = notificationObject => {
if (notificationObject.image) {
return Widget.Box({
valign: Gtk.Align.CENTER,
hexpand: false,
className: "notif-icon",
style:
`background-image: url("${notificationObject.image}");` +
"background-size: auto 100%;" +
"background-repeat: no-repeat;" +
"background-position: center;",
})
}
let icon = "NO_ICON"
if (lookUpIcon(notificationObject.appIcon)) icon = notificationObject.appIcon
if (lookUpIcon(notificationObject.appEntry)) icon = notificationObject.appEntry
return Widget.Box({
valign: Gtk.Align.CENTER,
hexpand: false,
className: "notif-icon",
setup: box => {
if (icon !== "NO_ICON")
box.pack_start(
Widget.Icon({
icon,
size: 30,
halign: Gtk.Align.CENTER,
hexpand: true,
valign: Gtk.Align.CENTER,
setup: () => {
box.toggleClassName(`notif-icon-material-${notificationObject.urgency}`, true)
},
}),
false,
true,
0,
)
else
box.pack_start(
MaterialIcon(`${notificationObject.urgency === "critical" ? "release_alert" : "chat"}`, "hugeass", {
hexpand: true,
setup: () => box.toggleClassName(`notif-icon-material-${notificationObject.urgency}`, true),
}),
false,
true,
0,
)
},
})
}
/**
* @param options {{notifObject?: NotificationObject; isPopup?: boolean, props?: object}}
* @returns {import('types/widgets/revealer').default}
*/
export default ({notifObject, isPopup = false, props = {}} = {}) => {
const command = isPopup ? () => notifObject.dismiss() : () => notifObject.close()
const destroyWithAnims = () => {
widget.sensitive = false
notificationBox.setStyle(rightAnim1)
Utils.timeout(200, () => {
wholeThing.revealChild = false
})
Utils.timeout(400, () => {
command()
wholeThing.destroy()
})
}
const widget = Widget.EventBox({
onHover: self => {
self.window.set_cursor(Gdk.Cursor.new_from_name(display, "grab"))
if (!wholeThing._hovered) wholeThing._hovered = true
},
onHoverLost: self => {
self.window.set_cursor(null)
if (wholeThing._hovered) wholeThing._hovered = false
if (isPopup) {
command()
}
},
onMiddleClick: self => {
destroyWithAnims()
},
})
const wholeThing = Widget.Revealer({
properties: [
["id", notifObject.id],
["close", undefined],
["hovered", false],
["dragging", false],
["destroyWithAnims", () => destroyWithAnims],
],
revealChild: false,
transition: "slide_down",
transitionDuration: 200,
child: Widget.Box({
// Box to make sure css-based spacing works
homogeneous: true,
}),
})
const display = Gdk.Display.get_default()
const notificationContent = Widget.Box({
...props,
className: `${isPopup ? "popup-" : ""}notif-${notifObject.urgency} spacing-h-10`,
children: [
NotificationIcon(notifObject),
Widget.Box({
valign: "center",
vertical: true,
hexpand: true,
children: [
Widget.Box({
children: [
Widget.Label({
xalign: 0,
className: "txt-small txt-semibold titlefont",
justify: Gtk.Justification.LEFT,
hexpand: true,
maxWidthChars: 24,
ellipsize: 3,
wrap: true,
useMarkup: notifObject.summary.startsWith("<"),
label: notifObject.summary,
}),
],
}),
Widget.Label({
xalign: 0,
className: "txt-smallie notif-body-${urgency}",
useMarkup: true,
justify: Gtk.Justification.LEFT,
wrap: true,
label: notifObject.body,
}),
],
}),
Widget.Box({
className: "spacing-h-5",
children: [
Widget.Label({
valign: "center",
className: "txt-smaller txt-semibold",
justify: Gtk.Justification.RIGHT,
setup: label => {
const messageTime = GLib.DateTime.new_from_unix_local(notifObject.time)
if (messageTime.get_day_of_year() == GLib.DateTime.new_now_local().get_day_of_year()) {
label.label = messageTime.format("%H:%M")
} else if (
messageTime.get_day_of_year() ==
GLib.DateTime.new_now_local().get_day_of_year() - 1
) {
label.label = messageTime.format("%H:%M\nYesterday")
} else {
label.label = messageTime.format("%H:%M\n%d/%m")
}
},
}),
Widget.Button({
className: "notif-close-btn",
onClicked: () => {
destroyWithAnims()
},
child: MaterialIcon("close", "large", {
valign: "center",
}),
setup: button => setupCursorHover(button),
}),
],
}),
// what is this? i think it should be at the bottom not on the right
// Box({
// className: 'actions',
// children: actions.map(action => Button({
// className: 'action-button',
// onClicked: () => Notifications.invoke(id, action.id),
// hexpand: true,
// child: Label(action.label),
// })),
// }),
],
})
// Gesture stuff
const gesture = Gtk.GestureDrag.new(widget)
let initialDir = 0
// in px
const startMargin = 0
const dragThreshold = 100
// in rem
const maxOffset = 10.227
const endMargin = 20.455
const disappearHeight = 6.818
const leftAnim1 = `transition: 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
margin-left: -${Number(maxOffset + endMargin)}rem;
margin-right: ${Number(maxOffset + endMargin)}rem;
opacity: 0;`
const rightAnim1 = `transition: 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
margin-left: ${Number(maxOffset + endMargin)}rem;
margin-right: -${Number(maxOffset + endMargin)}rem;
opacity: 0;`
const notificationBox = Widget.Box({
properties: [
["leftAnim1", leftAnim1],
["rightAnim1", rightAnim1],
["ready", false],
],
homogeneous: true,
children: [notificationContent],
connections: [
[
gesture,
self => {
var offset = gesture.get_offset()[1]
if (initialDir == 0 && offset != 0) initialDir = offset > 0 ? 1 : -1
if (offset > 0) {
if (initialDir < 0) self.setStyle(`margin-left: 0px; margin-right: 0px;`)
else
self.setStyle(`
margin-left: ${Number(offset + startMargin)}px;
margin-right: -${Number(offset + startMargin)}px;
`)
} else if (offset < 0) {
if (initialDir > 0) self.setStyle(`margin-left: 0px; margin-right: 0px;`)
else {
offset = Math.abs(offset)
self.setStyle(`
margin-right: ${Number(offset + startMargin)}px;
margin-left: -${Number(offset + startMargin)}px;
`)
}
}
wholeThing._dragging = Math.abs(offset) > 10
if (widget.window) widget.window.set_cursor(Gdk.Cursor.new_from_name(display, "grabbing"))
},
"drag-update",
],
[
gesture,
self => {
if (!self._ready) {
wholeThing.revealChild = true
self._ready = true
return
}
const offset = gesture.get_offset()[1] ?? NaN
if (Math.abs(offset) > dragThreshold && offset * initialDir > 0) {
if (offset > 0) {
self.set_style(rightAnim1)
widget.sensitive = false
} else {
self.set_style(leftAnim1)
widget.sensitive = false
}
Utils.timeout(200, () => {
wholeThing.revealChild = false
})
Utils.timeout(400, () => {
command()
wholeThing.destroy()
})
} else {
self.set_style(`transition: margin 200ms cubic-bezier(0.05, 0.7, 0.1, 1), opacity 200ms cubic-bezier(0.05, 0.7, 0.1, 1);
margin-left: ${startMargin}px;
margin-right: ${startMargin}px;
margin-bottom: unset; margin-top: unset;
opacity: 1;`)
if (widget.window) widget.window.set_cursor(Gdk.Cursor.new_from_name(display, "grab"))
wholeThing._dragging = false
}
initialDir = 0
},
"drag-end",
],
],
})
widget.add(notificationBox)
wholeThing.child.children = [widget]
return wholeThing
}