feat(runner/plugins/wallpapers): subdirectory exploration support

it's also recursivegit add .
This commit is contained in:
retrozinndev
2025-11-10 20:36:49 -03:00
parent 48c686194f
commit ccff2d9bee
3 changed files with 142 additions and 55 deletions
+1 -1
View File
@@ -281,7 +281,7 @@ you should use the socket in the XDG_RUNTIME_DIR/colorshell.sock for a faster re
Wallpaper.getDefault();
Stylesheet.getDefault();
console.log("Adding runner plugins");
console.log("Runner: Adding plugins");
runnerPlugins.forEach(plugin => Runner.addPlugin(plugin));
createSubscription(
+120 -26
View File
@@ -1,62 +1,156 @@
import Fuse from "fuse.js";
import Fuse, { IFuseOptions } from "fuse.js";
import { Wallpaper } from "../../modules/wallpaper";
import { Runner } from "../Runner";
import Gio from "gi://Gio?version=2.0";
import GLib from "gi://GLib?version=2.0";
class _PluginWallpapers implements Runner.Plugin {
prefix = "#";
prioritize = true;
#fuse!: Fuse<string>;
#files!: Array<string>;
#files!: Array<Gio.FileInfo>;
#dir: string = Wallpaper.getDefault().wallpapersPath;
#subdir: string|undefined = undefined;
readonly #options = {
useExtendedSearch: false,
shouldSort: true,
isCaseSensitive: false
} satisfies IFuseOptions<string>;
init() {
this.#files = [];
const dir = Gio.File.new_for_path(Wallpaper.getDefault().wallpapersPath);
this.#subdir = undefined;
const dir = Gio.File.new_for_path(this.#dir);
if(dir.query_file_type(null, null) === Gio.FileType.DIRECTORY) {
for(const file of dir.enumerate_children(
"standard::*",
Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
Gio.FileQueryInfoFlags.NONE,
null
)) {
this.#files.push(`${dir.get_path()}/${file.get_name()}`);
this.#files.push(file);
}
}
this.#fuse = new Fuse<string>(
this.#files as ReadonlyArray<string>,
{
useExtendedSearch: false,
shouldSort: true,
isCaseSensitive: false
}
this.#files.map(inf => inf.get_name()) as ReadonlyArray<string>,
this.#options
);
}
private wallpaperResult(path: string): Runner.Result {
private result(info: Gio.FileInfo): Runner.Result {
return {
title: path.split('/')[path.split('/').length-1].replace(/\..*$/, ""),
actionClick: () => Wallpaper.getDefault().setWallpaper(path)
title: `${info.get_display_name()}${info.get_file_type() === Gio.FileType.DIRECTORY ? "/" : ""}`,
icon: info.get_file_type() === Gio.FileType.DIRECTORY ?
"inode-directory-symbolic"
: undefined,
closeOnClick: info.get_file_type() !== Gio.FileType.DIRECTORY,
actionClick: () => {
if(info.get_file_type() === Gio.FileType.DIRECTORY) {
Runner.setEntryText(
this.#subdir !== undefined ?
`#${this.#subdir.startsWith('/') ?
this.#subdir
: `/${this.#subdir}`}/${info.get_name()}/`
: `#/${info.get_name()}/`
);
return;
}
Wallpaper.getDefault().setWallpaper(
`${Wallpaper.getDefault().wallpapersPath}/${
this.#subdir ? `${this.#subdir}/` : ""
}${info.get_name()}`
);
}
};
}
handle(search: string, limit?: number) {
if(search.trim().length === 0)
return this.#files.map(path =>
this.wallpaperResult(path)
);
if(this.#files.length > 0)
return this.#fuse.search(search, {
limit: limit ?? Infinity
}).map(result => this.wallpaperResult(result.item));
async handle(search: string, limit?: number) {
if(!GLib.file_test(this.#dir, GLib.FileTest.IS_DIR))
return {
title: "No wallpapers found!",
description: "Define the $WALLPAPERS variable on Hyprland or create a ~/wallpapers directory",
description: "Define the WALLPAPERS variable in Hyprland or create ~/wallpapers",
icon: "image-missing-symbolic"
};
this.#subdir = undefined;
if(search.startsWith('/')) {
let split = search.split('/').filter(s => s.trim() !== "");
search = split.length > 1 && !search.endsWith('/') ?
split[split.length - 1]
: "";
split = split.filter(s => s !== search);
this.#subdir = split.join('/');
if(this.#subdir === undefined) {
this.#fuse = new Fuse<string>(
this.#files.filter(inf =>
inf.get_file_type() === Gio.FileType.DIRECTORY
).map(inf => inf.get_name()) as ReadonlyArray<string>,
this.#options
);
return this.#fuse.search(search.replace(/^\//, "")).map(r => {
const info = this.#files.filter(inf =>
inf.get_name() === r.item
)[0];
return this.result(info);
});
}
// TODO: recursive search for subdirectories
if(GLib.file_test(`${this.#dir}/${this.#subdir}`, GLib.FileTest.IS_DIR)) {
const dir = Gio.File.new_for_path(`${this.#dir}/${this.#subdir}`);
this.#files = [];
for(const file of dir.enumerate_children(
"standard::*",
Gio.FileQueryInfoFlags.NONE,
null
)) {
this.#files.push(file);
}
this.#fuse = new Fuse<string>(
this.#files.map(n => n.get_name()) as ReadonlyArray<string>,
this.#options
);
}
}
if(search.length < 1)
return this.#files.sort(s =>
s.get_file_type() === Gio.FileType.DIRECTORY ? 1 : -1
).map(info => this.result(info));
const results: Array<Runner.Result> = [];
this.#fuse.search(search, {
limit: limit ?? Infinity
}).map(result => {
const info = this.#files.filter(inf =>
inf.get_name() === result.item
)[0];
results.push(this.result(info));
});
if(results.length < 1)
return {
title: "No results found",
description: "Wallpaper with provided name was not found :("
} satisfies Runner.Result;
return results;
}
}
+13 -20
View File
@@ -13,14 +13,14 @@ import AstalBluetooth from "gi://AstalBluetooth";
import AstalNetwork from "gi://AstalNetwork";
import AstalWp from "gi://AstalWp";
export const Status = () =>
(
<Gtk.Button
class={createBinding(Windows.getDefault(), "openWindows").as((openWins) =>
openWins.includes("control-center") ? "open status" : "status"
)}
onClicked={() => Windows.getDefault().toggle("control-center")}
>
<Gtk.Button class={createBinding(Windows.getDefault(), "openWindows").as((openWins) =>
openWins.includes("control-center") ?
"open status"
: "status"
)} onClicked={() => Windows.getDefault().toggle("control-center")}>
<Gtk.Box>
<Gtk.Box class={"volume-indicators"} spacing={5}>
<BatteryStatus
@@ -57,28 +57,21 @@ export const Status = () =>
)}
/>
</Gtk.Box>
<Gtk.Revealer
revealChild={createBinding(Recording.getDefault(), "recording")}
transitionDuration={500}
transitionType={Gtk.RevealerTransitionType.SLIDE_LEFT}
>
<Gtk.Revealer revealChild={createBinding(Recording.getDefault(), "recording")}
transitionDuration={500} transitionType={Gtk.RevealerTransitionType.SLIDE_LEFT}>
<Gtk.Box>
<Gtk.Image
class={"recording state"}
iconName={"media-record-symbolic"}
<Gtk.Image class={"recording state"} iconName={"media-record-symbolic"}
css={"margin-right: 6px;"}
/>
<Gtk.Label
<Gtk.Label label={createBinding(Recording.getDefault(), "recordingTime")}
class={"rec-time"}
label={createBinding(Recording.getDefault(), "recordingTime")}
/>
</Gtk.Box>
</Gtk.Revealer>
<StatusIcons />
</Gtk.Box>
</Gtk.Button>
) as Gtk.Button;
</Gtk.Button> as Gtk.Button;
function VolumeStatus(props: {
class?: string;