108 lines
2.9 KiB
TypeScript
108 lines
2.9 KiB
TypeScript
|
const { query } = await Service.import("applications")
|
||
|
const WINDOW_NAME = "applauncher"
|
||
|
|
||
|
/** @param {import('resource:///com/github/Aylur/ags/service/applications.js').Application} app */
|
||
|
const AppItem = app => Widget.Button({
|
||
|
on_clicked: () => {
|
||
|
App.closeWindow(WINDOW_NAME)
|
||
|
app.launch()
|
||
|
},
|
||
|
attribute: { app },
|
||
|
child: Widget.Box({
|
||
|
children: [
|
||
|
Widget.Icon({
|
||
|
icon: app.icon_name || "",
|
||
|
size: 42,
|
||
|
}),
|
||
|
Widget.Label({
|
||
|
class_name: "title",
|
||
|
label: app.name,
|
||
|
xalign: 0,
|
||
|
vpack: "center",
|
||
|
truncate: "end",
|
||
|
}),
|
||
|
],
|
||
|
}),
|
||
|
})
|
||
|
|
||
|
const Applauncher = ({ width = 500, height = 500, spacing = 12 }) => {
|
||
|
// list of application buttons
|
||
|
let applications = query("").map(AppItem)
|
||
|
|
||
|
// container holding the buttons
|
||
|
const list = Widget.Box({
|
||
|
vertical: true,
|
||
|
children: applications,
|
||
|
spacing,
|
||
|
})
|
||
|
|
||
|
// repopulate the box, so the most frequent apps are on top of the list
|
||
|
function repopulate() {
|
||
|
applications = query("").map(AppItem)
|
||
|
list.children = applications
|
||
|
}
|
||
|
|
||
|
// search entry
|
||
|
const entry = Widget.Entry({
|
||
|
hexpand: true,
|
||
|
css: `margin-bottom: ${spacing}px;`,
|
||
|
|
||
|
// to launch the first item on Enter
|
||
|
on_accept: () => {
|
||
|
// make sure we only consider visible (searched for) applications
|
||
|
const results = applications.filter((item) => item.visible);
|
||
|
if (results[0]) {
|
||
|
App.toggleWindow(WINDOW_NAME)
|
||
|
results[0].attribute.app.launch()
|
||
|
}
|
||
|
},
|
||
|
|
||
|
// filter out the list
|
||
|
on_change: ({ text }) => applications.forEach(item => {
|
||
|
item.visible = item.attribute.app.match(text ?? "")
|
||
|
}),
|
||
|
})
|
||
|
|
||
|
return Widget.Box({
|
||
|
vertical: true,
|
||
|
css: `margin: ${spacing * 2}px;`,
|
||
|
children: [
|
||
|
entry,
|
||
|
|
||
|
// wrap the list in a scrollable
|
||
|
Widget.Scrollable({
|
||
|
hscroll: "never",
|
||
|
css: `min-width: ${width}px;`
|
||
|
+ `min-height: ${height}px;`,
|
||
|
child: list,
|
||
|
}),
|
||
|
],
|
||
|
setup: self => self.hook(App, (_, windowName, visible) => {
|
||
|
if (windowName !== WINDOW_NAME)
|
||
|
return
|
||
|
|
||
|
// when the applauncher shows up
|
||
|
if (visible) {
|
||
|
repopulate()
|
||
|
entry.text = ""
|
||
|
entry.grab_focus()
|
||
|
}
|
||
|
}),
|
||
|
})
|
||
|
}
|
||
|
|
||
|
// there needs to be only one instance
|
||
|
export const applauncher = Widget.Window({
|
||
|
name: WINDOW_NAME,
|
||
|
setup: self => self.keybind("Escape", () => {
|
||
|
App.closeWindow(WINDOW_NAME)
|
||
|
}),
|
||
|
visible: false,
|
||
|
keymode: "exclusive",
|
||
|
child: Applauncher({
|
||
|
width: 500,
|
||
|
height: 500,
|
||
|
spacing: 12,
|
||
|
}),
|
||
|
})
|