From 02fd2e97f2c8a53bf2344e6fa8b14769cb15ee38 Mon Sep 17 00:00:00 2001
From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>
Date: Thu, 16 Jan 2025 16:35:37 +1100
Subject: refactor: move ts to src

Also move popupwindow to own file
---
 modules/launcher.tsx | 390 ---------------------------------------------------
 1 file changed, 390 deletions(-)
 delete mode 100644 modules/launcher.tsx

(limited to 'modules/launcher.tsx')

diff --git a/modules/launcher.tsx b/modules/launcher.tsx
deleted file mode 100644
index 966cdfd..0000000
--- a/modules/launcher.tsx
+++ /dev/null
@@ -1,390 +0,0 @@
-import { bind, execAsync, Gio, GLib, register, timeout, Variable } from "astal";
-import { Astal, Gtk, Widget } from "astal/gtk3";
-import fuzzysort from "fuzzysort";
-import type AstalApps from "gi://AstalApps";
-import AstalHyprland from "gi://AstalHyprland";
-import { launcher as config } from "../config";
-import { Apps } from "../services/apps";
-import Math, { type HistoryItem } from "../services/math";
-import { getAppCategoryIcon } from "../utils/icons";
-import { launch } from "../utils/system";
-import { PopupWindow, setupCustomTooltip } from "../utils/widgets";
-
-type Mode = "apps" | "files" | "math";
-
-interface Subcommand {
-    icon: string;
-    name: string;
-    description: string;
-    command: (...args: string[]) => void;
-}
-
-const getIconFromMode = (mode: Mode) => {
-    switch (mode) {
-        case "apps":
-            return "apps";
-        case "files":
-            return "folder";
-        case "math":
-            return "calculate";
-    }
-};
-
-const getEmptyTextFromMode = (mode: Mode) => {
-    switch (mode) {
-        case "apps":
-            return "No apps found";
-        case "files":
-            return GLib.find_program_in_path("fd") === null ? "File search requires `fd`" : "No files found";
-        case "math":
-            return "Type an expression";
-    }
-};
-
-const close = (self: JSX.Element) => {
-    const toplevel = self.get_toplevel();
-    if (toplevel instanceof Widget.Window) toplevel.hide();
-};
-
-const launchAndClose = (self: JSX.Element, astalApp: AstalApps.Application) => {
-    close(self);
-    launch(astalApp);
-};
-
-const openFileAndClose = (self: JSX.Element, path: string) => {
-    close(self);
-    execAsync([
-        "bash",
-        "-c",
-        `dbus-send --session --dest=org.freedesktop.FileManager1 --type=method_call /org/freedesktop/FileManager1 org.freedesktop.FileManager1.ShowItems array:string:"file://${path}" string:"" || xdg-open "${path}"`,
-    ]).catch(console.error);
-};
-
-const PinnedApp = (names: string[]) => {
-    let app: Gio.DesktopAppInfo | null = null;
-    let astalApp: AstalApps.Application | undefined;
-    for (const name of names) {
-        app = Gio.DesktopAppInfo.new(`${name}.desktop`);
-        if (app) {
-            astalApp = Apps.get_list().find(a => a.entry === `${name}.desktop`);
-            if (app.get_icon() && astalApp) break;
-            else app = null; // Set app to null if no icon or matching AstalApps#Application
-        }
-    }
-
-    if (!app) console.error(`Launcher - Unable to find app for "${names.join(", ")}"`);
-
-    return app ? (
-        <button
-            className="pinned-app result"
-            cursor="pointer"
-            onClicked={self => launchAndClose(self, astalApp!)}
-            setup={self => setupCustomTooltip(self, app.get_display_name())}
-        >
-            <icon gicon={app.get_icon()!} />
-        </button>
-    ) : null;
-};
-
-const PinnedApps = () => <box homogeneous>{config.pins.map(PinnedApp)}</box>;
-
-const SearchEntry = ({ entry }: { entry: Widget.Entry }) => (
-    <stack
-        hexpand
-        transitionType={Gtk.StackTransitionType.CROSSFADE}
-        transitionDuration={150}
-        setup={self =>
-            self.hook(entry, "notify::text-length", () =>
-                // Timeout to avoid flickering when replacing entire text (cause it'll set len to 0 then back to > 0)
-                timeout(1, () => (self.shown = entry.textLength > 0 ? "entry" : "placeholder"))
-            )
-        }
-    >
-        <label name="placeholder" className="placeholder" xalign={0} label='Type ">" for subcommands' />
-        {entry}
-    </stack>
-);
-
-const Result = ({
-    icon,
-    materialIcon,
-    label,
-    sublabel,
-    onClicked,
-}: {
-    icon?: string;
-    materialIcon?: string;
-    label: string;
-    sublabel?: string;
-    onClicked: (self: Widget.Button) => void;
-}) => (
-    <button className="result" cursor="pointer" onClicked={onClicked}>
-        <box>
-            {icon && Astal.Icon.lookup_icon(icon) ? (
-                <icon valign={Gtk.Align.START} className="icon" icon={icon} />
-            ) : (
-                <label valign={Gtk.Align.START} className="icon" label={materialIcon} />
-            )}
-            {sublabel ? (
-                <box vertical valign={Gtk.Align.CENTER} className="has-sublabel">
-                    <label hexpand truncate maxWidthChars={1} xalign={0} label={label} />
-                    <label hexpand truncate maxWidthChars={1} className="sublabel" xalign={0} label={sublabel} />
-                </box>
-            ) : (
-                <label xalign={0} label={label} />
-            )}
-        </box>
-    </button>
-);
-
-const SubcommandResult = ({
-    entry,
-    subcommand,
-    args,
-}: {
-    entry: Widget.Entry;
-    subcommand: Subcommand;
-    args: string[];
-}) => (
-    <Result
-        materialIcon={subcommand.icon}
-        label={subcommand.name}
-        sublabel={subcommand.description}
-        onClicked={() => {
-            subcommand.command(...args);
-            entry.set_text("");
-        }}
-    />
-);
-
-const AppResult = ({ app }: { app: AstalApps.Application }) => (
-    <Result
-        icon={app.iconName}
-        materialIcon={getAppCategoryIcon(app)}
-        label={app.name}
-        sublabel={app.description}
-        onClicked={self => launchAndClose(self, app)}
-    />
-);
-
-const MathResult = ({ math, isHistory, entry }: { math: HistoryItem; isHistory?: boolean; entry: Widget.Entry }) => (
-    <Result
-        materialIcon={math.icon}
-        label={math.equation}
-        sublabel={math.result}
-        onClicked={() => {
-            if (isHistory) {
-                Math.get_default().select(math);
-                entry.set_text(math.equation);
-                entry.grab_focus();
-                entry.set_position(-1);
-            } else {
-                execAsync(`wl-copy -- ${math.result}`).catch(console.error);
-                entry.set_text("");
-            }
-        }}
-    />
-);
-
-const FileResult = ({ path }: { path: string }) => (
-    <Result
-        label={path.split("/").pop()!}
-        sublabel={path.startsWith(HOME) ? "~" + path.slice(HOME.length) : path}
-        onClicked={self => openFileAndClose(self, path)}
-    />
-);
-
-const Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> }) => {
-    const empty = Variable(true);
-
-    return (
-        <stack
-            className="results"
-            transitionType={Gtk.StackTransitionType.CROSSFADE}
-            transitionDuration={150}
-            shown={bind(empty).as(t => (t ? "empty" : "list"))}
-        >
-            <box name="empty" className="empty" halign={Gtk.Align.CENTER} valign={Gtk.Align.CENTER}>
-                <label className="icon" label="bug_report" />
-                <label
-                    label={bind(entry, "text").as(t =>
-                        t.startsWith(">") ? "No matching subcommands" : getEmptyTextFromMode(mode.get())
-                    )}
-                />
-            </box>
-            <box
-                vertical
-                name="list"
-                setup={self => {
-                    const subcommands: Record<string, Subcommand> = {
-                        apps: {
-                            icon: "apps",
-                            name: "Apps",
-                            description: "Search for apps",
-                            command: () => mode.set("apps"),
-                        },
-                        files: {
-                            icon: "folder",
-                            name: "Files",
-                            description: "Search for files",
-                            command: () => mode.set("files"),
-                        },
-                        math: {
-                            icon: "calculate",
-                            name: "Math",
-                            description: "Do math calculations",
-                            command: () => mode.set("math"),
-                        },
-                        todo: {
-                            icon: "checklist",
-                            name: "Todo",
-                            description: "Create a todo in <INSERT_TODO_APP>",
-                            command: (...args) => {
-                                // TODO: todo service or maybe use external app
-                            },
-                        },
-                    };
-                    const subcommandList = Object.keys(subcommands);
-
-                    const updateEmpty = () => empty.set(self.get_children().length === 0);
-
-                    const appSearch = () => {
-                        const apps = Apps.fuzzy_query(entry.text);
-                        if (apps.length > config.maxResults) apps.length = config.maxResults;
-                        for (const app of apps) self.add(<AppResult app={app} />);
-                    };
-
-                    const calculate = () => {
-                        if (entry.text) {
-                            self.add(<MathResult math={Math.get_default().evaluate(entry.text)} entry={entry} />);
-                            self.add(<box className="separator" />);
-                        }
-                        for (const item of Math.get_default().history)
-                            self.add(<MathResult isHistory math={item} entry={entry} />);
-                    };
-
-                    const fileSearch = () =>
-                        execAsync(["fd", ...config.fdOpts, entry.text, HOME])
-                            .then(out => {
-                                const paths = out.split("\n").filter(path => path);
-                                if (paths.length > config.maxResults) paths.length = config.maxResults;
-                                self.foreach(ch => ch.destroy());
-                                for (const path of paths) self.add(<FileResult path={path} />);
-                            })
-                            .catch(e => {
-                                // Ignore execAsync error
-                                if (!(e instanceof Gio.IOErrorEnum || e instanceof GLib.SpawnError)) console.error(e);
-                            })
-                            .finally(updateEmpty);
-
-                    self.hook(entry, "activate", () => {
-                        if (mode.get() === "math") {
-                            if (entry.text.startsWith("clear")) Math.get_default().clear();
-                            else Math.get_default().commit();
-                        }
-                        self.get_children()[0]?.activate();
-                    });
-                    self.hook(entry, "changed", () => {
-                        if (!entry.text && mode.get() === "apps") return;
-
-                        // Files has delay cause async so it does some stuff by itself
-                        const ignoreFileAsync = entry.text.startsWith(">") || mode.get() !== "files";
-                        if (ignoreFileAsync) self.foreach(ch => ch.destroy());
-
-                        if (entry.text.startsWith(">")) {
-                            const args = entry.text.split(" ");
-                            for (const { target } of fuzzysort.go(args[0].slice(1), subcommandList, { all: true }))
-                                self.add(
-                                    <SubcommandResult
-                                        entry={entry}
-                                        subcommand={subcommands[target]}
-                                        args={args.slice(1)}
-                                    />
-                                );
-                        } else if (mode.get() === "apps") appSearch();
-                        else if (mode.get() === "math") calculate();
-                        else if (mode.get() === "files") fileSearch();
-
-                        if (ignoreFileAsync) updateEmpty();
-                    });
-                }}
-            />
-        </stack>
-    );
-};
-
-const LauncherContent = ({
-    mode,
-    showResults,
-    entry,
-}: {
-    mode: Variable<Mode>;
-    showResults: Variable<boolean>;
-    entry: Widget.Entry;
-}) => (
-    <box vertical className={bind(mode).as(m => `launcher ${m}`)}>
-        <box className="search-bar">
-            <label className="icon" label="search" />
-            <SearchEntry entry={entry} />
-            <label className="icon" label={bind(mode).as(getIconFromMode)} />
-        </box>
-        <revealer
-            revealChild={bind(showResults).as(s => !s)}
-            transitionType={Gtk.RevealerTransitionType.SLIDE_DOWN}
-            transitionDuration={150}
-        >
-            <PinnedApps />
-        </revealer>
-        <revealer
-            revealChild={bind(showResults)}
-            transitionType={Gtk.RevealerTransitionType.SLIDE_UP}
-            transitionDuration={150}
-        >
-            <Results entry={entry} mode={mode} />
-        </revealer>
-    </box>
-);
-
-@register()
-export default class Launcher extends PopupWindow {
-    readonly mode: Variable<Mode>;
-
-    constructor() {
-        const entry = (<entry name="entry" />) as Widget.Entry;
-        const mode = Variable<Mode>("apps");
-        const showResults = Variable.derive([bind(entry, "textLength"), mode], (t, m) => t > 0 || m !== "apps");
-
-        super({
-            name: "launcher",
-            anchor: Astal.WindowAnchor.TOP,
-            keymode: Astal.Keymode.EXCLUSIVE,
-            onKeyPressEvent(_, event) {
-                const keyval = event.get_keyval()[1];
-                // Focus entry on typing
-                if (!entry.isFocus && keyval >= 32 && keyval <= 126) {
-                    entry.text += String.fromCharCode(keyval);
-                    entry.grab_focus();
-                    entry.set_position(-1);
-
-                    // Consume event, if not consumed it will duplicate character in entry
-                    return true;
-                }
-            },
-            child: <LauncherContent mode={mode} showResults={showResults} entry={entry} />,
-        });
-
-        this.mode = mode;
-
-        this.connect("show", () => (this.marginTop = AstalHyprland.get_default().focusedMonitor.height / 4));
-
-        // Clear search on hide if not in math mode
-        this.connect("hide", () => mode.get() !== "math" && entry.set_text(""));
-
-        this.connect("destroy", () => showResults.drop());
-    }
-
-    open(mode: Mode) {
-        this.mode.set(mode);
-        this.show();
-    }
-}
-- 
cgit v1.2.3-freya

