From 97130dbe27ab85b759073d6290a7cfbd37b50ced Mon Sep 17 00:00:00 2001
From: Franek <me@sador.me>
Date: Sun, 27 Apr 2025 15:35:41 +0200
Subject: [PATCH] fix(ags): clipboard logic

---
 ags/styles/colors.scss                | 40 ++++++------
 ags/styles/components/_clipboard.scss | 27 ++++++---
 ags/styles/components/_launcher.scss  | 27 ++++-----
 ags/widget/launcher/clipboard.tsx     | 87 ++++++++++++++++-----------
 ags/widget/launcher/launcher.tsx      | 44 +++++++-------
 5 files changed, 124 insertions(+), 101 deletions(-)

diff --git a/ags/styles/colors.scss b/ags/styles/colors.scss
index 87f2e7b..dba36a0 100644
--- a/ags/styles/colors.scss
+++ b/ags/styles/colors.scss
@@ -1,26 +1,26 @@
 // SCSS Variables
 // Generated by 'wal'
-$wallpaper: "/home/sadorowo/images/wallpapers/youtube_Sheri_142.jpg";
+$wallpaper: "/home/sadorowo/images/wallpapers/youtube_Sheri_170.jpg";
 
 // Special
-$background: #f9f7f5;
-$foreground: #4C3A47;
-$cursor: #4C3A47;
+$background: #f9f6f3;
+$foreground: #251F23;
+$cursor: #251F23;
 
 // Colors
-$color0: #f9f7f5;
-$color1: #B77E8E;
-$color2: #6E8EAF;
-$color3: #138DCF;
-$color4: #609DD4;
-$color5: #A196AD;
-$color6: #D6ABA7;
-$color7: #4C3A47;
-$color8: #847974;
-$color9: #B77E8E;
-$color10: #6E8EAF;
-$color11: #138DCF;
-$color12: #609DD4;
-$color13: #A196AD;
-$color14: #D6ABA7;
-$color15: #4C3A47;
+$color0: #f9f6f3;
+$color1: #AB8772;
+$color2: #D69768;
+$color3: #727188;
+$color4: #897A85;
+$color5: #79869C;
+$color6: #A29597;
+$color7: #251F23;
+$color8: #82766c;
+$color9: #AB8772;
+$color10: #D69768;
+$color11: #727188;
+$color12: #897A85;
+$color13: #79869C;
+$color14: #A29597;
+$color15: #251F23;
diff --git a/ags/styles/components/_clipboard.scss b/ags/styles/components/_clipboard.scss
index ff67294..e17eadb 100644
--- a/ags/styles/components/_clipboard.scss
+++ b/ags/styles/components/_clipboard.scss
@@ -8,12 +8,20 @@
         border: none;
 
         margin-bottom: 6px;
-        
+
         &:focus {
             outline: 2px solid $color0;
         }
     }
 
+    .entry.primary {
+        background: $color3;
+
+        &:hover>.content {
+            color: $color3;
+        }
+    }
+
     .entry {
         padding: 10px;
         background: $color5;
@@ -22,17 +30,16 @@
 
         &:hover {
             background: $color0;
-        
-            box>.content {
+
+            >.content {
                 color: $color5;
             }
         }
-        box {
-            .content {
-                color: $color0;
-                font-size: 14px;
-                font-weight: 600;
-            }
+
+        .content {
+            color: $color0;
+            font-size: 14px;
+            font-weight: 600;
         }
     }
 
@@ -47,4 +54,4 @@
             margin-bottom: 8px;
         }
     }
-}
+}
\ No newline at end of file
diff --git a/ags/styles/components/_launcher.scss b/ags/styles/components/_launcher.scss
index d81eb4d..684a04f 100644
--- a/ags/styles/components/_launcher.scss
+++ b/ags/styles/components/_launcher.scss
@@ -8,7 +8,7 @@
         border: none;
 
         margin-bottom: 6px;
-        
+
         &:focus {
             outline: 2px solid $color0;
         }
@@ -22,8 +22,9 @@
 
         &:hover {
             background: $color0;
-        
-            box>.name, box>.description {
+
+            box>.name,
+            box>.description {
                 color: $color5;
             }
         }
@@ -32,17 +33,15 @@
             margin-right: 12px;
         }
 
-        box {
-            .name {
-                color: $color0;
-                font-size: 14px;
-                font-weight: 600;
-            }
+        .name {
+            color: $color0;
+            font-size: 14px;
+            font-weight: 600;
+        }
 
-            .description {
-                color: $color0;
-                font-size: 12px;
-            }
+        .description {
+            color: $color0;
+            font-size: 12px;
         }
     }
 
@@ -57,4 +56,4 @@
             margin-bottom: 8px;
         }
     }
-}
+}
\ No newline at end of file
diff --git a/ags/widget/launcher/clipboard.tsx b/ags/widget/launcher/clipboard.tsx
index 791bbc7..96d9661 100644
--- a/ags/widget/launcher/clipboard.tsx
+++ b/ags/widget/launcher/clipboard.tsx
@@ -17,55 +17,61 @@ function ClipboardEntry({ id, content }: Entry) {
             execAsync([SHELL, "-c", `cliphist decode ${id} | wl-copy`]);
         }}
     >
-        <box>
-            <label
-                cssClasses={["content"]}
-                xalign={0}
-                label={content}
-            />
-        </box>
+        <label
+            cssClasses={["content"]}
+            xalign={0}
+            label={content}
+        />
     </button>
 }
 
-async function getClipboardHistory() {
+async function getClipboardHistory(history: Variable<Entry[]>) {
     try {
         const ids = await execAsync([SHELL, "-c", "cliphist list | awk '{print $1}'"])
             .then(it => it.split("\n"));
         const contents = await execAsync([SHELL, "-c", "cliphist list | awk '{$1=\"\"; print}'"])
             .then(it => it.split("\n"));
 
-        return ids
+        history.set(ids
             .map((id, index) => ({ id, content: contents[index] }))
-            .filter(({ content }) => content && content.length > 0);
+            .filter(({ content }) => content && content.length > 0));
     } catch {
-        return [];
+        history.set([]);
     }
 }
 
 export default async function Clipboard(_monitor_id: number) {
     const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor;
-
-    const history = await getClipboardHistory();
     const hypr = Hyprland.get_default();
-    
+
+    const history: Variable<Entry[]> = Variable([]);
     const toSkip = Variable(0);
-    const list = bind(toSkip).as(count => limit(
-        skip(history, count), 
+
+    const list = Variable.derive([
+        bind(history),
+        bind(toSkip)
+    ], (history, count) => limit(
+        skip(history, count),
         10
     ));
 
+    const isEmpty = bind(list).as(list => list.length === 0);
     const onScroll = (dy: number) => {
         const value = toSkip.get();
-        
+
         if (dy < 0) {
             if ((value - 10) < 0) return;
             toSkip.set(value - 10)
         } else {
-            if ((value + 10) > history.length) return;
+            if ((value + 10) > history.get().length) return;
             toSkip.set(value + 10)
         }
     };
 
+    const setup = (self: Gtk.Window) => self.connect('notify::visible', async () => {
+        await getClipboardHistory(history)
+    })
+
     return <window
         visible={false}
         monitor={bind(hypr, "focusedMonitor").as(monitor => monitor.id)}
@@ -74,22 +80,35 @@ export default async function Clipboard(_monitor_id: number) {
         keymode={Astal.Keymode.EXCLUSIVE}
         anchor={TOP | BOTTOM | LEFT | RIGHT}
         onKeyPressed={hideWindow}
-        application={App}>
-        <box hexpand={false} vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}>
-            <box cssClasses={["clipboard"]} vertical>
-                <box spacing={6} vertical onScroll={(_, __, dy) => onScroll(dy)}>
-                    {list.as(list => list.map(({ id, content }) => (
-                        <ClipboardEntry id={id} content={content} />
-                    )))}
-                </box>
-                <box
-                    halign={Gtk.Align.CENTER}
-                    cssClasses={["not-found"]}
-                    vertical
-                    visible={list.as(it => it.length === 0)}>
-                    <image iconName="system-search-symbolic" />
-                    <label label="No match found" />
-                </box>
+        application={App}
+        setup={setup}>
+        <box hexpand={false} cssClasses={["clipboard"]} vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}>
+            <box spacing={6} vertical onScroll={(_, __, dy) => onScroll(dy)}>
+                {bind(list).as(list => list.map(({ id, content }) => (
+                    <ClipboardEntry id={id} content={content} />
+                )))}
+                <button
+                    cssClasses={["entry", "primary"]}
+                    visible={isEmpty.as(empty => !empty)}
+                    onClicked={() => {
+                        execAsync([SHELL, "-c", "cliphist wipe"])
+                        history.set([])
+                    }}
+                >
+                    <label
+                        cssClasses={["content"]}
+                        xalign={0}
+                        label="Wipe clipboard history"
+                    />
+                </button>
+            </box>
+            <box
+                halign={Gtk.Align.CENTER}
+                cssClasses={["not-found"]}
+                vertical
+                visible={isEmpty}>
+                <image iconName="system-search-symbolic" />
+                <label label="No match found" />
             </box>
         </box>
     </window>
diff --git a/ags/widget/launcher/launcher.tsx b/ags/widget/launcher/launcher.tsx
index 0e73568..9385c88 100644
--- a/ags/widget/launcher/launcher.tsx
+++ b/ags/widget/launcher/launcher.tsx
@@ -12,8 +12,8 @@ function AppButton({ app }: { app: Apps.Application }) {
         cssClasses={["application"]}
         onClicked={() => {
             hide();
-        	app.launch();
-		}}
+            app.launch();
+        }}
     >
         <box>
             <image iconName={app.iconName} />
@@ -37,7 +37,7 @@ function AppButton({ app }: { app: Apps.Application }) {
 export default function Launcher(_monitor_id: number) {
     const { TOP, BOTTOM, LEFT, RIGHT } = Astal.WindowAnchor;
 
-	const hypr = Hyprland.get_default();
+    const hypr = Hyprland.get_default();
     const apps = new Apps.Apps();
 
     const query = Variable("")
@@ -56,26 +56,24 @@ export default function Launcher(_monitor_id: number) {
         anchor={TOP | BOTTOM | LEFT | RIGHT}
         onKeyPressed={hideWindow}
         application={App}>
-        <box hexpand={false} vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}>
-            <box cssClasses={["launcher"]} vertical>
-                <entry
-                    placeholderText="Search"
-                    onChanged={self => query.set(self.text)}
-                    onActivate={onEnter}
-                />
-                <box spacing={6} vertical>
-                    {list.as(list => list.map(app => (
-                        <AppButton app={app} />
-                    )))}
-                </box>
-                <box
-                    halign={Gtk.Align.CENTER}
-                    cssClasses={["not-found"]}
-                    vertical
-                    visible={list.as(l => l.length === 0)}>
-                    <image iconName="system-search-symbolic" />
-                    <label label="No match found" />
-                </box>
+        <box hexpand={false} cssClasses={["launcher"]} vertical halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}>
+            <entry
+                placeholderText="Search"
+                onChanged={self => query.set(self.text)}
+                onActivate={onEnter}
+            />
+            <box spacing={6} vertical>
+                {list.as(list => list.map(app => (
+                    <AppButton app={app} />
+                )))}
+            </box>
+            <box
+                halign={Gtk.Align.CENTER}
+                cssClasses={["not-found"]}
+                vertical
+                visible={list.as(l => l.length === 0)}>
+                <image iconName="system-search-symbolic" />
+                <label label="No match found" />
             </box>
         </box>
     </window>