summaryrefslogtreecommitdiff
path: root/src/modules/sidebar/dashboard.tsx
blob: 1a8626fd62e43b4f27722457820acb3bece81fb5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import Players from "@/services/players";
import { lengthStr } from "@/utils/strings";
import { bindCurrentTime, osIcon } from "@/utils/system";
import Slider from "@/widgets/slider";
import { bind, GLib, monitorFile, Variable } from "astal";
import { Gtk } from "astal/gtk3";
import AstalMpris from "gi://AstalMpris";
import Notifications from "./modules/notifications";
import Upcoming from "./modules/upcoming";

const noNull = (s: string | null) => s ?? "-";

const FaceFallback = () => (
    <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 = "";
                self.xalign = 0.44;
            }
        }}
    />
);

const User = () => {
    const uptime = Variable("").poll(5000, "uptime -p");
    const hasFace = Variable(GLib.file_test(HOME + "/.face", GLib.FileTest.EXISTS));

    return (
        <box className="user">
            <box
                homogeneous
                className="face"
                setup={self => {
                    self.css = `background-image: url("${HOME}/.face");`;
                    const monitor = monitorFile(HOME + "/.face", () => {
                        hasFace.set(GLib.file_test(HOME + "/.face", GLib.FileTest.EXISTS));
                        self.css = `background-image: url("${HOME}/.face");`;
                    });
                    self.connect("destroy", () => monitor.cancel());
                }}
            >
                {bind(hasFace).as(h => (h ? <box visible={false} /> : <FaceFallback />))}
            </box>
            <box vertical hexpand valign={Gtk.Align.CENTER} className="details">
                <label truncate xalign={0} className="name" label={`${osIcon} ${GLib.get_user_name()}`} />
                <label truncate xalign={0} label={bind(uptime)} onDestroy={() => uptime.drop()} />
                <label truncate xalign={0} label={bindCurrentTime("%A, %e %B")} />
            </box>
        </box>
    );
};

const Media = ({ player }: { player: AstalMpris.Player | null }) => {
    const position = player
        ? Variable.derive([bind(player, "position"), bind(player, "length")], (p, l) => p / l)
        : Variable(0);

    return (
        <box className="media" onDestroy={() => position.drop()}>
            <box
                homogeneous
                className="cover-art"
                css={player ? bind(player, "coverArt").as(a => `background-image: url("${a}");`) : ""}
            >
                {player ? (
                    bind(player, "coverArt").as(a => (a ? <box visible={false} /> : <label xalign={0.31} label="" />))
                ) : (
                    <label xalign={0.31} label="" />
                )}
            </box>
            <box vertical className="details">
                <label truncate className="title" label={player ? bind(player, "title").as(noNull) : ""} />
                <label truncate className="artist" label={player ? bind(player, "artist").as(noNull) : "No media"} />
                <box hexpand className="controls">
                    <button
                        hexpand
                        sensitive={player ? bind(player, "canGoPrevious") : false}
                        cursor="pointer"
                        onClicked={() => player?.next()}
                        label="󰒮"
                    />
                    <button
                        hexpand
                        sensitive={player ? bind(player, "canControl") : false}
                        cursor="pointer"
                        onClicked={() => player?.play_pause()}
                        label={
                            player
                                ? bind(player, "playbackStatus").as(s =>
                                      s === AstalMpris.PlaybackStatus.PLAYING ? "󰏤" : "󰐊"
                                  )
                                : "󰐊"
                        }
                    />
                    <button
                        hexpand
                        sensitive={player ? bind(player, "canGoNext") : false}
                        cursor="pointer"
                        onClicked={() => player?.next()}
                        label="󰒭"
                    />
                </box>
                <Slider value={bind(position)} onChange={(_, v) => player?.set_position(v * player.length)} />
                <box className="time">
                    <label label={player ? bind(player, "position").as(lengthStr) : "-1:-1"} />
                    <box hexpand />
                    <label label={player ? bind(player, "length").as(lengthStr) : "-1:-1"} />
                </box>
            </box>
        </box>
    );
};

export default () => (
    <box vertical className="pane dashboard" name="dashboard">
        <User />
        <box className="separator" />
        {bind(Players.get_default(), "lastPlayer").as(p => (
            <Media player={p} />
        ))}
        <box className="separator" />
        <Notifications compact />
        <box className="separator" />
        <Upcoming />
    </box>
);