Added ags

TODO copy ags config
This commit is contained in:
2024-04-16 00:28:26 +02:00
parent be8fbb5d9f
commit 8aa6bcd5cb
125 changed files with 8130 additions and 113 deletions

View File

@@ -0,0 +1,130 @@
import { type Application } from "types/service/applications"
import { launchApp, icon } from "lib/utils"
import options from "options"
import icons from "lib/icons"
const apps = await Service.import("applications")
const { query } = apps
const { iconSize } = options.launcher.apps
const QuickAppButton = (app: Application) => Widget.Button({
hexpand: true,
tooltip_text: app.name,
on_clicked: () => {
App.closeWindow("launcher")
launchApp(app)
},
child: Widget.Icon({
size: iconSize.bind(),
icon: icon(app.icon_name, icons.fallback.executable),
}),
})
const AppItem = (app: Application) => {
const title = Widget.Label({
class_name: "title",
label: app.name,
hexpand: true,
xalign: 0,
vpack: "center",
truncate: "end",
})
const description = Widget.Label({
class_name: "description",
label: app.description || "",
hexpand: true,
wrap: true,
max_width_chars: 30,
xalign: 0,
justification: "left",
vpack: "center",
})
const appicon = Widget.Icon({
icon: icon(app.icon_name, icons.fallback.executable),
size: iconSize.bind(),
})
const textBox = Widget.Box({
vertical: true,
vpack: "center",
children: app.description ? [title, description] : [title],
})
return Widget.Button({
class_name: "app-item",
attribute: { app },
child: Widget.Box({
children: [appicon, textBox],
}),
on_clicked: () => {
App.closeWindow("launcher")
launchApp(app)
},
})
}
export function Favorites() {
const favs = options.launcher.apps.favorites.bind()
return Widget.Revealer({
visible: favs.as(f => f.length > 0),
child: Widget.Box({
vertical: true,
children: favs.as(favs => favs.flatMap(fs => [
Widget.Separator(),
Widget.Box({
class_name: "quicklaunch horizontal",
children: fs
.map(f => query(f)?.[0])
.filter(f => f)
.map(QuickAppButton),
}),
])),
}),
})
}
export function Launcher() {
const applist = Variable(query(""))
const max = options.launcher.apps.max
let first = applist.value[0]
function SeparatedAppItem(app: Application) {
return Widget.Revealer(
{ attribute: { app } },
Widget.Box(
{ vertical: true },
Widget.Separator(),
AppItem(app),
),
)
}
const list = Widget.Box({
vertical: true,
children: applist.bind().as(list => list.map(SeparatedAppItem)),
setup: self => self
.hook(apps, () => applist.value = query(""), "notify::frequents"),
})
return Object.assign(list, {
filter(text: string | null) {
first = query(text || "")[0]
list.children.reduce((i, item) => {
if (!text || i >= max.value) {
item.reveal_child = false
return i
}
if (item.attribute.app.match(text)) {
item.reveal_child = true
return ++i
}
item.reveal_child = false
return i
}, 0)
},
launchFirst() {
launchApp(first)
},
})
}

View File

@@ -0,0 +1,139 @@
import { type Binding } from "lib/utils"
import PopupWindow, { Padding } from "widget/PopupWindow"
import icons from "lib/icons"
import options from "options"
import nix from "service/nix"
import * as AppLauncher from "./AppLauncher"
import * as NixRun from "./NixRun"
import * as ShRun from "./ShRun"
const { width, margin } = options.launcher
const isnix = nix.available
function Launcher() {
const favs = AppLauncher.Favorites()
const applauncher = AppLauncher.Launcher()
const sh = ShRun.ShRun()
const shicon = ShRun.Icon()
const nix = NixRun.NixRun()
const nixload = NixRun.Spinner()
function HelpButton(cmd: string, desc: string | Binding<string>) {
return Widget.Box(
{ vertical: true },
Widget.Separator(),
Widget.Button(
{
class_name: "help",
on_clicked: () => {
entry.grab_focus()
entry.text = `:${cmd} `
entry.set_position(-1)
},
},
Widget.Box([
Widget.Label({
class_name: "name",
label: `:${cmd}`,
}),
Widget.Label({
hexpand: true,
hpack: "end",
class_name: "description",
label: desc,
}),
]),
),
)
}
const help = Widget.Revealer({
child: Widget.Box(
{ vertical: true },
HelpButton("sh", "run a binary"),
isnix ? HelpButton("nx", options.launcher.nix.pkgs.bind().as(pkg =>
`run a nix package from ${pkg}`,
)) : Widget.Box(),
),
})
const entry = Widget.Entry({
hexpand: true,
primary_icon_name: icons.ui.search,
on_accept: ({ text }) => {
if (text?.startsWith(":nx"))
nix.run(text.substring(3))
else if (text?.startsWith(":sh"))
sh.run(text.substring(3))
else
applauncher.launchFirst()
App.toggleWindow("launcher")
entry.text = ""
},
on_change: ({ text }) => {
text ||= ""
favs.reveal_child = text === ""
help.reveal_child = text.split(" ").length === 1 && text?.startsWith(":")
if (text?.startsWith(":nx"))
nix.filter(text.substring(3))
else
nix.filter("")
if (text?.startsWith(":sh"))
sh.filter(text.substring(3))
else
sh.filter("")
if (!text?.startsWith(":"))
applauncher.filter(text)
},
})
function focus() {
entry.text = "Search"
entry.set_position(-1)
entry.select_region(0, -1)
entry.grab_focus()
favs.reveal_child = true
}
const layout = Widget.Box({
css: width.bind().as(v => `min-width: ${v}pt;`),
class_name: "launcher",
vertical: true,
vpack: "start",
setup: self => self.hook(App, (_, win, visible) => {
if (win !== "launcher")
return
entry.text = ""
if (visible)
focus()
}),
children: [
Widget.Box([entry, nixload, shicon]),
favs,
help,
applauncher,
nix,
sh,
],
})
return Widget.Box(
{ vertical: true, css: "padding: 1px" },
Padding("applauncher", {
css: margin.bind().as(v => `min-height: ${v}pt;`),
vexpand: false,
}),
layout,
)
}
export default () => PopupWindow({
name: "launcher",
layout: "top",
child: Launcher(),
})

View File

@@ -0,0 +1,118 @@
import icons from "lib/icons"
import nix, { type Nixpkg } from "service/nix"
const iconVisible = Variable(false)
function Item(pkg: Nixpkg) {
const name = Widget.Label({
class_name: "name",
label: pkg.name.split(".").at(-1),
})
const subpkg = pkg.name.includes(".") ? Widget.Label({
class_name: "description",
hpack: "end",
hexpand: true,
label: ` ${pkg.name.split(".").slice(0, -1).join(".")}`,
}) : null
const version = Widget.Label({
class_name: "version",
label: pkg.version,
hexpand: true,
hpack: "end",
})
const description = pkg.description ? Widget.Label({
class_name: "description",
label: pkg.description,
justification: "left",
wrap: true,
hpack: "start",
max_width_chars: 40,
}) : null
return Widget.Box(
{
attribute: { name: pkg.name },
vertical: true,
},
Widget.Separator(),
Widget.Button(
{
class_name: "nix-item",
on_clicked: () => {
nix.run(pkg.name)
App.closeWindow("launcher")
},
},
Widget.Box(
{ vertical: true },
Widget.Box([name, version]),
Widget.Box([
description as ReturnType<typeof Widget.Label>,
subpkg as ReturnType<typeof Widget.Label>,
]),
),
),
)
}
export function Spinner() {
const icon = Widget.Icon({
icon: icons.nix.nix,
class_name: "spinner",
css: `
@keyframes spin {
to { -gtk-icon-transform: rotate(1turn); }
}
image.spinning {
animation-name: spin;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
`,
setup: self => self.hook(nix, () => {
self.toggleClassName("spinning", !nix.ready)
}),
})
return Widget.Revealer({
transition: "slide_left",
child: icon,
reveal_child: Utils.merge([
nix.bind("ready"),
iconVisible.bind(),
], (ready, show) => !ready || show),
})
}
export function NixRun() {
const list = Widget.Box<ReturnType<typeof Item>>({
vertical: true,
})
const revealer = Widget.Revealer({
child: list,
})
async function filter(term: string) {
iconVisible.value = Boolean(term)
if (!term)
revealer.reveal_child = false
if (term.trim()) {
const found = await nix.query(term)
list.children = found.map(k => Item(nix.db[k]))
revealer.reveal_child = true
}
}
return Object.assign(revealer, {
filter,
run: nix.run,
})
}

View File

@@ -0,0 +1,89 @@
import icons from "lib/icons"
import options from "options"
import { bash, dependencies } from "lib/utils"
const iconVisible = Variable(false)
const MAX = options.launcher.sh.max
const BINS = `${Utils.CACHE_DIR}/binaries`
bash("{ IFS=:; ls -H $PATH; } | sort ")
.then(bins => Utils.writeFile(bins, BINS))
async function query(filter: string) {
if (!dependencies("fzf"))
return [] as string[]
return bash(`cat ${BINS} | fzf -f ${filter} | head -n ${MAX}`)
.then(str => Array.from(new Set(str.split("\n").filter(i => i)).values()))
.catch(err => { print(err); return [] })
}
function run(args: string) {
Utils.execAsync(args)
.then(out => {
print(`:sh ${args.trim()}:`)
print(out)
})
.catch(err => {
Utils.notify("ShRun Error", err, icons.app.terminal)
})
}
function Item(bin: string) {
return Widget.Box(
{
attribute: { bin },
vertical: true,
},
Widget.Separator(),
Widget.Button({
child: Widget.Label({
label: bin,
hpack: "start",
}),
class_name: "sh-item",
on_clicked: () => {
Utils.execAsync(bin)
App.closeWindow("launcher")
},
}),
)
}
export function Icon() {
const icon = Widget.Icon({
icon: icons.app.terminal,
class_name: "spinner",
})
return Widget.Revealer({
transition: "slide_left",
child: icon,
reveal_child: iconVisible.bind(),
})
}
export function ShRun() {
const list = Widget.Box<ReturnType<typeof Item>>({
vertical: true,
})
const revealer = Widget.Revealer({
child: list,
})
async function filter(term: string) {
iconVisible.value = Boolean(term)
if (!term)
revealer.reveal_child = false
if (term.trim()) {
const found = await query(term)
list.children = found.map(Item)
revealer.reveal_child = true
}
}
return Object.assign(revealer, { filter, run })
}

View File

@@ -0,0 +1,143 @@
@use "sass:math";
@use "sass:color";
window#launcher .launcher {
@include floating_widget;
.quicklaunch {
@include spacing;
button {
@include button($flat: true);
padding: $padding;
}
}
entry {
@include button;
padding: $padding;
margin: $spacing;
selection {
color: color.mix($fg, $bg, 50%);
background-color: transparent;
}
label,
image {
color: $fg;
}
}
image.spinner {
color: $primary-bg;
margin-right: $spacing;
}
separator {
margin: 4pt 0;
background-color: $popover-border-color;
}
button.app-item {
@include button($flat: true, $reactive: false);
>box {
@include spacing(0.5);
}
transition: $transition;
padding: $padding;
label {
transition: $transition;
&.title {
color: $fg;
}
&.description {
color: transparentize($fg, 0.3);
}
}
image {
transition: $transition;
}
&:hover,
&:focus {
.title {
color: $primary-bg;
}
.description {
color: transparentize($primary-bg, .4);
}
image {
-gtk-icon-shadow: 2px 2px $primary-bg;
}
}
&:active {
background-color: transparentize($primary-bg, 0.5);
border-radius: $radius;
box-shadow: inset 0 0 0 $border-width $border-color;
.title {
color: $fg;
}
}
}
button.help,
button.nix-item {
@include button($flat: true, $reactive: false);
padding: 0 ($padding * .5);
label {
transition: $transition;
color: $fg;
}
.name {
font-size: 1.2em;
font-weight: bold;
}
.description {
color: transparentize($fg, .3)
}
&:hover,
&:focus {
label {
text-shadow: $text-shadow;
}
.name,
.version {
color: $primary-bg;
}
.description {
color: transparentize($primary-bg, .3)
}
}
}
button.sh-item {
@include button($flat: true, $reactive: false);
padding: 0 ($padding * .5);
transition: $transition;
color: $fg;
&:hover,
&:focus {
color: $primary-bg;
text-shadow: $text-shadow;
}
}
}