summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-01-14 15:41:28 +1100
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-01-14 15:41:28 +1100
commita313624734dde8b2f562eb0815c52e93b00f7986 (patch)
tree18ce329e15814cda92fd3e750fea6f28fcfc6dc7
parentpopupwindow: allow different anims for show/hide (diff)
downloadcaelestia-shell-a313624734dde8b2f562eb0815c52e93b00f7986.tar.gz
caelestia-shell-a313624734dde8b2f562eb0815c52e93b00f7986.tar.bz2
caelestia-shell-a313624734dde8b2f562eb0815c52e93b00f7986.zip
launcher modes + player controls IPC
-rw-r--r--app.tsx5
-rw-r--r--modules/launcher.tsx230
-rw-r--r--package-lock.json494
-rw-r--r--package.json9
-rwxr-xr-xrun.fish11
-rw-r--r--scss/launcher.scss38
6 files changed, 735 insertions, 52 deletions
diff --git a/app.tsx b/app.tsx
index db766c8..86d98e6 100644
--- a/app.tsx
+++ b/app.tsx
@@ -4,6 +4,7 @@ import AstalHyprland from "gi://AstalHyprland";
import Bar from "./modules/bar";
import Launcher from "./modules/launcher";
import NotifPopups from "./modules/notifpopups";
+import Players from "./services/players";
import { PopupWindow } from "./utils/widgets";
const loadStyleAsync = async () => {
@@ -29,6 +30,10 @@ App.start({
let log = true;
if (request === "reload css") loadStyleAsync().catch(console.error);
+ else if (request === "media play pause") Players.get_default().lastPlayer?.play_pause();
+ else if (request === "media next") Players.get_default().lastPlayer?.next();
+ else if (request === "media previous") Players.get_default().lastPlayer?.previous();
+ else if (request === "media stop") Players.get_default().lastPlayer?.stop();
else if (request.startsWith("toggle")) {
const window = App.get_window(request.slice(7));
if (window instanceof PopupWindow) window.toggle();
diff --git a/modules/launcher.tsx b/modules/launcher.tsx
index 4cde7c4..8f01596 100644
--- a/modules/launcher.tsx
+++ b/modules/launcher.tsx
@@ -1,12 +1,23 @@
-import { bind, Gio, timeout, Variable } from "astal";
+import { bind, execAsync, Gio, 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 Mexp from "math-expression-evaluator";
import { Apps } from "../services/apps";
import { getAppCategoryIcon } from "../utils/icons";
import { launch } from "../utils/system";
import { PopupWindow, setupCustomTooltip, TransitionType } from "../utils/widgets";
+type Mode = "apps" | "files" | "math";
+
+interface Subcommand {
+ icon: string;
+ name: string;
+ description: string;
+ command: (...args: string[]) => void;
+}
+
const maxSearchResults = 15;
const browser = [
@@ -23,6 +34,17 @@ const files = ["thunar", "nemo", "nautilus"];
const ide = ["codium", "code", "clion", "intellij-idea-ultimate-edition"];
const music = ["spotify-adblock", "spotify", "audacious", "elisa"];
+const getIconFromMode = (mode: Mode) => {
+ switch (mode) {
+ case "apps":
+ return "apps";
+ case "files":
+ return "folder";
+ case "math":
+ return "calculate";
+ }
+};
+
const launchAndClose = (self: JSX.Element, astalApp: AstalApps.Application) => {
const toplevel = self.get_toplevel();
if (toplevel instanceof Widget.Window) toplevel.hide();
@@ -45,7 +67,7 @@ const PinnedApp = ({ names }: { names: string[] }) => {
return app ? (
<button
- className="app"
+ className="pinned-app result"
cursor="pointer"
onClicked={self => launchAndClose(self, astalApp!)}
setup={self => setupCustomTooltip(self, app.get_display_name())}
@@ -56,7 +78,7 @@ const PinnedApp = ({ names }: { names: string[] }) => {
};
const PinnedApps = () => (
- <box homogeneous className="pinned">
+ <box homogeneous>
<PinnedApp names={browser} />
<PinnedApp names={terminal} />
<PinnedApp names={files} />
@@ -82,21 +104,72 @@ const SearchEntry = ({ entry }: { entry: Widget.Entry }) => (
</stack>
);
-const Result = ({ app }: { app: AstalApps.Application }) => (
- <button className="app" cursor="pointer" onClicked={self => launchAndClose(self, app)}>
+// TODO: description field
+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>
- {Astal.Icon.lookup_icon(app.iconName) ? (
- <icon className="icon" icon={app.iconName} />
+ {icon && Astal.Icon.lookup_icon(icon) ? (
+ <icon className="icon" icon={icon} />
+ ) : (
+ <label className="icon" label={materialIcon} />
+ )}
+ {sublabel ? (
+ <box vertical valign={Gtk.Align.CENTER} className="has-sublabel">
+ <label xalign={0} label={label} />
+ <label hexpand truncate maxWidthChars={1} className="sublabel" xalign={0} label={sublabel} />
+ </box>
) : (
- <label className="icon" label={getAppCategoryIcon(app)} />
+ <label xalign={0} label={label} />
)}
- <label xalign={0} label={app.name} />
</box>
</button>
);
-const Results = ({ entry }: { entry: Widget.Entry }) => {
+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 Results = ({ entry, mode }: { entry: Widget.Entry; mode: Variable<Mode> }) => {
const empty = Variable(true);
+
return (
<stack
className="results"
@@ -112,17 +185,82 @@ const Results = ({ entry }: { entry: Widget.Entry }) => {
vertical
name="list"
setup={self => {
- let apps: AstalApps.Application[] = [];
- self.hook(entry, "activate", () => {
- if (entry.text && apps[0]) launchAndClose(self, apps[0]);
- });
+ 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"),
+ },
+ calc: {
+ icon: "calculate",
+ name: "Calculator",
+ description: "A calculator...",
+ 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 mexp = new Mexp();
+
+ const appSearch = () => {
+ const apps = Apps.fuzzy_query(entry.text);
+ empty.set(apps.length === 0);
+ if (apps.length > maxSearchResults) apps.length = maxSearchResults;
+ for (const app of apps) self.add(<AppResult app={app} />);
+ };
+
+ const calculate = () => {
+ // TODO: allow defs, history
+ let math = null;
+ try {
+ math = mexp.eval(entry.text);
+ } catch (e) {
+ // Ignore
+ }
+ if (math !== null)
+ self.add(
+ <Result
+ materialIcon="calculate"
+ label={entry.text}
+ sublabel={String(math)}
+ onClicked={() => execAsync(`wl-copy -- ${math}`).catch(console.error)}
+ />
+ );
+ };
+
+ self.hook(entry, "activate", () => entry.text && self.get_children()[0].activate());
self.hook(entry, "changed", () => {
if (!entry.text) return;
self.foreach(ch => ch.destroy());
- apps = Apps.fuzzy_query(entry.text);
- empty.set(apps.length === 0);
- if (apps.length > maxSearchResults) apps.length = maxSearchResults;
- for (const app of apps) self.add(<Result app={app} />);
+
+ 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();
+
+ empty.set(self.get_children().length === 0);
});
}}
/>
@@ -130,16 +268,16 @@ const Results = ({ entry }: { entry: Widget.Entry }) => {
);
};
-const Launcher = ({ entry }: { entry: Widget.Entry }) => (
+const LauncherContent = ({ mode, entry }: { mode: Variable<Mode>; entry: Widget.Entry }) => (
<box
vertical
- className="launcher"
+ className={bind(mode).as(m => `launcher ${m}`)}
css={bind(AstalHyprland.get_default(), "focusedMonitor").as(m => `margin-top: ${m.height / 4}px;`)}
>
<box className="search-bar">
<label className="icon" label="search" />
<SearchEntry entry={entry} />
- <label className="icon" label="apps" />
+ <label className="icon" label={bind(mode).as(getIconFromMode)} />
</box>
<revealer
revealChild={bind(entry, "textLength").as(t => t === 0)}
@@ -153,20 +291,24 @@ const Launcher = ({ entry }: { entry: Widget.Entry }) => (
transitionType={Gtk.RevealerTransitionType.SLIDE_UP}
transitionDuration={150}
>
- <Results entry={entry} />
+ <Results entry={entry} mode={mode} />
</revealer>
</box>
);
-export default () => {
- const entry = (<entry name="entry" />) as Widget.Entry;
+@register()
+export default class Launcher extends PopupWindow {
+ readonly mode: Variable<Mode>;
- return (
- <PopupWindow
- name="launcher"
- keymode={Astal.Keymode.EXCLUSIVE}
- exclusivity={Astal.Exclusivity.IGNORE}
- onKeyPressEvent={(_, event) => {
+ constructor() {
+ const entry = (<entry name="entry" />) as Widget.Entry;
+ const mode = Variable<Mode>("apps");
+
+ super({
+ name: "launcher",
+ keymode: Astal.Keymode.EXCLUSIVE,
+ exclusivity: Astal.Exclusivity.IGNORE,
+ onKeyPressEvent(_, event) {
const keyval = event.get_keyval()[1];
// Focus entry on typing
if (!entry.isFocus && keyval >= 32 && keyval <= 126) {
@@ -177,14 +319,20 @@ export default () => {
// Consume event, if not consumed it will duplicate character in entry
return true;
}
- }}
- // Clear entry text on hide
- setup={self => self.connect("hide", () => entry.set_text(""))}
- transitionType={TransitionType.SLIDE_DOWN}
- halign={Gtk.Align.CENTER}
- valign={Gtk.Align.START}
- >
- <Launcher entry={entry} />
- </PopupWindow>
- );
-};
+ },
+ transitionType: TransitionType.SLIDE_DOWN,
+ halign: Gtk.Align.CENTER,
+ valign: Gtk.Align.START,
+ child: <LauncherContent mode={mode} entry={entry} />,
+ });
+
+ this.mode = mode;
+
+ this.connect("hide", () => entry.set_text(""));
+ }
+
+ open(mode: Mode) {
+ this.mode.set(mode);
+ this.show();
+ }
+}
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..c5de752
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,494 @@
+{
+ "name": "caelestia",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "fuzzysort": "^3.1.0",
+ "math-expression-evaluator": "^2.0.6"
+ },
+ "devDependencies": {
+ "esbuild": "0.24.2"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
+ "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
+ "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
+ "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
+ "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
+ "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
+ "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
+ "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
+ "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
+ "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
+ "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
+ "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
+ "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
+ "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
+ "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
+ "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
+ "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
+ "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
+ "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
+ "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
+ "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
+ "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
+ "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz",
+ "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.24.2",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
+ "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.24.2",
+ "@esbuild/android-arm": "0.24.2",
+ "@esbuild/android-arm64": "0.24.2",
+ "@esbuild/android-x64": "0.24.2",
+ "@esbuild/darwin-arm64": "0.24.2",
+ "@esbuild/darwin-x64": "0.24.2",
+ "@esbuild/freebsd-arm64": "0.24.2",
+ "@esbuild/freebsd-x64": "0.24.2",
+ "@esbuild/linux-arm": "0.24.2",
+ "@esbuild/linux-arm64": "0.24.2",
+ "@esbuild/linux-ia32": "0.24.2",
+ "@esbuild/linux-loong64": "0.24.2",
+ "@esbuild/linux-mips64el": "0.24.2",
+ "@esbuild/linux-ppc64": "0.24.2",
+ "@esbuild/linux-riscv64": "0.24.2",
+ "@esbuild/linux-s390x": "0.24.2",
+ "@esbuild/linux-x64": "0.24.2",
+ "@esbuild/netbsd-arm64": "0.24.2",
+ "@esbuild/netbsd-x64": "0.24.2",
+ "@esbuild/openbsd-arm64": "0.24.2",
+ "@esbuild/openbsd-x64": "0.24.2",
+ "@esbuild/sunos-x64": "0.24.2",
+ "@esbuild/win32-arm64": "0.24.2",
+ "@esbuild/win32-ia32": "0.24.2",
+ "@esbuild/win32-x64": "0.24.2"
+ }
+ },
+ "node_modules/fuzzysort": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/fuzzysort/-/fuzzysort-3.1.0.tgz",
+ "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==",
+ "license": "MIT"
+ },
+ "node_modules/math-expression-evaluator": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-2.0.6.tgz",
+ "integrity": "sha512-DRung1qNcKbgkhFeQ0fBPUFB6voRUMY7KyRyp1TRQ2v95Rp2egC823xLRooM1mDx1rmbkY7ym6ZWmpaE/VimOA==",
+ "license": "MIT"
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..f0cb26d
--- /dev/null
+++ b/package.json
@@ -0,0 +1,9 @@
+{
+ "dependencies": {
+ "fuzzysort": "^3.1.0",
+ "math-expression-evaluator": "^2.0.6"
+ },
+ "devDependencies": {
+ "esbuild": "0.24.2"
+ }
+}
diff --git a/run.fish b/run.fish
new file mode 100755
index 0000000..66e23ef
--- /dev/null
+++ b/run.fish
@@ -0,0 +1,11 @@
+#!/bin/fish
+
+cd (dirname (status filename)) || exit 1
+
+set -q XDG_RUNTIME_DIR && set bundle_dir $XDG_RUNTIME_DIR || set bundle_dir /tmp
+
+./node_modules/.bin/esbuild app.tsx --bundle --outfile=$bundle_dir/caelestia.js \
+ --external:console --external:system --external:cairo --external:gettext --external:'file://*' --external:'gi://*' --external:'resource://*' \
+ --define:SRC=\"(pwd)\" --format=esm --platform=neutral --main-fields=module,main
+
+gjs -m $bundle_dir/caelestia.js
diff --git a/scss/launcher.scss b/scss/launcher.scss
index 44b135d..8e710e9 100644
--- a/scss/launcher.scss
+++ b/scss/launcher.scss
@@ -29,7 +29,6 @@
.icon {
font-size: lib.s(18);
- color: scheme.$sapphire;
}
.placeholder {
@@ -37,7 +36,7 @@
}
}
- .app {
+ .result {
@include lib.element-decel;
padding: lib.s(5) lib.s(10);
@@ -52,35 +51,52 @@
}
}
- .pinned .app {
+ .pinned-app {
@include lib.rounded(5);
font-size: lib.s(64);
}
.results {
+ .icon {
+ font-size: lib.s(32);
+ }
+
.empty {
color: scheme.$subtext0;
@include lib.spacing;
-
- .icon {
- font-size: lib.s(32);
- }
}
- .app {
+ .result {
@include lib.rounded(10);
font-size: lib.s(18);
& > * {
- @include lib.spacing;
+ @include lib.spacing(8);
}
- .icon {
- font-size: lib.s(32);
+ .has-sublabel {
+ font-size: lib.s(16);
+
+ .sublabel {
+ color: scheme.$subtext0;
+ font-size: lib.s(14);
+ }
}
}
}
+
+ &.apps label.icon {
+ color: scheme.$sapphire;
+ }
+
+ &.files label.icon {
+ color: scheme.$peach;
+ }
+
+ &.math label.icon {
+ color: scheme.$green;
+ }
}