diff options
Diffstat (limited to 'src/modules')
| -rw-r--r-- | src/modules/popdowns/bluetoothdevices.tsx | 98 | ||||
| -rw-r--r-- | src/modules/popdowns/index.tsx | 19 | ||||
| -rw-r--r-- | src/modules/popdowns/media.tsx | 152 | ||||
| -rw-r--r-- | src/modules/popdowns/networks.tsx | 101 | ||||
| -rw-r--r-- | src/modules/popdowns/notifications.tsx | 65 | ||||
| -rw-r--r-- | src/modules/popdowns/sideleft.tsx | 205 | ||||
| -rw-r--r-- | src/modules/popdowns/sideright.tsx | 146 | ||||
| -rw-r--r-- | src/modules/popdowns/updates.tsx | 145 |
8 files changed, 0 insertions, 931 deletions
diff --git a/src/modules/popdowns/bluetoothdevices.tsx b/src/modules/popdowns/bluetoothdevices.tsx deleted file mode 100644 index c71d981..0000000 --- a/src/modules/popdowns/bluetoothdevices.tsx +++ /dev/null @@ -1,98 +0,0 @@ -import { bind, Variable } from "astal"; -import { Astal, Gtk } from "astal/gtk3"; -import AstalBluetooth from "gi://AstalBluetooth"; -import PopdownWindow from "../../widgets/popdownwindow"; - -const BluetoothDevice = (device: AstalBluetooth.Device) => ( - <box className={bind(device, "connected").as(c => `device ${c ? "active" : ""}`)}> - <icon - className="icon" - icon={bind(device, "icon").as(i => - Astal.Icon.lookup_icon(`${i}-symbolic`) ? `${i}-symbolic` : "bluetooth-symbolic" - )} - /> - <label - truncate - xalign={0} - setup={self => { - const update = () => - (self.label = `${device.alias}${ - device.connected || device.paired ? ` (${device.connected ? "Connected" : "Paired"})` : "" - }`); - self.hook(device, "notify::alias", update); - self.hook(device, "notify::connected", update); - self.hook(device, "notify::paired", update); - update(); - }} - /> - <box hexpand /> - <button - cursor="pointer" - onClicked={self => { - if (device.connected) - device.disconnect_device((_, res) => { - self.sensitive = true; - device.disconnect_device_finish(res); - }); - else - device.connect_device((_, res) => { - self.sensitive = true; - device.connect_device_finish(res); - }); - self.sensitive = false; - }} - label={bind(device, "connected").as(c => (c ? "Disconnect" : "Connect"))} - /> - <button - cursor="pointer" - onClicked={() => AstalBluetooth.get_default().adapter.remove_device(device)} - label="Remove" - /> - </box> -); - -const List = () => ( - <box vertical valign={Gtk.Align.START} className="list"> - {bind(AstalBluetooth.get_default(), "devices").as(d => d.map(BluetoothDevice))} - </box> -); - -export default () => { - const bluetooth = AstalBluetooth.get_default(); - const label = Variable(""); - - const update = () => { - const devices = bluetooth.get_devices(); - const connected = devices.filter(d => d.connected).length; - label.set(`${connected} connected device${connected === 1 ? "" : "s"} (${devices.length} available)`); - }; - bluetooth.get_devices().forEach(d => d.connect("notify::connected", update)); - bluetooth.connect("device-added", (_, device) => device.connect("notify::connected", update)); - bluetooth.connect("notify::devices", update); - update(); - - return ( - <PopdownWindow - name="bluetooth-devices" - count={bind(bluetooth, "devices").as(n => n.length)} - countLabel={bind(label)} - headerButtons={[ - { - label: bind(bluetooth, "isPowered").as(p => (p ? "Disable" : "Enable")), - onClicked: () => bluetooth.toggle(), - }, - { - label: "Discovery", - onClicked: () => { - if (bluetooth.adapter.discovering) bluetooth.adapter.start_discovery(); - else bluetooth.adapter.stop_discovery(); - }, - enabled: bind(bluetooth.adapter, "discovering"), - }, - ]} - emptyIcon="bluetooth_disabled" - emptyLabel="No Bluetooth devices" - list={<List />} - /> - ); -}; diff --git a/src/modules/popdowns/index.tsx b/src/modules/popdowns/index.tsx deleted file mode 100644 index fb9abf7..0000000 --- a/src/modules/popdowns/index.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import BluetoothDevices from "./bluetoothdevices"; -import Media from "./media"; -import Networks from "./networks"; -import Notifications from "./notifications"; -import SideLeft from "./sideleft"; -import SideRight from "./sideright"; -import Updates from "./updates"; - -export default () => { - <Notifications />; - <Updates />; - <BluetoothDevices />; - <Networks />; - <Media />; - <SideRight />; - <SideLeft />; - - return null; -}; diff --git a/src/modules/popdowns/media.tsx b/src/modules/popdowns/media.tsx deleted file mode 100644 index 8757cd2..0000000 --- a/src/modules/popdowns/media.tsx +++ /dev/null @@ -1,152 +0,0 @@ -import Players from "@/services/players"; -import { isRealPlayer } from "@/utils/mpris"; -import PopupWindow from "@/widgets/popupwindow"; -import { bind, Variable } from "astal"; -import { Astal, Gtk } from "astal/gtk3"; -import AstalMpris from "gi://AstalMpris"; - -const shuffleToIcon = (s: AstalMpris.Shuffle) => - `caelestia-${s === AstalMpris.Shuffle.ON ? "shuffle" : "consecutive"}-symbolic`; - -const playbackToIcon = (s: AstalMpris.PlaybackStatus) => - `caelestia-${s === AstalMpris.PlaybackStatus.PLAYING ? "pause" : "play"}-symbolic`; - -const loopToIcon = (s: AstalMpris.Loop) => { - if (s === AstalMpris.Loop.PLAYLIST) return "caelestia-repeat-symbolic"; - if (s === AstalMpris.Loop.TRACK) return "caelestia-repeat-one-symbolic"; - return "caelestia-no-repeat-symbolic"; -}; - -const lengthStr = (length: number) => - `${Math.floor(length / 60)}:${Math.floor(length % 60) - .toString() - .padStart(2, "0")}`; - -const Player = ({ player }: { player: AstalMpris.Player }) => { - const background = ( - <box className="background" css={bind(player, "coverArt").as(u => `background-image: url("${u}");`)} /> - ); - return ( - <overlay - overlays={[ - <box className="overlay" />, - <box - vertical - className="player" - setup={self => - self.connect("size-allocate", () => - background.set_size_request(self.get_preferred_width()[1], self.get_preferred_height()[1]) - ) - } - > - <label className="title" label={bind(player, "title").as(t => t ?? "")} /> - <label className="artist" label={bind(player, "artist").as(a => a ?? "")} /> - <label className="album" label={bind(player, "album").as(a => a ?? "")} /> - <box className="controls" halign={Gtk.Align.CENTER}> - <button - cursor="pointer" - onClicked={() => player.shuffle()} - sensitive={bind(player, "canControl")} - > - <icon icon={bind(player, "shuffleStatus").as(shuffleToIcon)} /> - </button> - <button - cursor="pointer" - onClicked={() => player.previous()} - sensitive={bind(player, "canGoPrevious")} - > - <icon icon="caelestia-skip-previous-symbolic" /> - </button> - <button - cursor="pointer" - onClicked={() => player.play_pause()} - sensitive={bind(player, "canControl")} - > - <icon icon={bind(player, "playbackStatus").as(playbackToIcon)} /> - </button> - <button cursor="pointer" onClicked={() => player.next()} sensitive={bind(player, "canGoNext")}> - <icon icon="caelestia-skip-next-symbolic" /> - </button> - <button cursor="pointer" onClicked={() => player.loop()} sensitive={bind(player, "canControl")}> - <icon icon={bind(player, "loopStatus").as(loopToIcon)} /> - </button> - </box> - <slider - hexpand - onDragged={self => player.set_position(self.value * player.length)} - setup={self => { - const update = () => { - self.set_tooltip_text(`${lengthStr(player.position)}/${lengthStr(player.length)}`); - self.set_value(player.position / player.length); - }; - self.hook(player, "notify::position", update); - self.hook(player, "notify::length", update); - update(); - }} - /> - </box>, - ]} - > - {background} - </overlay> - ); -}; - -export default () => { - const shown = Variable("none"); - return ( - <PopupWindow name="media"> - <box className="media"> - <eventbox - onClick={(_, event) => { - if (event.button === Astal.MouseButton.SECONDARY) { - const current = Players.get_default().list.find(p => p.busName === shown.get()); - if (current) Players.get_default().makeCurrent(current); - } - }} - onScroll={(_, event) => { - const players = AstalMpris.get_default().players.filter(isRealPlayer); - const idx = players.findIndex(p => p.busName === shown.get()); - if (idx === -1) return; - if (event.delta_y < 0) { - if (idx > 0) shown.set(players[idx - 1].busName); - } else if (idx < players.length - 1) shown.set(players[idx + 1].busName); - }} - > - <stack - expand - transitionType={Gtk.StackTransitionType.SLIDE_LEFT_RIGHT} - transitionDuration={150} - shown={bind(shown)} - setup={self => { - const players = new Map<string, JSX.Element>(); - - const addPlayer = (player: AstalMpris.Player) => { - if (!isRealPlayer(player) || players.has(player.busName)) return; - const widget = <Player player={player} />; - self.add_named(widget, player.busName); - if (players.size === 0) shown.set(player.busName); - players.set(player.busName, widget); - }; - - for (const player of Players.get_default().list) addPlayer(player); - - self.hook(AstalMpris.get_default(), "player-added", (_, player) => addPlayer(player)); - self.hook(AstalMpris.get_default(), "player-closed", (_, player) => { - players.get(player.busName)?.destroy(); - players.delete(player.busName); - if (shown.get() === player.busName) - shown.set(AstalMpris.get_default().players.find(isRealPlayer)?.busName ?? "none"); - }); - }} - > - <box vertical valign={Gtk.Align.CENTER} name="none"> - <label className="icon" label="music_note" /> - <label label="No media playing" /> - </box> - </stack> - </eventbox> - </box> - </PopupWindow> - ); -}; diff --git a/src/modules/popdowns/networks.tsx b/src/modules/popdowns/networks.tsx deleted file mode 100644 index 726f1c9..0000000 --- a/src/modules/popdowns/networks.tsx +++ /dev/null @@ -1,101 +0,0 @@ -import { bind, execAsync, Variable } from "astal"; -import { Gtk } from "astal/gtk3"; -import AstalNetwork from "gi://AstalNetwork"; -import PopdownWindow from "../../widgets/popdownwindow"; - -const Network = (accessPoint: AstalNetwork.AccessPoint) => ( - <box - className={bind(AstalNetwork.get_default().wifi, "activeAccessPoint").as( - a => `network ${a === accessPoint ? "active" : ""}` - )} - > - <icon className="icon" icon={bind(accessPoint, "iconName")} /> - <label - truncate - xalign={0} - setup={self => { - const update = () => - (self.label = `${accessPoint.ssid}${` (${accessPoint.frequency > 5000 ? 5 : 2.4}GHz | ${ - accessPoint.strength - }/100)`}`); - self.hook(accessPoint, "notify::ssid", update); - self.hook(accessPoint, "notify::frequency", update); - self.hook(accessPoint, "notify::strength", update); - update(); - }} - /> - <box hexpand /> - <button - cursor="pointer" - onClicked={self => { - const cmd = - AstalNetwork.get_default().wifi.activeAccessPoint === accessPoint ? "c down id" : "d wifi connect"; - execAsync(`nmcli ${cmd} '${accessPoint.ssid}'`) - .then(() => (self.sensitive = true)) - .catch(console.error); - self.sensitive = false; - }} - label={bind(AstalNetwork.get_default().wifi, "activeAccessPoint").as(a => - a === accessPoint ? "Disconnect" : "Connect" - )} - /> - <button - cursor="pointer" - onClicked={() => execAsync(`nmcli c delete id '${accessPoint.ssid}'`).catch(() => {})} - label="Forget" - /> - </box> -); - -const List = () => { - const { wifi } = AstalNetwork.get_default(); - const children = Variable.derive([bind(wifi, "accessPoints"), bind(wifi, "activeAccessPoint")], (aps, ac) => - aps - .filter(a => a.ssid) - .sort((a, b) => (a === ac ? -1 : b.strength - a.strength)) - .map(Network) - ); - - return ( - <box vertical valign={Gtk.Align.START} className="list" onDestroy={() => children.drop()}> - {bind(children)} - </box> - ); -}; - -export default () => { - const network = AstalNetwork.get_default(); - const label = Variable(""); - - const update = () => { - if (network.primary === AstalNetwork.Primary.WIFI) label.set(network.wifi.ssid ?? "Disconnected"); - else if (network.primary === AstalNetwork.Primary.WIRED) label.set(`Ethernet (${network.wired.speed})`); - else label.set("No Wifi"); - }; - network.connect("notify::primary", update); - network.get_wifi()?.connect("notify::ssid", update); - network.get_wired()?.connect("notify::speed", update); - update(); - - return ( - <PopdownWindow - name="networks" - count={bind(network.wifi, "accessPoints").as(a => a.length)} - countLabel={bind(label)} - headerButtons={[ - { - label: bind(network.wifi, "enabled").as(e => (e ? "Disable" : "Enable")), - onClicked: () => (network.wifi.enabled = !network.wifi.enabled), - }, - { - label: bind(network.wifi, "scanning").as(s => (s ? "Scanning" : "Scan")), - onClicked: () => network.wifi.scan(), - enabled: bind(network.wifi, "scanning"), - }, - ]} - emptyIcon="wifi_off" - emptyLabel={bind(network.wifi, "enabled").as(p => (p ? "No available networks" : "Wifi is off"))} - list={<List />} - /> - ); -}; diff --git a/src/modules/popdowns/notifications.tsx b/src/modules/popdowns/notifications.tsx deleted file mode 100644 index 4ab0095..0000000 --- a/src/modules/popdowns/notifications.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { bind } from "astal"; -import { Astal, Gtk } from "astal/gtk3"; -import AstalNotifd from "gi://AstalNotifd"; -import Notification from "../../widgets/notification"; -import PopdownWindow from "../../widgets/popdownwindow"; - -const List = () => ( - <box - vertical - valign={Gtk.Align.START} - className="list" - setup={self => { - const notifd = AstalNotifd.get_default(); - const map = new Map<number, Notification>(); - - const addNotification = (notification: AstalNotifd.Notification) => { - const notif = (<Notification notification={notification} />) as Notification; - notif.connect("destroy", () => map.get(notification.id) === notif && map.delete(notification.id)); - map.get(notification.id)?.destroyWithAnims(); - map.set(notification.id, notif); - - self.pack_end( - <eventbox - // Dismiss on middle click - onClick={(_, event) => event.button === Astal.MouseButton.MIDDLE && notification.dismiss()} - > - {notif} - </eventbox>, - false, - false, - 0 - ); - }; - - notifd - .get_notifications() - .sort((a, b) => a.time - b.time) - .forEach(addNotification); - - self.hook(notifd, "notified", (_, id) => addNotification(notifd.get_notification(id))); - self.hook(notifd, "resolved", (_, id) => map.get(id)?.destroyWithAnims()); - }} - /> -); - -export default () => ( - <PopdownWindow - name="notifications" - count={bind(AstalNotifd.get_default(), "notifications").as(n => n.length)} - headerButtons={[ - { - label: "Silence", - onClicked: () => (AstalNotifd.get_default().dontDisturb = !AstalNotifd.get_default().dontDisturb), - enabled: bind(AstalNotifd.get_default(), "dontDisturb"), - }, - { - label: "Clear", - onClicked: () => AstalNotifd.get_default().notifications.forEach(n => n.dismiss()), - }, - ]} - emptyIcon="notifications_active" - emptyLabel="All caught up!" - list={<List />} - /> -); diff --git a/src/modules/popdowns/sideleft.tsx b/src/modules/popdowns/sideleft.tsx deleted file mode 100644 index 753026e..0000000 --- a/src/modules/popdowns/sideleft.tsx +++ /dev/null @@ -1,205 +0,0 @@ -import Cpu from "@/services/cpu"; -import Gpu from "@/services/gpu"; -import Memory from "@/services/memory"; -import Storage from "@/services/storage"; -import { osId } from "@/utils/system"; -import PopupWindow from "@/widgets/popupwindow"; -import { bind, execAsync, GLib, type Binding } from "astal"; -import { App, Gtk, type Widget } from "astal/gtk3"; -import type cairo from "cairo"; -import { sideleft } from "config"; - -const fmt = (bytes: number, pow: number) => +(bytes / 1024 ** pow).toFixed(2); -const format = ({ total, used }: { total: number; used: number }) => { - if (total >= 1024 ** 4) return `${fmt(used, 4)}/${fmt(total, 4)} TiB`; - if (total >= 1024 ** 3) return `${fmt(used, 3)}/${fmt(total, 3)} GiB`; - if (total >= 1024 ** 2) return `${fmt(used, 2)}/${fmt(total, 2)} MiB`; - if (total >= 1024) return `${fmt(used, 1)}/${fmt(total, 1)} KiB`; - return `${used}/${total} B`; -}; - -const User = () => ( - <box className="user"> - <box - valign={Gtk.Align.CENTER} - className="face" - css={` - background-image: url("${HOME}/.face"); - `} - > - {!GLib.file_test(HOME + "/.face", GLib.FileTest.EXISTS) && ( - <label - setup={self => { - const name = GLib.get_real_name(); - if (name !== "Unknown") - self.label = name - .split(" ") - .map(s => s[0].toUpperCase()) - .join(""); - else self.label = ""; - }} - /> - )} - </box> - <box vertical hexpand valign={Gtk.Align.CENTER} className="details"> - <label xalign={0} className="name" label={GLib.get_user_name()} /> - <label xalign={0} label={(GLib.getenv("XDG_CURRENT_DESKTOP") ?? osId).toUpperCase()} /> - </box> - <button - valign={Gtk.Align.CENTER} - className="power" - cursor="pointer" - onClicked={() => App.toggle_window("session")} - label="power_settings_new" - /> - </box> -); - -const QuickLaunch = () => ( - <box className="quick-launch"> - <box vertical> - <box>{/* <button */}</box> // TODO - </box> - </box> -); - -const Location = ({ label, num }: { label: Binding<string>; num: number }) => ( - <button - className={"loc" + num} - cursor="pointer" - onClicked={self => { - self.get_toplevel().hide(); - const dir = label.get().split(" ").at(-1); - execAsync(`xdg-open ${HOME}/${dir?.toLowerCase() === "home" ? "" : `${dir}/`}`).catch(console.error); - }} - > - <label xalign={0} label={label} /> - </button> -); - -const Locations = () => ( - <box className="locations"> - <box vertical> - <Location label={bind(sideleft.directories.left.top)} num={1} /> - <Location label={bind(sideleft.directories.left.middle)} num={2} /> - <Location label={bind(sideleft.directories.left.bottom)} num={3} /> - </box> - <box vertical> - <Location label={bind(sideleft.directories.right.top)} num={4} /> - <Location label={bind(sideleft.directories.right.middle)} num={5} /> - <Location label={bind(sideleft.directories.right.bottom)} num={6} /> - </box> - </box> -); - -const Slider = ({ value }: { value: Binding<number> }) => ( - <drawingarea - hexpand - valign={Gtk.Align.CENTER} - className="slider" - css={bind(value).as(v => `font-size: ${v}px;`)} - setup={self => { - const halfPi = Math.PI / 2; - - const styleContext = self.get_style_context(); - self.set_size_request(-1, styleContext.get_property("min-height", Gtk.StateFlags.NORMAL) as number); - - self.connect("draw", (_, cr: cairo.Context) => { - const styleContext = self.get_style_context(); - - const width = self.get_allocated_width(); - const height = styleContext.get_property("min-height", Gtk.StateFlags.NORMAL) as number; - self.set_size_request(-1, height); - - const progressValue = styleContext.get_property("font-size", Gtk.StateFlags.NORMAL) as number; - let radius = styleContext.get_property("border-radius", Gtk.StateFlags.NORMAL) as number; - - const bg = styleContext.get_background_color(Gtk.StateFlags.NORMAL); - cr.setSourceRGBA(bg.red, bg.green, bg.blue, bg.alpha); - - // Background - cr.arc(radius, radius, radius, -Math.PI, -halfPi); // Top left - cr.arc(width - radius, radius, radius, -halfPi, 0); // Top right - cr.arc(width - radius, height - radius, radius, 0, halfPi); // Bottom right - cr.arc(radius, height - radius, radius, halfPi, Math.PI); // Bottom left - cr.fill(); - - // Flatten when near 0 - radius = Math.min(radius, Math.min(width * progressValue, height) / 2); - - const progressPosition = width * progressValue - radius; - const fg = styleContext.get_color(Gtk.StateFlags.NORMAL); - cr.setSourceRGBA(fg.red, fg.green, fg.blue, fg.alpha); - - // Foreground - cr.arc(radius, radius, radius, -Math.PI, -halfPi); // Top left - cr.arc(progressPosition, radius, radius, -halfPi, 0); // Top right - cr.arc(progressPosition, height - radius, radius, 0, halfPi); // Bottom right - cr.arc(radius, height - radius, radius, halfPi, Math.PI); // Bottom left - cr.fill(); - }); - }} - /> -); - -const Resource = ({ - icon, - name, - value, - labelSetup, -}: { - icon: string; - name: string; - value: Binding<number>; - labelSetup?: (self: Widget.Label) => void; -}) => ( - <box vertical className={`resource ${name}`}> - <box className="inner"> - <label label={icon} /> - <Slider value={value.as(v => v / 100)} /> - </box> - <label halign={Gtk.Align.END} label={labelSetup ? "" : value.as(v => `${+v.toFixed(2)}%`)} setup={labelSetup} /> - </box> -); - -const HwResources = () => ( - <box vertical className="hw-resources"> - {Gpu.get_default().available && <Resource icon="" name="gpu" value={bind(Gpu.get_default(), "usage")} />} - <Resource icon="" name="cpu" value={bind(Cpu.get_default(), "usage")} /> - <Resource - icon="" - name="memory" - value={bind(Memory.get_default(), "usage")} - labelSetup={self => { - const mem = Memory.get_default(); - const update = () => (self.label = format(mem)); - self.hook(mem, "notify::used", update); - self.hook(mem, "notify::total", update); - update(); - }} - /> - <Resource - icon="" - name="storage" - value={bind(Storage.get_default(), "usage")} - labelSetup={self => { - const storage = Storage.get_default(); - const update = () => (self.label = format(storage)); - self.hook(storage, "notify::used", update); - self.hook(storage, "notify::total", update); - update(); - }} - /> - </box> -); - -export default () => ( - <PopupWindow name="sideleft"> - <box vertical className="sideleft"> - <User /> - {/* <QuickLaunch /> */} - <Locations /> - <HwResources /> - </box> - </PopupWindow> -); diff --git a/src/modules/popdowns/sideright.tsx b/src/modules/popdowns/sideright.tsx deleted file mode 100644 index 5e873b3..0000000 --- a/src/modules/popdowns/sideright.tsx +++ /dev/null @@ -1,146 +0,0 @@ -import SWeather, { type WeatherData } from "@/services/weather"; -import { ellipsize } from "@/utils/strings"; -import { bindCurrentTime } from "@/utils/system"; -import { setupCustomTooltip, Calendar as WCal } from "@/utils/widgets"; -import PopupWindow from "@/widgets/popupwindow"; -import { bind, timeout } from "astal"; -import { Astal, Gtk, type Gdk } from "astal/gtk3"; - -const getHoursFromUpdate = (data: WeatherData, hours: number) => { - const updateTime = data.location.localtime_epoch; - const updateHourStart = updateTime - (updateTime % 3600); - - let nextHour = new Date((updateHourStart + hours * 3600) * 1000).getHours(); - if (nextHour >= 24) nextHour -= 24; - - return nextHour; -}; - -const Time = () => ( - <box className="time"> - <box hexpand halign={Gtk.Align.CENTER}> - <label label={bindCurrentTime("%I:%M:%S")} /> - <label className="ampm" label={bindCurrentTime("%p", c => (c.get_hour() < 12 ? "AM" : "PM"))} /> - </box> - </box> -); - -const Calendar = () => ( - <box className="calendar"> - <eventbox - setup={self => { - self.connect("button-press-event", (_, event: Gdk.Event) => { - if (event.get_button()[1] === Astal.MouseButton.MIDDLE) { - const now = new Date(); - const child = self.get_child() as WCal | null; - if (!child) return; - child.select_month(now.getMonth(), now.getFullYear()); - } - }); - }} - > - <WCal - hexpand - showDetails={false} - setup={self => { - const update = () => - timeout(0.1, () => { - const now = new Date(); - if (self.month === now.getMonth() && self.year === now.getFullYear()) - self.day = now.getDate(); - else self.day = 0; - }); - self.connect("month-changed", update); - self.connect("next-month", update); - self.connect("prev-month", update); - self.connect("next-year", update); - self.connect("prev-year", update); - update(); - }} - /> - </eventbox> - </box> -); - -const Weather = () => { - const weather = SWeather.get_default(); - - return ( - <box vertical className="weather"> - <centerbox className="current"> - <label - halign={Gtk.Align.START} - valign={Gtk.Align.CENTER} - className="status-icon" - label={bind(weather, "icon")} - /> - <box vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER} className="status"> - <box halign={Gtk.Align.CENTER} className="temperature"> - <label label={bind(weather, "temperature")} /> - <label - className={bind(weather, "tempColour").as(c => `temp-icon ${c}`)} - label={bind(weather, "tempIcon")} - /> - </box> - <label label={bind(weather, "condition").as(c => ellipsize(c, 16))} /> - </box> - <box vertical halign={Gtk.Align.END} valign={Gtk.Align.CENTER} className="other-data"> - <label - xalign={0} - label={bind(weather, "wind").as(w => ` ${w}`)} - setup={self => - setupCustomTooltip( - self, - bind(weather, "wind").as(w => `${w} wind speed`) - ) - } - /> - <label - xalign={0} - label={bind(weather, "rainChance").as(r => ` ${r}`)} - setup={self => - setupCustomTooltip( - self, - bind(weather, "rainChance").as(r => `${r} chance of rain`) - ) - } - /> - </box> - </centerbox> - <box className="separator" /> - <box className="forecast"> - {Array.from({ length: 4 }).map((_, i) => ( - <box vertical hexpand className="hour"> - <label - label={bind(weather, "raw").as(r => { - const hour = getHoursFromUpdate(r, i + 1); - return `${hour % 12 || 12}${hour < 12 ? "AM" : "PM"}`; - })} - /> - <label - className="icon" - label={bind(weather, "raw").as(r => - weather.getIcon(weather.forecast[getHoursFromUpdate(r, i + 1)].condition.text) - )} - /> - <label - label={bind(weather, "raw").as(r => - weather.getTemp(weather.forecast[getHoursFromUpdate(r, i + 1)]) - )} - /> - </box> - ))} - </box> - </box> - ); -}; - -export default () => ( - <PopupWindow name="sideright"> - <box vertical className="sideright"> - <Time /> - <Calendar /> - <Weather /> - </box> - </PopupWindow> -); diff --git a/src/modules/popdowns/updates.tsx b/src/modules/popdowns/updates.tsx deleted file mode 100644 index 4cb95e7..0000000 --- a/src/modules/popdowns/updates.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import Palette from "@/services/palette"; -import Updates, { Repo as IRepo, Update as IUpdate } from "@/services/updates"; -import { MenuItem } from "@/utils/widgets"; -import PopdownWindow from "@/widgets/popdownwindow"; -import { bind, execAsync, GLib, Variable } from "astal"; -import { App, Astal, Gtk } from "astal/gtk3"; - -const constructItem = (label: string, exec: string, quiet = true) => - new MenuItem({ - label, - onActivate() { - App.get_window("updates")?.hide(); - execAsync(exec).catch(e => !quiet && console.error(e)); - }, - }); - -const Update = (update: IUpdate) => { - const menu = new Gtk.Menu(); - menu.append(constructItem("Open info in browser", `xdg-open '${update.url}'`, false)); - menu.append(constructItem("Open info in terminal", `uwsm app -- foot -H pacman -Qi ${update.name}`)); - menu.append(new Gtk.SeparatorMenuItem({ visible: true })); - menu.append(constructItem("Reinstall", `uwsm app -T -- yay -S ${update.name}`)); - menu.append(constructItem("Remove with dependencies", `uwsm app -T -- yay -Rns ${update.name}`)); - - return ( - <button - onClick={(_, event) => event.button === Astal.MouseButton.SECONDARY && menu.popup_at_pointer(null)} - onDestroy={() => menu.destroy()} - > - <label - truncate - useMarkup - xalign={0} - label={bind(Palette.get_default(), "colours").as( - c => - `${update.name} <span foreground="${c.teal}">(${update.version.old} -> ${ - update.version.new - })</span>\n <span foreground="${c.subtext0}">${GLib.markup_escape_text( - update.description, - update.description.length - )}</span>` - )} - /> - </button> - ); -}; - -const Repo = ({ repo, first }: { repo: IRepo; first?: boolean }) => { - const expanded = Variable(first); - - return ( - <box vertical className="repo"> - <button className="wrapper" cursor="pointer" onClicked={() => expanded.set(!expanded.get())}> - <box className="header"> - <label className="icon" label={repo.icon} /> - <label label={`${repo.name} (${repo.updates.length})`} /> - <box hexpand /> - <label className="icon" label={bind(expanded).as(e => (e ? "expand_less" : "expand_more"))} /> - </box> - </button> - <revealer - revealChild={bind(expanded)} - transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN} - transitionDuration={200} - > - <box vertical className="list"> - {repo.updates.map(Update)} - </box> - </revealer> - </box> - ); -}; - -const News = ({ news }: { news: string }) => { - const expanded = Variable(true); - - news = news - .replace(/^([0-9]{4}-[0-9]{2}-[0-9]{2} .+)$/gm, "<b>$1</b>") // Make titles bold - .replaceAll("\n\x1b[0m\n<b>", "\n\n<b>") // Remove unopened \x1b[0m after each piece of news - .slice(0, -5) // Remove last unopened \x1b[0m - .replaceAll("\x1b[0m", "</span>"); // Replace reset code with end span - - return ( - <box vertical className="repo"> - <button className="wrapper" cursor="pointer" onClicked={() => expanded.set(!expanded.get())}> - <box className="header"> - <label className="icon" label="newspaper" /> - <label label="News" /> - <box hexpand /> - <label className="icon" label={bind(expanded).as(e => (e ? "expand_less" : "expand_more"))} /> - </box> - </button> - <revealer - revealChild={bind(expanded)} - transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN} - transitionDuration={200} - > - <label - wrap - useMarkup - xalign={0} - className="news" - label={bind(Palette.get_default(), "teal").as( - c => news.replaceAll("\x1b[36m", `<span foreground="${c}">`) // Replace color codes with html spans - )} - /> - </revealer> - </box> - ); -}; - -const List = () => ( - <box vertical valign={Gtk.Align.START} className="repos"> - {bind(Updates.get_default(), "updateData").as(d => - d.news - ? [<News news={d.news} />, ...d.repos.map(r => <Repo repo={r} />)] - : d.repos.map((r, i) => <Repo repo={r} first={i === 0} />) - )} - </box> -); - -export default () => ( - <PopdownWindow - name="updates" - count={bind(Updates.get_default(), "numUpdates")} - headerButtons={[ - { - label: "Update all", - onClicked: () => - execAsync("uwsm app -T -- yay") - .then(() => Updates.get_default().getUpdates()) - // Ignore errors - .catch(() => {}), - }, - { - label: bind(Updates.get_default(), "loading").as(l => (l ? "Loading" : "Reload")), - onClicked: () => Updates.get_default().getUpdates(), - enabled: bind(Updates.get_default(), "loading"), - }, - ]} - emptyIcon="deployed_code_history" - emptyLabel="All packages up to date!" - list={<List />} - /> -); |