cleanup: remove popdown stuff
Popdowns were replaced with the sidebar
This commit is contained in:
parent
dcc3c1fd4c
commit
e9eba2e291
19 changed files with 0 additions and 1612 deletions
|
|
@ -1,51 +0,0 @@
|
|||
@use "sass:color";
|
||||
@use "../scheme";
|
||||
@use "../lib";
|
||||
@use "../font";
|
||||
|
||||
$-accent: scheme.$rosewater;
|
||||
|
||||
.bluetooth-devices {
|
||||
@include lib.popdown-window($-accent);
|
||||
|
||||
min-width: lib.s(500);
|
||||
min-height: lib.s(400);
|
||||
|
||||
.list {
|
||||
@include lib.spacing($vertical: true);
|
||||
|
||||
color: scheme.$text;
|
||||
|
||||
.device {
|
||||
@include lib.spacing(8);
|
||||
|
||||
.icon {
|
||||
font-size: lib.s(18);
|
||||
}
|
||||
|
||||
button {
|
||||
@include lib.rounded(5);
|
||||
@include lib.element-decel;
|
||||
|
||||
padding: lib.s(3) lib.s(8);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: scheme.$surface0;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: scheme.$surface1;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: scheme.$subtext0;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $-accent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
@forward "notifications";
|
||||
@forward "updates";
|
||||
@forward "bluetoothdevices";
|
||||
@forward "networks";
|
||||
@forward "media";
|
||||
@forward "sideright";
|
||||
@forward "sideleft";
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
@use "sass:color";
|
||||
@use "../scheme";
|
||||
@use "../lib";
|
||||
@use "../font";
|
||||
|
||||
$-accent: scheme.$lavender;
|
||||
$-accent2: scheme.$pink;
|
||||
|
||||
.media {
|
||||
@include lib.rounded(8);
|
||||
@include lib.border($-accent, 0.4, 2);
|
||||
@include lib.shadow;
|
||||
@include font.mono;
|
||||
|
||||
background-color: scheme.$mantle;
|
||||
color: $-accent;
|
||||
padding: lib.s(12);
|
||||
font-size: lib.s(14);
|
||||
min-width: lib.s(500);
|
||||
min-height: lib.s(220);
|
||||
|
||||
.icon {
|
||||
font-size: lib.s(32);
|
||||
}
|
||||
|
||||
.overlay {
|
||||
@include lib.rounded(5);
|
||||
|
||||
background-color: color.change(scheme.$mantle, $alpha: 0.8);
|
||||
}
|
||||
|
||||
.background {
|
||||
@include lib.rounded(5);
|
||||
|
||||
transition: background 300ms ease-in-out;
|
||||
background-color: scheme.$surface0;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.player {
|
||||
padding: lib.s(35) lib.s(12);
|
||||
font-weight: bold;
|
||||
|
||||
@include lib.spacing(10, true);
|
||||
|
||||
.title {
|
||||
font-size: lib.s(18);
|
||||
}
|
||||
|
||||
.artist {
|
||||
color: scheme.$green;
|
||||
}
|
||||
|
||||
.album {
|
||||
color: $-accent2;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: lib.s(3);
|
||||
|
||||
@include lib.spacing(10);
|
||||
|
||||
button {
|
||||
@include lib.rounded(4);
|
||||
@include lib.element-decel;
|
||||
@include lib.border(scheme.$base, 0.7);
|
||||
|
||||
padding: lib.s(5) lib.s(12);
|
||||
font-size: lib.s(18);
|
||||
background-color: $-accent;
|
||||
color: scheme.$base;
|
||||
|
||||
&:disabled {
|
||||
background-color: scheme.$overlay2;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: color.change($-accent, $alpha: 0.8);
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: color.change($-accent, $alpha: 0.6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Progress bar
|
||||
trough {
|
||||
@include lib.rounded(3);
|
||||
|
||||
margin-top: lib.s(10);
|
||||
background-color: scheme.$surface0;
|
||||
|
||||
highlight {
|
||||
@include lib.rounded(3);
|
||||
|
||||
background-color: $-accent2;
|
||||
padding: lib.s(3) 0;
|
||||
}
|
||||
|
||||
slider {
|
||||
@include lib.rounded(3);
|
||||
|
||||
background-color: scheme.$overlay0;
|
||||
min-width: lib.s(15);
|
||||
min-height: lib.s(15);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
@use "sass:color";
|
||||
@use "../scheme";
|
||||
@use "../lib";
|
||||
@use "../font";
|
||||
|
||||
$-accent: scheme.$rosewater;
|
||||
|
||||
.networks {
|
||||
@include lib.popdown-window($-accent);
|
||||
|
||||
min-width: lib.s(500);
|
||||
min-height: lib.s(400);
|
||||
|
||||
.list {
|
||||
@include lib.spacing($vertical: true);
|
||||
|
||||
color: scheme.$text;
|
||||
|
||||
.network {
|
||||
@include lib.spacing(8);
|
||||
|
||||
.icon {
|
||||
font-size: lib.s(18);
|
||||
}
|
||||
|
||||
button {
|
||||
@include lib.rounded(5);
|
||||
@include lib.element-decel;
|
||||
|
||||
padding: lib.s(3) lib.s(8);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: scheme.$surface0;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: scheme.$surface1;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: scheme.$subtext0;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: $-accent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
@use "sass:color";
|
||||
@use "../scheme";
|
||||
@use "../lib";
|
||||
@use "../font";
|
||||
|
||||
@mixin popup($accent) {
|
||||
.separator {
|
||||
background-color: $accent;
|
||||
}
|
||||
|
||||
.image {
|
||||
@include lib.border($accent, 0.05);
|
||||
}
|
||||
}
|
||||
|
||||
.notifications {
|
||||
@include lib.popdown-window(scheme.$mauve);
|
||||
|
||||
min-width: lib.s(400);
|
||||
min-height: lib.s(600);
|
||||
|
||||
.notification {
|
||||
.wrapper {
|
||||
padding-bottom: lib.s(10);
|
||||
}
|
||||
|
||||
.inner {
|
||||
background-color: color.mix(scheme.$base, scheme.$surface0, 70%);
|
||||
|
||||
&.low {
|
||||
@include popup(scheme.$overlay0);
|
||||
}
|
||||
|
||||
&.normal {
|
||||
@include lib.border(scheme.$primary, 0.3);
|
||||
@include popup(scheme.$primary);
|
||||
}
|
||||
|
||||
&.critical {
|
||||
@include lib.border(scheme.$error, 0.8);
|
||||
@include popup(scheme.$error);
|
||||
}
|
||||
}
|
||||
|
||||
.actions > * {
|
||||
background-color: scheme.$surface0;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: scheme.$surface1;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: scheme.$surface2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
@use "sass:color";
|
||||
@use "../scheme";
|
||||
@use "../lib";
|
||||
@use "../font";
|
||||
|
||||
$-accent: scheme.$yellow;
|
||||
|
||||
.sideleft {
|
||||
@include lib.rounded(8);
|
||||
@include lib.border($-accent, 0.4, 2);
|
||||
@include lib.shadow;
|
||||
@include font.mono;
|
||||
|
||||
background-color: scheme.$crust;
|
||||
color: $-accent;
|
||||
padding: lib.s(12);
|
||||
font-size: lib.s(14);
|
||||
|
||||
@include lib.spacing(10, true);
|
||||
|
||||
& > * {
|
||||
@include lib.rounded(5);
|
||||
|
||||
background-color: scheme.$base;
|
||||
padding: lib.s(16) lib.s(20);
|
||||
}
|
||||
|
||||
.user {
|
||||
@include lib.spacing(15);
|
||||
|
||||
.face {
|
||||
@include lib.rounded(1000);
|
||||
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
min-width: lib.s(48);
|
||||
min-height: lib.s(48);
|
||||
font-size: lib.s(24);
|
||||
}
|
||||
|
||||
.details {
|
||||
font-size: lib.s(14);
|
||||
|
||||
@include lib.spacing(3, true);
|
||||
|
||||
.name {
|
||||
font-size: lib.s(18);
|
||||
color: scheme.$text;
|
||||
}
|
||||
}
|
||||
|
||||
.power {
|
||||
@include lib.element-decel;
|
||||
@include font.icon;
|
||||
|
||||
font-size: lib.s(24);
|
||||
color: scheme.$red;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: color.mix(scheme.$red, scheme.$base, 80%);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: color.mix(scheme.$red, scheme.$base, 60%);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.locations {
|
||||
@include lib.spacing(30);
|
||||
|
||||
& > * {
|
||||
@include lib.spacing(8, true);
|
||||
}
|
||||
|
||||
$-colours: scheme.$pink, scheme.$yellow, scheme.$maroon, scheme.$green, scheme.$mauve, scheme.$lavender;
|
||||
@for $i from 1 through 6 {
|
||||
.loc#{$i} {
|
||||
@include lib.element-decel;
|
||||
|
||||
color: nth($-colours, $i);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: color.mix(nth($-colours, $i), scheme.$base, 80%);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: color.mix(nth($-colours, $i), scheme.$base, 60%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hw-resources {
|
||||
@include lib.spacing(10, true);
|
||||
|
||||
.resource {
|
||||
font-size: lib.s(13);
|
||||
|
||||
.inner {
|
||||
font-size: lib.s(16);
|
||||
|
||||
@include lib.spacing(20);
|
||||
}
|
||||
|
||||
.slider {
|
||||
@include lib.rounded(5);
|
||||
@include lib.fluent-decel(1000ms);
|
||||
|
||||
min-height: lib.s(10);
|
||||
background-color: scheme.$surface0;
|
||||
color: scheme.$red;
|
||||
}
|
||||
|
||||
$-names: gpu, cpu, memory, storage;
|
||||
$-colours: scheme.$green, scheme.$maroon, scheme.$yellow, scheme.$pink;
|
||||
@for $i from 1 through length($-names) {
|
||||
&.#{nth($-names, $i)} {
|
||||
color: nth($-colours, $i);
|
||||
|
||||
.slider {
|
||||
color: nth($-colours, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
@use "sass:color";
|
||||
@use "../scheme";
|
||||
@use "../lib";
|
||||
@use "../font";
|
||||
|
||||
$-accent: scheme.$peach;
|
||||
$-accent2: scheme.$blue;
|
||||
|
||||
.sideright {
|
||||
@include lib.rounded(8);
|
||||
@include lib.border($-accent, 0.4, 2);
|
||||
@include lib.shadow;
|
||||
@include font.mono;
|
||||
|
||||
background-color: scheme.$crust;
|
||||
color: $-accent;
|
||||
padding: lib.s(12);
|
||||
font-size: lib.s(14);
|
||||
|
||||
@include lib.spacing(10, true);
|
||||
|
||||
& > * {
|
||||
@include lib.rounded(5);
|
||||
|
||||
background-color: scheme.$base;
|
||||
}
|
||||
|
||||
.time {
|
||||
padding: lib.s(12) lib.s(8);
|
||||
font-size: lib.s(48);
|
||||
font-weight: bold;
|
||||
|
||||
& > * {
|
||||
@include lib.spacing(3);
|
||||
}
|
||||
|
||||
.ampm {
|
||||
font-size: lib.s(24);
|
||||
font-weight: normal;
|
||||
margin-top: lib.s(18);
|
||||
color: $-accent2;
|
||||
}
|
||||
}
|
||||
|
||||
.calendar {
|
||||
padding: lib.s(5) lib.s(8) 0 lib.s(8);
|
||||
min-width: lib.s(350);
|
||||
color: scheme.$text;
|
||||
|
||||
calendar {
|
||||
padding: lib.s(8); // Padding for each day
|
||||
|
||||
// Month and year
|
||||
&.header {
|
||||
color: $-accent2;
|
||||
}
|
||||
|
||||
// Change month/year buttons
|
||||
&.button {
|
||||
&:hover {
|
||||
color: scheme.$subtext0;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
color: color.mix(scheme.$text, scheme.$base, 65%);
|
||||
}
|
||||
}
|
||||
|
||||
// Weekday labels
|
||||
&.highlight {
|
||||
color: $-accent;
|
||||
}
|
||||
|
||||
// Days in other month
|
||||
&:indeterminate {
|
||||
color: color.mix(scheme.$text, scheme.$base, 65%);
|
||||
}
|
||||
|
||||
// Current day
|
||||
&:selected {
|
||||
$base: color.change(scheme.$base, $alpha: 1);
|
||||
box-shadow: inset 0 lib.s(-12) 0 0 $-accent, inset 0 lib.s(-9) 0 0 $base,
|
||||
inset lib.s(-10) lib.s(-3) 0 0 $base, inset lib.s(10) 0 0 lib.s(0.1) $base;
|
||||
color: $-accent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.weather {
|
||||
padding: lib.s(5) lib.s(15);
|
||||
|
||||
@include lib.spacing(10, true);
|
||||
|
||||
.current {
|
||||
@include lib.spacing(20);
|
||||
|
||||
.status-icon {
|
||||
font-size: lib.s(64);
|
||||
padding: lib.s(10) lib.s(20) 0 0;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: $-accent2;
|
||||
font-size: lib.s(16);
|
||||
|
||||
@include lib.spacing($vertical: true);
|
||||
|
||||
.temperature {
|
||||
font-size: lib.s(28);
|
||||
font-weight: bold;
|
||||
color: scheme.$text;
|
||||
|
||||
@include lib.spacing;
|
||||
|
||||
.temp-icon {
|
||||
font-size: lib.s(30);
|
||||
|
||||
$-temps: freezing, cold, normal, hot, burning;
|
||||
$-colours: scheme.$sky, scheme.$blue, scheme.$green, scheme.$yellow, scheme.$red;
|
||||
@for $i from 1 through length($-temps) {
|
||||
&.#{nth($-temps, $i)} {
|
||||
color: nth($-colours, $i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
background-color: scheme.$surface0;
|
||||
}
|
||||
|
||||
.forecast {
|
||||
padding: lib.s(10) 0 lib.s(15) 0;
|
||||
|
||||
@include lib.spacing(10);
|
||||
|
||||
.hour {
|
||||
@include lib.spacing($vertical: true);
|
||||
|
||||
.icon {
|
||||
font-size: lib.s(32);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
@use "sass:color";
|
||||
@use "../scheme";
|
||||
@use "../lib";
|
||||
@use "../font";
|
||||
|
||||
$-accent: scheme.$blue;
|
||||
|
||||
.updates {
|
||||
@include lib.popdown-window($-accent);
|
||||
|
||||
min-width: lib.s(550);
|
||||
min-height: lib.s(800);
|
||||
|
||||
.wrapper {
|
||||
@include lib.element-decel;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
color: color.mix($-accent, scheme.$base, 80%);
|
||||
}
|
||||
|
||||
&:active {
|
||||
color: color.mix($-accent, scheme.$base, 60%);
|
||||
}
|
||||
}
|
||||
|
||||
.repos {
|
||||
@include lib.spacing($vertical: true);
|
||||
|
||||
.repo {
|
||||
.header {
|
||||
font-size: lib.s(16);
|
||||
}
|
||||
|
||||
.list,
|
||||
.news {
|
||||
color: scheme.$text;
|
||||
margin-left: lib.s(12);
|
||||
}
|
||||
|
||||
.list {
|
||||
@include lib.spacing($vertical: true);
|
||||
}
|
||||
|
||||
.news {
|
||||
margin-bottom: lib.s(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -102,20 +102,6 @@ export default {
|
|||
sidebar: {
|
||||
showOnStartup: false,
|
||||
},
|
||||
sideleft: {
|
||||
directories: {
|
||||
left: {
|
||||
top: " Downloads",
|
||||
middle: " Documents",
|
||||
bottom: " Music",
|
||||
},
|
||||
right: {
|
||||
top: " Pictures",
|
||||
middle: " Videos",
|
||||
bottom: " Home",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Services
|
||||
math: {
|
||||
maxHistory: 100,
|
||||
|
|
|
|||
|
|
@ -54,13 +54,6 @@ export default {
|
|||
"osds.lock.num.hideDelay": NUM,
|
||||
// Sidebar
|
||||
"sidebar.showOnStartup": BOOL,
|
||||
// Sideleft
|
||||
"sideleft.directories.left.top": STR,
|
||||
"sideleft.directories.left.middle": STR,
|
||||
"sideleft.directories.left.bottom": STR,
|
||||
"sideleft.directories.right.top": STR,
|
||||
"sideleft.directories.right.middle": STR,
|
||||
"sideleft.directories.right.bottom": STR,
|
||||
// Services
|
||||
"math.maxHistory": NUM,
|
||||
"updates.interval": NUM,
|
||||
|
|
|
|||
|
|
@ -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 />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 />}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
|
@ -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 />}
|
||||
/>
|
||||
);
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
@ -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>
|
||||
);
|
||||
|
|
@ -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 />}
|
||||
/>
|
||||
);
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
import type { Binding } from "astal";
|
||||
import { Gtk } from "astal/gtk3";
|
||||
import PopupWindow from "./popupwindow";
|
||||
|
||||
export default ({
|
||||
name,
|
||||
count,
|
||||
countLabel = count.as(c => `${c} ${name.slice(0, -1)}${c === 1 ? "" : "s"}`),
|
||||
headerButtons,
|
||||
emptyIcon,
|
||||
emptyLabel,
|
||||
list,
|
||||
}: {
|
||||
name: string;
|
||||
count: Binding<number>;
|
||||
countLabel?: Binding<string>;
|
||||
headerButtons: { label: string | Binding<string>; onClicked: () => void; enabled?: Binding<boolean> }[];
|
||||
emptyIcon: string;
|
||||
emptyLabel: string | Binding<string>;
|
||||
list: JSX.Element;
|
||||
}) => (
|
||||
<PopupWindow name={name}>
|
||||
<box vertical className={name}>
|
||||
<box className="header">
|
||||
<label label={countLabel} />
|
||||
<box hexpand />
|
||||
{headerButtons.map(({ label, onClicked, enabled }) => (
|
||||
<button
|
||||
cursor="pointer"
|
||||
onClicked={onClicked}
|
||||
label={label}
|
||||
className={enabled?.as(d => (d ? "enabled" : ""))}
|
||||
/>
|
||||
))}
|
||||
</box>
|
||||
<stack
|
||||
transitionType={Gtk.StackTransitionType.CROSSFADE}
|
||||
transitionDuration={150}
|
||||
shown={count.as(c => (c > 0 ? "list" : "empty"))}
|
||||
>
|
||||
<box vertical valign={Gtk.Align.CENTER} name="empty">
|
||||
<label className="icon" label={emptyIcon} />
|
||||
<label label={emptyLabel} />
|
||||
</box>
|
||||
<scrollable expand hscroll={Gtk.PolicyType.NEVER} name="list">
|
||||
{list}
|
||||
</scrollable>
|
||||
</stack>
|
||||
</box>
|
||||
</PopupWindow>
|
||||
);
|
||||
Loading…
Add table
Reference in a new issue