refactor(ags): abstract appL and clip with SortedList
All checks were successful
Discord / discord commits (push) Has been skipped
All checks were successful
Discord / discord commits (push) Has been skipped
This commit is contained in:
parent
384e0c24b3
commit
9a9cfc13f6
7 changed files with 317 additions and 278 deletions
13
modules/ags/config/global-types.d.ts
vendored
13
modules/ags/config/global-types.d.ts
vendored
|
@ -115,22 +115,9 @@ export type CursorBox = CursorBox;
|
||||||
export type CursorBoxProps = CursorBoxProps;
|
export type CursorBoxProps = CursorBoxProps;
|
||||||
|
|
||||||
// For PopupWindow
|
// For PopupWindow
|
||||||
export type PopupChild = Binding<
|
|
||||||
Var<Widget>,
|
|
||||||
'is_listening' | 'is_polling' | 'value',
|
|
||||||
Widget[]
|
|
||||||
>;
|
|
||||||
export type HyprTransition = 'slide' | 'slide top' | 'slide bottom' | 'slide left' |
|
export type HyprTransition = 'slide' | 'slide top' | 'slide bottom' | 'slide left' |
|
||||||
'slide right' | 'popin' | 'fade';
|
'slide right' | 'popin' | 'fade';
|
||||||
export type CloseType = 'none' | 'stay' | 'released' | 'clicked';
|
export type CloseType = 'none' | 'stay' | 'released' | 'clicked';
|
||||||
export type PopupWindowProps<Child extends Widget, Attr = unknown> =
|
|
||||||
WindowProps<Child> & {
|
|
||||||
transition?: HyprTransition;
|
|
||||||
on_open?(self: PopupWindow<Child, Attr>): void
|
|
||||||
on_close?(self: PopupWindow<Child, Attr>): void
|
|
||||||
close_on_unfocus?: CloseType
|
|
||||||
anchor?: Array<'top' | 'bottom' | 'right' | 'left'>;
|
|
||||||
};
|
|
||||||
import { PopupWindow } from 'ts/misc/popup';
|
import { PopupWindow } from 'ts/misc/popup';
|
||||||
export type PopupWindow = PopupWindow;
|
export type PopupWindow = PopupWindow;
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ const { Box, Icon, Label } = Widget;
|
||||||
const { lookUpIcon } = Utils;
|
const { lookUpIcon } = Utils;
|
||||||
|
|
||||||
import CursorBox from '../misc/cursorbox.ts';
|
import CursorBox from '../misc/cursorbox.ts';
|
||||||
import { launchApp } from './launch.ts';
|
|
||||||
|
|
||||||
/* Types */
|
/* Types */
|
||||||
import { Application } from 'types/service/applications.ts';
|
import { Application } from 'types/service/applications.ts';
|
||||||
|
@ -54,11 +53,6 @@ export default (app: Application) => {
|
||||||
|
|
||||||
attribute: { app },
|
attribute: { app },
|
||||||
|
|
||||||
on_primary_click_release: () => {
|
|
||||||
App.closeWindow('win-applauncher');
|
|
||||||
launchApp(app);
|
|
||||||
},
|
|
||||||
|
|
||||||
child: Box({
|
child: Box({
|
||||||
children: [
|
children: [
|
||||||
icon,
|
icon,
|
||||||
|
|
|
@ -1,87 +1,67 @@
|
||||||
const Applications = await Service.import('applications');
|
const Applications = await Service.import('applications');
|
||||||
const { Box, Entry, Icon, Label, ListBox, Revealer, Scrollable } = Widget;
|
|
||||||
|
|
||||||
import { Fzf, FzfResultItem } from 'fzf';
|
import { Fzf, FzfResultItem } from 'fzf';
|
||||||
|
|
||||||
import PopupWindow from '../misc/popup.ts';
|
import SortedList from '../misc/sorted-list.ts';
|
||||||
import AppItem from './app-item.ts';
|
import AppItem from './app-item.ts';
|
||||||
|
|
||||||
import { launchApp } from './launch.ts';
|
import { launchApp } from './launch.ts';
|
||||||
|
|
||||||
// Types
|
/* Types */
|
||||||
import { ListBoxRow } from 'types/@girs/gtk-3.0/gtk-3.0.cjs';
|
import { ListBoxRow } from 'types/@girs/gtk-3.0/gtk-3.0.cjs';
|
||||||
import { Application } from 'types/service/applications.ts';
|
import { Application } from 'types/service/applications.ts';
|
||||||
import { AgsAppItem } from 'global-types';
|
import { AgsAppItem } from 'global-types';
|
||||||
|
|
||||||
|
|
||||||
const Applauncher = (window_name = 'applauncher') => {
|
export default () => {
|
||||||
let fzfResults: FzfResultItem<Application>[];
|
let fzfResults: FzfResultItem<Application>[];
|
||||||
const list = ListBox();
|
|
||||||
|
|
||||||
const setSort = (text: string) => {
|
return SortedList({
|
||||||
const fzf = new Fzf(Applications.list, {
|
name: 'applauncher',
|
||||||
selector: (app) => app.name + app.executable,
|
class_name: 'applauncher',
|
||||||
|
transition: 'slide top',
|
||||||
|
|
||||||
tiebreakers: [
|
on_select: (r) => {
|
||||||
(a, b) => b.item.frequency - a.item.frequency,
|
App.closeWindow('win-applauncher');
|
||||||
],
|
launchApp((r.get_child() as AgsAppItem).attribute.app);
|
||||||
});
|
|
||||||
|
|
||||||
fzfResults = fzf.find(text);
|
|
||||||
list.set_sort_func((a, b) => {
|
|
||||||
const row1 = (a.get_children()[0] as AgsAppItem).attribute.app.name;
|
|
||||||
const row2 = (b.get_children()[0] as AgsAppItem).attribute.app.name;
|
|
||||||
|
|
||||||
const s1 = fzfResults.find((r) => r.item.name === row1)?.score ?? 0;
|
|
||||||
const s2 = fzfResults.find((r) => r.item.name === row2)?.score ?? 0;
|
|
||||||
|
|
||||||
return s2 - s1;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const makeNewChildren = () => {
|
|
||||||
const rows = list.get_children() as ListBoxRow[];
|
|
||||||
|
|
||||||
rows.forEach((ch) => {
|
|
||||||
ch.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
const children = Applications.query('')
|
|
||||||
.flatMap((app) => AppItem(app));
|
|
||||||
|
|
||||||
children.forEach((ch) => {
|
|
||||||
list.add(ch);
|
|
||||||
});
|
|
||||||
list.show_all();
|
|
||||||
};
|
|
||||||
|
|
||||||
makeNewChildren();
|
|
||||||
|
|
||||||
const placeholder = Revealer({
|
|
||||||
child: Label({
|
|
||||||
label: " Couldn't find a match",
|
|
||||||
class_name: 'placeholder',
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
const entry = Entry({
|
|
||||||
// Set some text so on-change works the first time
|
|
||||||
text: '-',
|
|
||||||
hexpand: true,
|
|
||||||
|
|
||||||
on_accept: ({ text }) => {
|
|
||||||
const appList = Applications.query(text || '');
|
|
||||||
|
|
||||||
if (appList[0]) {
|
|
||||||
App.closeWindow(`win-${window_name}`);
|
|
||||||
launchApp(appList[0]);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
on_change: ({ text }) => {
|
init_rows: (list) => {
|
||||||
if (text === null) {
|
const rows = list.get_children() as ListBoxRow[];
|
||||||
return;
|
|
||||||
}
|
rows.forEach((ch) => {
|
||||||
setSort(text);
|
ch.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
const children = Applications.query('')
|
||||||
|
.flatMap((app) => AppItem(app));
|
||||||
|
|
||||||
|
children.forEach((ch) => {
|
||||||
|
list.add(ch);
|
||||||
|
});
|
||||||
|
list.show_all();
|
||||||
|
},
|
||||||
|
|
||||||
|
set_sort: (text, list, placeholder) => {
|
||||||
|
const fzf = new Fzf(Applications.list, {
|
||||||
|
selector: (app) => app.name + app.executable,
|
||||||
|
|
||||||
|
tiebreakers: [
|
||||||
|
(a, b) => b.item.frequency - a.item.frequency,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
fzfResults = fzf.find(text);
|
||||||
|
list.set_sort_func((a, b) => {
|
||||||
|
const row1 = (a.get_children()[0] as AgsAppItem).attribute.app.name;
|
||||||
|
const row2 = (b.get_children()[0] as AgsAppItem).attribute.app.name;
|
||||||
|
|
||||||
|
const s1 = fzfResults.find((r) => r.item.name === row1)?.score ?? 0;
|
||||||
|
const s2 = fzfResults.find((r) => r.item.name === row2)?.score ?? 0;
|
||||||
|
|
||||||
|
return s2 - s1;
|
||||||
|
});
|
||||||
|
|
||||||
let visibleApps = 0;
|
let visibleApps = 0;
|
||||||
|
|
||||||
const rows = list.get_children() as ListBoxRow[];
|
const rows = list.get_children() as ListBoxRow[];
|
||||||
|
@ -106,52 +86,4 @@ const Applauncher = (window_name = 'applauncher') => {
|
||||||
placeholder.reveal_child = visibleApps <= 0;
|
placeholder.reveal_child = visibleApps <= 0;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return Box({
|
|
||||||
class_name: 'applauncher',
|
|
||||||
vertical: true,
|
|
||||||
|
|
||||||
setup: (self) => {
|
|
||||||
self.hook(App, (_, name, visible) => {
|
|
||||||
if (name !== `win-${window_name}`) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
entry.text = '';
|
|
||||||
|
|
||||||
if (visible) {
|
|
||||||
entry.grab_focus();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
makeNewChildren();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
children: [
|
|
||||||
Box({
|
|
||||||
class_name: 'header',
|
|
||||||
children: [
|
|
||||||
Icon('preferences-system-search-symbolic'),
|
|
||||||
entry,
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
|
|
||||||
Scrollable({
|
|
||||||
hscroll: 'never',
|
|
||||||
vscroll: 'automatic',
|
|
||||||
child: Box({
|
|
||||||
vertical: true,
|
|
||||||
children: [list, placeholder],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default () => PopupWindow({
|
|
||||||
name: 'applauncher',
|
|
||||||
transition: 'slide top',
|
|
||||||
keymode: 'on-demand',
|
|
||||||
child: Applauncher(),
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
const { Box, Entry, Icon, Label, ListBox, Scrollable } = Widget;
|
const { Box, Icon, Label } = Widget;
|
||||||
const { execAsync } = Utils;
|
const { execAsync } = Utils;
|
||||||
|
|
||||||
const Hyprland = await Service.import('hyprland');
|
|
||||||
|
|
||||||
import { Fzf, FzfResultItem } from 'fzf';
|
import { Fzf, FzfResultItem } from 'fzf';
|
||||||
import Gtk from 'gi://Gtk?version=3.0';
|
import Gtk from 'gi://Gtk?version=3.0';
|
||||||
|
|
||||||
import CursorBox from '../misc/cursorbox.ts';
|
import CursorBox from '../misc/cursorbox.ts';
|
||||||
import PopupWindow from '../misc/popup.ts';
|
import SortedList from '../misc/sorted-list.ts';
|
||||||
import { Monitor } from 'types/service/hyprland';
|
|
||||||
|
|
||||||
|
|
||||||
const N_ITEMS = 30;
|
const N_ITEMS = 30;
|
||||||
|
@ -27,39 +24,7 @@ export default () => {
|
||||||
|
|
||||||
const getKey = (r: Gtk.ListBoxRow) => parseInt(r.get_child()?.name ?? '');
|
const getKey = (r: Gtk.ListBoxRow) => parseInt(r.get_child()?.name ?? '');
|
||||||
|
|
||||||
const list = ListBox().on('row-activated', (_, row) => {
|
const makeItem = (list: Gtk.ListBox, key: string, val: string) => {
|
||||||
copyOldItem(getKey(row));
|
|
||||||
});
|
|
||||||
|
|
||||||
const updateItems = () => {
|
|
||||||
(list.get_children() as Gtk.ListBoxRow[]).forEach((r) => {
|
|
||||||
r.changed();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setSort = (text: string) => {
|
|
||||||
if (text === '' || text === '-') {
|
|
||||||
list.set_sort_func((row1, row2) => getKey(row2) - getKey(row1));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const fzf = new Fzf(CopiedItems, {
|
|
||||||
selector: (item) => item[0],
|
|
||||||
|
|
||||||
tiebreakers: [(a, b) => b[1] - a[1]],
|
|
||||||
});
|
|
||||||
|
|
||||||
fzfResults = fzf.find(text);
|
|
||||||
list.set_sort_func((a, b) => {
|
|
||||||
const row1 = fzfResults.find((f) => f.item[1] === getKey(a))?.score ?? 0;
|
|
||||||
const row2 = fzfResults.find((f) => f.item[1] === getKey(b))?.score ?? 0;
|
|
||||||
|
|
||||||
return row2 - row1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
updateItems();
|
|
||||||
};
|
|
||||||
|
|
||||||
const makeItem = (key: string, val: string) => {
|
|
||||||
CopiedItems.push([val, parseInt(key)]);
|
CopiedItems.push([val, parseInt(key)]);
|
||||||
|
|
||||||
const widget = CursorBox({
|
const widget = CursorBox({
|
||||||
|
@ -87,110 +52,67 @@ export default () => {
|
||||||
|
|
||||||
list.add(widget);
|
list.add(widget);
|
||||||
widget.show_all();
|
widget.show_all();
|
||||||
updateItems();
|
(list.get_children() as Gtk.ListBoxRow[]).forEach((r) => {
|
||||||
};
|
r.changed();
|
||||||
|
|
||||||
// Decode old item:
|
|
||||||
const decodeItem = (index: string) => {
|
|
||||||
execAsync([
|
|
||||||
'bash', '-c', `cliphist list | grep ${index} | cliphist decode`,
|
|
||||||
]).then((out) => {
|
|
||||||
makeItem(index, out);
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const entry = Entry({
|
// Decode old item:
|
||||||
// Set some text so on-change works the first time
|
const decodeItem = (list: Gtk.ListBox, index: string) => {
|
||||||
text: '-',
|
execAsync([
|
||||||
hexpand: true,
|
'bash', '-c', `cliphist list | grep ${index} | cliphist decode`,
|
||||||
|
]).then((out) => {
|
||||||
|
makeItem(list, index, out);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
on_change: ({ text }) => {
|
return SortedList({
|
||||||
if (text !== null) {
|
name: 'clipboard',
|
||||||
setSort(text);
|
class_name: 'clipboard',
|
||||||
|
transition: 'slide top',
|
||||||
|
|
||||||
|
on_select: (r) => copyOldItem(getKey(r)),
|
||||||
|
|
||||||
|
init_rows: (list) => {
|
||||||
|
CopiedItems = [];
|
||||||
|
|
||||||
|
execAsync('clipboard-manager').then((out) => {
|
||||||
|
list.get_children()?.forEach((ch) => {
|
||||||
|
ch.destroy();
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = out.split('\n');
|
||||||
|
|
||||||
|
for (let i = 0; i < N_ITEMS; ++i) {
|
||||||
|
if (items[i].includes('img')) {
|
||||||
|
makeItem(list, (items[i].match('[0-9]+') ?? [''])[0], items[i]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
decodeItem(list, items[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}).catch(console.error);
|
||||||
|
},
|
||||||
|
|
||||||
|
set_sort: (text, list) => {
|
||||||
|
if (text === '' || text === '-') {
|
||||||
|
list.set_sort_func((row1, row2) => getKey(row2) - getKey(row1));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const fzf = new Fzf(CopiedItems, {
|
||||||
|
selector: (item) => item[0],
|
||||||
|
|
||||||
|
tiebreakers: [(a, b) => b[1] - a[1]],
|
||||||
|
});
|
||||||
|
|
||||||
|
fzfResults = fzf.find(text);
|
||||||
|
list.set_sort_func((a, b) => {
|
||||||
|
const row1 = fzfResults.find((f) => f.item[1] === getKey(a))?.score ?? 0;
|
||||||
|
const row2 = fzfResults.find((f) => f.item[1] === getKey(b))?.score ?? 0;
|
||||||
|
|
||||||
|
return row2 - row1;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const on_open = () => {
|
|
||||||
CopiedItems = [];
|
|
||||||
entry.text = '';
|
|
||||||
|
|
||||||
execAsync('clipboard-manager').then(async(out) => {
|
|
||||||
list.get_children()?.forEach((ch) => {
|
|
||||||
ch.destroy();
|
|
||||||
});
|
|
||||||
|
|
||||||
const items = out.split('\n');
|
|
||||||
|
|
||||||
for (let i = 0; i < N_ITEMS; ++i) {
|
|
||||||
if (items[i].includes('img')) {
|
|
||||||
makeItem((items[i].match('[0-9]+') ?? [''])[0], items[i]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
decodeItem(items[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let x: number;
|
|
||||||
let y: number;
|
|
||||||
const monitor = (JSON.parse(await Hyprland.messageAsync('j/monitors')) as Monitor[])
|
|
||||||
.find((m) => m.focused) as Monitor;
|
|
||||||
|
|
||||||
switch (monitor.transform) {
|
|
||||||
case 1:
|
|
||||||
x = monitor.x - (monitor.height / 2);
|
|
||||||
y = monitor.y - (monitor.width / 2);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 2:
|
|
||||||
x = monitor.x - (monitor.width / 2);
|
|
||||||
y = monitor.y - (monitor.height / 2);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 3:
|
|
||||||
x = monitor.x + (monitor.height / 2);
|
|
||||||
y = monitor.y + (monitor.width / 2);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
x = monitor.x + (monitor.width / 2);
|
|
||||||
y = monitor.y + (monitor.height / 2);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
await Hyprland.messageAsync(`dispatch movecursor ${x} ${y}`);
|
|
||||||
entry.grab_focus();
|
|
||||||
}).catch(console.log);
|
|
||||||
};
|
|
||||||
|
|
||||||
on_open();
|
|
||||||
|
|
||||||
return PopupWindow({
|
|
||||||
name: 'clipboard',
|
|
||||||
keymode: 'on-demand',
|
|
||||||
on_open,
|
|
||||||
|
|
||||||
child: Box({
|
|
||||||
class_name: 'clipboard',
|
|
||||||
vertical: true,
|
|
||||||
children: [
|
|
||||||
Box({
|
|
||||||
class_name: 'header',
|
|
||||||
children: [
|
|
||||||
Icon('preferences-system-search-symbolic'),
|
|
||||||
entry,
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
|
|
||||||
Scrollable({
|
|
||||||
hscroll: 'never',
|
|
||||||
vscroll: 'automatic',
|
|
||||||
child: Box({
|
|
||||||
vertical: true,
|
|
||||||
children: [list],
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -3,13 +3,29 @@ const Hyprland = await Service.import('hyprland');
|
||||||
|
|
||||||
/* Types */
|
/* Types */
|
||||||
import { Window } from 'resource:///com/github/Aylur/ags/widgets/window.js';
|
import { Window } from 'resource:///com/github/Aylur/ags/widgets/window.js';
|
||||||
|
import type { WindowProps } from 'types/widgets/window';
|
||||||
|
import type { Widget as AgsWidget } from 'types/widgets/widget';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CloseType,
|
CloseType,
|
||||||
HyprTransition,
|
HyprTransition,
|
||||||
PopupWindowProps,
|
|
||||||
} from 'global-types';
|
} from 'global-types';
|
||||||
|
|
||||||
|
export type PopupWindowProps<
|
||||||
|
Child extends Gtk.Widget,
|
||||||
|
Attr = unknown,
|
||||||
|
Self = PopupWindow<Child, Attr>,
|
||||||
|
> = WindowProps<Child, Attr, Self> & {
|
||||||
|
transition?: HyprTransition;
|
||||||
|
on_open?(self: PopupWindow<Child, Attr>): void;
|
||||||
|
on_close?(self: PopupWindow<Child, Attr>): void;
|
||||||
|
close_on_unfocus?: CloseType;
|
||||||
|
anchor?: Array<'top' | 'bottom' | 'right' | 'left'>;
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export interface PopupWindow<Child, Attr> extends AgsWidget<Attr> { }
|
||||||
|
|
||||||
|
|
||||||
export class PopupWindow<
|
export class PopupWindow<
|
||||||
Child extends Gtk.Widget,
|
Child extends Gtk.Widget,
|
||||||
|
@ -50,7 +66,7 @@ export class PopupWindow<
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private _on_open: (self: PopupWindow<Child, Attr>) => void;
|
protected _on_open: (self: PopupWindow<Child, Attr>) => void;
|
||||||
|
|
||||||
get on_open() {
|
get on_open() {
|
||||||
return this._on_open;
|
return this._on_open;
|
||||||
|
@ -114,10 +130,10 @@ export class PopupWindow<
|
||||||
this.transition = transition;
|
this.transition = transition;
|
||||||
|
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
this.on_open(this);
|
this._on_open(this);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.on_close(this);
|
this._on_close(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -147,6 +163,6 @@ export class PopupWindow<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default <Child extends Gtk.Widget, Attr>(
|
export default <Child extends Gtk.Widget, Attr> (
|
||||||
props: PopupWindowProps<Child, Attr>,
|
props: PopupWindowProps<Child, Attr>,
|
||||||
) => new PopupWindow(props);
|
) => new PopupWindow(props);
|
||||||
|
|
188
modules/ags/config/ts/misc/sorted-list.ts
Normal file
188
modules/ags/config/ts/misc/sorted-list.ts
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
const Hyprland = await Service.import('hyprland');
|
||||||
|
|
||||||
|
const { Box, Entry, Icon, Label, ListBox, Revealer, Scrollable } = Widget;
|
||||||
|
|
||||||
|
/* Types */
|
||||||
|
import { PopupWindow, PopupWindowProps } from '../misc/popup.ts';
|
||||||
|
import type { Widget as AgsWidget } from 'types/widgets/widget';
|
||||||
|
import { ListBoxRow } from 'types/@girs/gtk-3.0/gtk-3.0.cjs';
|
||||||
|
import { Monitor } from 'types/service/hyprland';
|
||||||
|
import { Binding } from 'types/service';
|
||||||
|
|
||||||
|
type MakeChild = ReturnType<typeof makeChild>;
|
||||||
|
|
||||||
|
type SortedListProps<Attr = unknown, Self = SortedList<Attr>> =
|
||||||
|
PopupWindowProps<MakeChild['child'], Attr, Self> & {
|
||||||
|
on_select: (row: ListBoxRow) => void;
|
||||||
|
init_rows: (list: MakeChild['list']) => void;
|
||||||
|
set_sort: (
|
||||||
|
text: string,
|
||||||
|
list: MakeChild['list'],
|
||||||
|
placeholder: MakeChild['placeholder'],
|
||||||
|
) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
export interface SortedList<Attr> extends AgsWidget<Attr> { }
|
||||||
|
|
||||||
|
|
||||||
|
const centerCursor = async(): Promise<void> => {
|
||||||
|
let x: number;
|
||||||
|
let y: number;
|
||||||
|
const monitor = (JSON.parse(await Hyprland.messageAsync('j/monitors')) as Monitor[])
|
||||||
|
.find((m) => m.focused) as Monitor;
|
||||||
|
|
||||||
|
switch (monitor.transform) {
|
||||||
|
case 1:
|
||||||
|
x = monitor.x - (monitor.height / 2);
|
||||||
|
y = monitor.y - (monitor.width / 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
x = monitor.x - (monitor.width / 2);
|
||||||
|
y = monitor.y - (monitor.height / 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 3:
|
||||||
|
x = monitor.x + (monitor.height / 2);
|
||||||
|
y = monitor.y + (monitor.width / 2);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
x = monitor.x + (monitor.width / 2);
|
||||||
|
y = monitor.y + (monitor.height / 2);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Hyprland.messageAsync(`dispatch movecursor ${x} ${y}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
const makeChild = (class_name: string | Binding<any, any, string>) => {
|
||||||
|
const list = ListBox();
|
||||||
|
|
||||||
|
const placeholder = Revealer({
|
||||||
|
child: Label({
|
||||||
|
label: " Couldn't find a match",
|
||||||
|
class_name: 'placeholder',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
const entry = Entry({
|
||||||
|
// Set some text so on-change works the first time
|
||||||
|
text: '-',
|
||||||
|
hexpand: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
list,
|
||||||
|
entry,
|
||||||
|
placeholder,
|
||||||
|
child: Box({
|
||||||
|
class_name,
|
||||||
|
vertical: true,
|
||||||
|
children: [
|
||||||
|
Box({
|
||||||
|
class_name: 'header',
|
||||||
|
children: [
|
||||||
|
Icon('preferences-system-search-symbolic'),
|
||||||
|
entry,
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
|
||||||
|
Scrollable({
|
||||||
|
hscroll: 'never',
|
||||||
|
vscroll: 'automatic',
|
||||||
|
child: Box({
|
||||||
|
vertical: true,
|
||||||
|
children: [list, placeholder],
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SortedList<
|
||||||
|
Attr,
|
||||||
|
> extends PopupWindow<MakeChild['child'], Attr> {
|
||||||
|
static {
|
||||||
|
Widget.register(this, {
|
||||||
|
properties: {
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private _list: MakeChild['list'];
|
||||||
|
private _entry: MakeChild['entry'];
|
||||||
|
private _placeholder: MakeChild['placeholder'];
|
||||||
|
private _on_select: (row: ListBoxRow) => void;
|
||||||
|
private _init_rows: (list: MakeChild['list']) => void;
|
||||||
|
private _set_sort: (
|
||||||
|
text: string,
|
||||||
|
list: MakeChild['list'],
|
||||||
|
placeholder: MakeChild['placeholder'],
|
||||||
|
) => void;
|
||||||
|
|
||||||
|
set on_open(fun: (self: PopupWindow<MakeChild['child'], Attr>) => void) {
|
||||||
|
this._on_open = () => {
|
||||||
|
this._entry.text = '';
|
||||||
|
fun(this);
|
||||||
|
this._init_rows(this._list);
|
||||||
|
centerCursor();
|
||||||
|
this._entry.grab_focus();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor({
|
||||||
|
on_select,
|
||||||
|
init_rows,
|
||||||
|
set_sort,
|
||||||
|
on_open = () => {/**/},
|
||||||
|
class_name = '',
|
||||||
|
keymode = 'on-demand',
|
||||||
|
...rest
|
||||||
|
}: SortedListProps<Attr, PopupWindow<MakeChild['child'], Attr>>) {
|
||||||
|
const makeChildResult = makeChild(class_name);
|
||||||
|
|
||||||
|
// PopupWindow
|
||||||
|
super({
|
||||||
|
child: makeChildResult.child,
|
||||||
|
keymode,
|
||||||
|
...rest,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.on_open = on_open;
|
||||||
|
|
||||||
|
// SortedList
|
||||||
|
this._on_select = on_select;
|
||||||
|
this._init_rows = init_rows;
|
||||||
|
this._set_sort = set_sort;
|
||||||
|
|
||||||
|
this._placeholder = makeChildResult.placeholder;
|
||||||
|
|
||||||
|
this._list = makeChildResult.list;
|
||||||
|
this._list.on('row-activated', (_, row) => {
|
||||||
|
this._on_select(row);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._entry = makeChildResult.entry;
|
||||||
|
|
||||||
|
this._entry.on_change = ({ text }) => {
|
||||||
|
if (text !== null) {
|
||||||
|
this._set_sort(text, this._list, this._placeholder);
|
||||||
|
(this._list.get_children() as ListBoxRow[]).forEach((r) => {
|
||||||
|
r.changed();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// TODO: add on_accept where it just selects the first visible one
|
||||||
|
|
||||||
|
this._init_rows(this._list);
|
||||||
|
this._set_sort('', this._list, this._placeholder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default <Attr>(
|
||||||
|
props: SortedListProps<Attr, PopupWindow<MakeChild['child'], Attr>>,
|
||||||
|
) => new SortedList(props);
|
|
@ -11,12 +11,12 @@
|
||||||
"noImplicitAny": false,
|
"noImplicitAny": false,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"fzf": ["./node_modules/fzf/dist/types"],
|
"fzf": ["./node_modules/fzf/dist/types"]
|
||||||
},
|
},
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"./types",
|
"./types",
|
||||||
"./global-types.d.ts",
|
"./global-types.d.ts",
|
||||||
"./node_modules",
|
"./node_modules"
|
||||||
],
|
],
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"forceConsistentCasingInFileNames": true
|
"forceConsistentCasingInFileNames": true
|
||||||
|
|
Loading…
Reference in a new issue