✨ ags: add ask popup, make notifications work(finally :3) and more improvements
This commit is contained in:
@@ -12,6 +12,10 @@ export function updateApps(): void {
|
||||
appsList = astalApps.get_list();
|
||||
}
|
||||
|
||||
export function getAstalApps(): AstalApps.Apps {
|
||||
return astalApps;
|
||||
}
|
||||
|
||||
export function getAppsByName(appName: string): (Array<AstalApps.Application>|undefined) {
|
||||
let found: Array<AstalApps.Application> = [];
|
||||
|
||||
|
||||
@@ -2,6 +2,9 @@ import { Gtk } from "astal/gtk3";
|
||||
import { Windows } from "../windows";
|
||||
import { restartInstance } from "./reload-handler";
|
||||
import { Wireplumber } from "./volume";
|
||||
import { startRunnerDefault } from "../window/Runner";
|
||||
import { AskPopup } from "../widget/AskPopup";
|
||||
import { execAsync } from "astal";
|
||||
|
||||
export function handleArguments(request: string): any {
|
||||
const args: Array<string> = request.split(" ");
|
||||
@@ -20,7 +23,18 @@ export function handleArguments(request: string): any {
|
||||
|
||||
case "reload":
|
||||
restartInstance();
|
||||
return "Reloading instance..."
|
||||
return "Restarting instance..."
|
||||
|
||||
case "runner":
|
||||
startRunnerDefault();
|
||||
return "Opening runner..."
|
||||
|
||||
case "test":
|
||||
return AskPopup({
|
||||
onAccept: () => execAsync("notify-send -u normal haha dumb"),
|
||||
text: "Would you accept?",
|
||||
title: "Dumb Question"
|
||||
});
|
||||
|
||||
default:
|
||||
return "command not found! try checking help";
|
||||
@@ -143,6 +157,7 @@ Options:
|
||||
toggle [window_name]: toggles visibility of specified window.
|
||||
reload: creates a new astal instance and removes this one.
|
||||
volume: wireplumber volume controller, see "volume help".
|
||||
runner: open the application runner.
|
||||
help, -h, --help: shows this help message.
|
||||
|
||||
2025 (c) retrozinndev's Hyprland-Dots, licensed under the MIT License.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { exec, execAsync, GObject, monitorFile, Process, readFileAsync, register, signal } from "astal";
|
||||
import { exec, execAsync, GObject, monitorFile, readFileAsync, register, signal } from "astal";
|
||||
import { Connectable } from "astal/binding";
|
||||
|
||||
|
||||
|
||||
@@ -1,172 +0,0 @@
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
import { timeout } from "astal/time";
|
||||
import { Subscribable } from "astal/binding";
|
||||
import { GObject, property, register, Variable } from "astal";
|
||||
import { Windows } from "../windows";
|
||||
import { FloatingNotifications } from "../window/FloatingNotifications";
|
||||
import { Gtk, Widget } from "astal/gtk3";
|
||||
|
||||
@register({ GTypeName: "Notifications" })
|
||||
class NotificationsClass extends GObject.Object implements Subscribable {
|
||||
|
||||
private static instance: NotificationsClass;
|
||||
|
||||
@property(AstalNotifd.Notifd)
|
||||
private notifd: AstalNotifd.Notifd;
|
||||
|
||||
@property(Boolean)
|
||||
private doNotDisturb: boolean = false;
|
||||
|
||||
@property()
|
||||
public notificationHistory: Array<AstalNotifd.Notification> = [];
|
||||
|
||||
@property()
|
||||
public notifications: Variable<Array<AstalNotifd.Notification>> = new Variable<Array<AstalNotifd.Notification>>([]);
|
||||
|
||||
public static getDefault(): NotificationsClass {
|
||||
if(!NotificationsClass.instance) {
|
||||
NotificationsClass.instance = new NotificationsClass();
|
||||
}
|
||||
|
||||
return NotificationsClass.instance;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.notifd = new AstalNotifd.Notifd({
|
||||
ignoreTimeout: true,
|
||||
dontDisturb: false
|
||||
} as AstalNotifd.Notifd.ConstructorProps);
|
||||
|
||||
this.getNotifd().connect("notified", (daemon: AstalNotifd.Notifd, id: number) => {
|
||||
const notification: (AstalNotifd.Notification|null) = daemon.get_notification(id);
|
||||
if(!notification) {
|
||||
console.log("[LOG] Notification is null, ignoring");
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this.doNotDisturb) {
|
||||
this.handleNotification(notification);
|
||||
return;
|
||||
}
|
||||
|
||||
this.addHistory(notification);
|
||||
});
|
||||
}
|
||||
|
||||
public handleNotification(notification: AstalNotifd.Notification): void {
|
||||
Windows.open(FloatingNotifications);
|
||||
|
||||
let tmpArray = this.notifications.get().reverse();
|
||||
tmpArray.push(notification);
|
||||
this.notifications.set(tmpArray.reverse());
|
||||
|
||||
// default timeout if undefined
|
||||
let notificationTimeout = 4000;
|
||||
|
||||
switch(notification.urgency) {
|
||||
case AstalNotifd.Urgency.LOW:
|
||||
notificationTimeout = 2000;
|
||||
break;
|
||||
case AstalNotifd.Urgency.NORMAL:
|
||||
notificationTimeout = 4000;
|
||||
break;
|
||||
}
|
||||
|
||||
notification.urgency !== AstalNotifd.Urgency.CRITICAL &&
|
||||
timeout(notificationTimeout, () => {
|
||||
this.notifications.set(this.notifications.get().filter((item) => item.id !== notification.id));
|
||||
this.addHistory(notification);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public addHistory(notification: AstalNotifd.Notification): void {
|
||||
let tmpArray: Array<AstalNotifd.Notification> = this.notificationHistory.reverse()
|
||||
.filter((item: AstalNotifd.Notification) => item.id !== notification.id);
|
||||
tmpArray.push(notification);
|
||||
this.notificationHistory = tmpArray.reverse();
|
||||
}
|
||||
|
||||
public removeHistory(notification: AstalNotifd.Notification) {
|
||||
this.notificationHistory = this.notificationHistory.filter((curNotification: AstalNotifd.Notification) =>
|
||||
curNotification.id !== notification.id);
|
||||
}
|
||||
|
||||
public getNotifd(): AstalNotifd.Notifd {
|
||||
return this.notifd;
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.notifications.get();
|
||||
}
|
||||
|
||||
subscribe(callback: (list: Array<AstalNotifd.Notification>) => void) {
|
||||
return this.notifications.subscribe(callback);
|
||||
}
|
||||
}
|
||||
|
||||
function NotificationWidget(notification: AstalNotifd.Notification): Gtk.Widget {
|
||||
return new Widget.Box({
|
||||
className: "notification",
|
||||
homogeneous: false,
|
||||
expand: false,
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
children: [
|
||||
new Widget.Box({
|
||||
className: "top",
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
hexpand: true,
|
||||
vexpand: false,
|
||||
children: [
|
||||
new Widget.Icon({
|
||||
className: "icon",
|
||||
visible: notification.appIcon !== "",
|
||||
icon: notification.appIcon || "image-missing",
|
||||
iconSize: Gtk.IconSize.DND,
|
||||
css: ".icon { font-size: 24px; }"
|
||||
}),
|
||||
new Widget.Label({
|
||||
className: "app-name",
|
||||
halign: Gtk.Align.START,
|
||||
label: notification.appName || "Unknown Application"
|
||||
} as Widget.LabelProps),
|
||||
new Widget.Button({
|
||||
className: "close nf",
|
||||
onClick: () => notification.dismiss(),
|
||||
label: ""
|
||||
} as Widget.ButtonProps)
|
||||
]
|
||||
} as Widget.BoxProps),
|
||||
new Widget.Box({
|
||||
className: "content",
|
||||
orientation: Gtk.Orientation.HORIZONTAL,
|
||||
children: [
|
||||
new Widget.Box({
|
||||
className: "image",
|
||||
visible: notification.image !== "",
|
||||
css: `box.image { background-image: url('${notification.image}'); }`
|
||||
} as Widget.BoxProps),
|
||||
new Widget.Box({
|
||||
className: "text",
|
||||
orientation: Gtk.Orientation.VERTICAL,
|
||||
children: [
|
||||
new Widget.Label({
|
||||
className: "summary",
|
||||
useMarkup: true,
|
||||
label: notification.summary
|
||||
}),
|
||||
new Widget.Label({
|
||||
className: "body",
|
||||
useMarkup: true,
|
||||
label: notification.body
|
||||
} as Widget.LabelProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps)
|
||||
]
|
||||
} as Widget.BoxProps);
|
||||
}
|
||||
|
||||
export const Notifications = new NotificationsClass();
|
||||
@@ -0,0 +1,103 @@
|
||||
import { GObject, property, register, signal, timeout } from "astal";
|
||||
import AstalNotifd from "gi://AstalNotifd";
|
||||
|
||||
@register({ GTypeName: "Notifications" })
|
||||
class Notifications extends GObject.Object {
|
||||
private static instance: (Notifications|null) = null;
|
||||
|
||||
#notifications: Array<AstalNotifd.Notification> = [];
|
||||
#history: Array<AstalNotifd.Notification> = [];
|
||||
#connections: Array<number>;
|
||||
|
||||
|
||||
@property()
|
||||
public get notifications() { return this.#notifications };
|
||||
|
||||
@property()
|
||||
public get history() { return this.#history };
|
||||
|
||||
|
||||
@signal(AstalNotifd.Notification)
|
||||
declare notificationAdded: (notification: AstalNotifd.Notification) => void;
|
||||
|
||||
@signal(Number)
|
||||
declare notificationRemoved: (id: number) => void;
|
||||
|
||||
@signal(AstalNotifd.Notification)
|
||||
declare historyAdded: (notification: AstalNotifd.Notification) => void;
|
||||
|
||||
@signal(Number)
|
||||
declare historyRemoved: (id: number) => void;
|
||||
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.#connections = [
|
||||
AstalNotifd.get_default().connect("notified", (notifd, id, _replaced) => {
|
||||
const notification = notifd.get_notification(id);
|
||||
const notifTimeout = 4000;
|
||||
|
||||
this.addNotification(notification, () => {
|
||||
if(notification.urgency !== AstalNotifd.Urgency.CRITICAL)
|
||||
timeout(notifTimeout, () => {
|
||||
this.removeNotification(id);
|
||||
});
|
||||
});
|
||||
}),
|
||||
AstalNotifd.get_default().connect("resolved", (notifd, id, _reason) => {
|
||||
this.removeNotification(id);
|
||||
this.addHistory(notifd.get_notification(id));
|
||||
})
|
||||
];
|
||||
|
||||
this.vfunc_dispose = () => {
|
||||
this.#connections.map((id: number) =>
|
||||
AstalNotifd.get_default().disconnect(id));
|
||||
};
|
||||
}
|
||||
|
||||
public static getDefault(): Notifications {
|
||||
if(!this.instance)
|
||||
this.instance = new Notifications();
|
||||
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
private addHistory(notif: AstalNotifd.Notification, onAdded?: (notif: AstalNotifd.Notification) => void): void {
|
||||
const newArray = this.#history.reverse().filter((item) => item.id !== notif.id);
|
||||
newArray.push(notif);
|
||||
this.#history = newArray.reverse();
|
||||
this.notify("history");
|
||||
this.emit("history-added", notif);
|
||||
onAdded && onAdded(notif);
|
||||
}
|
||||
|
||||
public removeHistory(notif: (AstalNotifd.Notification|number)): void {
|
||||
const notifId = (notif instanceof AstalNotifd.Notification) ? notif.id : notif;
|
||||
this.#history = this.#history.filter((item: AstalNotifd.Notification) =>
|
||||
item.id !== notifId);
|
||||
|
||||
this.notify("history");
|
||||
this.emit("history-removed", notifId);
|
||||
}
|
||||
|
||||
private addNotification(notif: AstalNotifd.Notification, onAdded?: (notif: AstalNotifd.Notification) => void): void {
|
||||
const newArray = this.#notifications.reverse().filter((item) => item.id !== notif.id);
|
||||
newArray.push(notif);
|
||||
this.#notifications = newArray.reverse();
|
||||
this.notify("notifications");
|
||||
this.emit("notification-added", notif);
|
||||
onAdded && onAdded(notif);
|
||||
}
|
||||
|
||||
public removeNotification(notif: (AstalNotifd.Notification|number)): void {
|
||||
const notifId = (notif instanceof AstalNotifd.Notification) ? notif.id : notif;
|
||||
this.#notifications = this.#notifications.filter((item: AstalNotifd.Notification) =>
|
||||
item.id !== notifId);
|
||||
this.notify("notifications");
|
||||
this.emit("notification-removed", notifId);
|
||||
}
|
||||
}
|
||||
|
||||
export { Notifications };
|
||||
@@ -0,0 +1,88 @@
|
||||
import { execAsync, GLib, GObject, register, signal, writeFile } from "astal";
|
||||
import { Subscribable } from "astal/binding";
|
||||
import { Gdk } from "astal/gtk3";
|
||||
import { getDateTime } from "./time";
|
||||
import AstalWp from "gi://AstalWp";
|
||||
|
||||
@register({ GTypeName: "ScreenRecording" })
|
||||
class Recording extends GObject.Object implements Subscribable {
|
||||
|
||||
private static instance: Recording;
|
||||
|
||||
@signal()
|
||||
declare started: () => void;
|
||||
@signal(String)
|
||||
declare stopped: (outputFile: string) => void;
|
||||
@signal(String)
|
||||
declare outputChanged: (newPath: string) => void;
|
||||
|
||||
#recording: boolean = false;
|
||||
#subs = new Set<(isRec: boolean) => void>();
|
||||
#path: string = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_VIDEOS) || `${GLib.get_home_dir()}/Recordings`;
|
||||
/** Default extension: mp4(h264) */
|
||||
#extension: string = "mp4";
|
||||
#recordAudio: boolean|AstalWp.Endpoint = false; // TODO
|
||||
|
||||
private notifySub() {
|
||||
const subs = this.#subs;
|
||||
for(const sub of subs) {
|
||||
sub(this.recording);
|
||||
}
|
||||
}
|
||||
|
||||
public get recording() { return this.#recording; }
|
||||
private set recording(newValue: boolean) { this.#recording = newValue; }
|
||||
|
||||
public get path() { return this.#path; }
|
||||
public set path(newPath: string) { this.#path = newPath; }
|
||||
|
||||
public get extension() { return this.#extension; }
|
||||
public set extension(newExt: string) { this.#extension = newExt; }
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static getDefault() {
|
||||
if(!this.instance)
|
||||
this.instance = new Recording();
|
||||
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
public get() {
|
||||
return this.recording;
|
||||
}
|
||||
|
||||
private emit(id: string, ...args: any[]) {
|
||||
super.emit(id, ...args);
|
||||
this.notifySub();
|
||||
}
|
||||
|
||||
|
||||
public startRecording(area?: Gdk.Rectangle) {
|
||||
const output = `${getDateTime().get().format("%Y-%m-%d-%H%M%S")}_rec.${this.extension}`;
|
||||
execAsync([ "wf-recorder",
|
||||
`${Boolean(area) ?
|
||||
`-g ${area?.x || 0},${area?.y || 0} ${area?.width || 1}x${area?.height || 1}`
|
||||
: ""}`,
|
||||
"-f", output ]
|
||||
).then(() => {
|
||||
this.emit("stopped", `${this.path}/${output}`);
|
||||
});
|
||||
writeFile("", "");
|
||||
this.emit("started");
|
||||
this.notifySub();
|
||||
}
|
||||
|
||||
public stopRecording() {
|
||||
|
||||
}
|
||||
|
||||
public subscribe(callback: (isRec: boolean) => void) {
|
||||
this.#subs.add(callback);
|
||||
return () => this.#subs.delete(callback);
|
||||
}
|
||||
}
|
||||
|
||||
export { Recording };
|
||||
@@ -0,0 +1,15 @@
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
import { getAstalApps } from "../apps";
|
||||
import { ResultWidget, ResultWidgetProps } from "../../widget/runner/ResultWidget";
|
||||
import AstalApps from "gi://AstalApps";
|
||||
|
||||
export function handleApplications(search: string): (Array<ResultWidget>|null) {
|
||||
return getAstalApps().fuzzy_query(search).map((app: AstalApps.Application) =>
|
||||
new ResultWidget({
|
||||
title: app.get_name(),
|
||||
description: app.get_description(),
|
||||
icon: app.iconName,
|
||||
onClick: () => AstalHyprland.get_default().dispatch("exec", app.get_executable())
|
||||
} as ResultWidgetProps)
|
||||
) || null;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import { ResultWidget, ResultWidgetProps } from "../../widget/runner/ResultWidget";
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
import { GLib } from "astal";
|
||||
|
||||
export function handleShell(command: string): ResultWidget {
|
||||
const userShell = GLib.getenv("SHELL") || "/usr/bin/env bash";
|
||||
|
||||
return new ResultWidget({
|
||||
onClick: () => AstalHyprland.get_default().dispatch("exec", `${userShell} -c "${command}"`),
|
||||
title: `Run: \`${command}\``,
|
||||
description: userShell,
|
||||
icon: "utilities-terminal-symbolic"
|
||||
} as ResultWidgetProps);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import AstalHyprland from "gi://AstalHyprland";
|
||||
import { ResultWidget, ResultWidgetProps } from "../../widget/runner/ResultWidget";
|
||||
|
||||
export enum SearchEngine {
|
||||
GOOGLE,
|
||||
DUCKDUCKGO,
|
||||
YAHOO
|
||||
}
|
||||
|
||||
export const SearchEngineMap: Map<SearchEngine, string> = new Map([
|
||||
[ SearchEngine.DUCKDUCKGO, "https://duckduckgo.com/?q=" ],
|
||||
[ SearchEngine.GOOGLE, "https://google.com/search?q=" ],
|
||||
[ SearchEngine.YAHOO, "https://search.yahoo.com/search?p=" ]
|
||||
]);
|
||||
|
||||
let searchEngine: SearchEngine = SearchEngine.GOOGLE;
|
||||
|
||||
export function handleWebSearch(search: string): ResultWidget {
|
||||
|
||||
let engineString: string;
|
||||
|
||||
switch(searchEngine as SearchEngine) {
|
||||
case SearchEngine.GOOGLE:
|
||||
engineString = "Google";
|
||||
case SearchEngine.YAHOO:
|
||||
engineString = "Yahoo";
|
||||
case SearchEngine.DUCKDUCKGO:
|
||||
engineString = "DuckDuckGo";
|
||||
default: engineString = "Web";
|
||||
|
||||
}
|
||||
|
||||
return new ResultWidget({
|
||||
icon: "system-search-symbolic",
|
||||
title: search || "",
|
||||
description: `Search with ${engineString}`,
|
||||
onClick: () => AstalHyprland.get_default().dispatch(
|
||||
"exec",
|
||||
`xdg-open "${SearchEngineMap.get(searchEngine)! + search.replaceAll(" ", "%20")}"`
|
||||
)
|
||||
} as ResultWidgetProps);
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
// handles reloading stylesheet and pywal colors
|
||||
|
||||
import { readFile, monitorFile, Process } from "astal";
|
||||
import { readFile, monitorFile, AstalIO, exec, timeout } from "astal";
|
||||
import { App } from "astal/gtk3";
|
||||
import { getUserDirs } from "./user";
|
||||
import { getUserDirs } from "./utils";
|
||||
|
||||
let watchDelay: (AstalIO.Time|null);
|
||||
|
||||
const stylePath = `${getUserDirs().state}/ags/style`
|
||||
const watchPaths = [
|
||||
@@ -22,8 +24,8 @@ export function reloadStyle(): void {
|
||||
|
||||
export function compileStyle(): void {
|
||||
console.log("[LOG] Compiling sass (stylesheet)");
|
||||
Process.exec(`mkdir -p ${stylePath}`);
|
||||
Process.exec(`sh -c "sass -I ./style ./style.scss ${stylePath}/style.css"`);
|
||||
exec(`mkdir -p ${stylePath}`);
|
||||
exec(`sh -c "sass -I ./style ./style.scss ${stylePath}/style.css"`);
|
||||
}
|
||||
|
||||
export function applyStyle(): void {
|
||||
@@ -34,14 +36,15 @@ export function applyStyle(): void {
|
||||
);
|
||||
}
|
||||
|
||||
/** Monitor changes on stylesheet at runtime */
|
||||
function watch(): void {
|
||||
// Monitor changes on stylesheet at runtime
|
||||
watchPaths.map((path: string) =>
|
||||
monitorFile(
|
||||
`${path}`,
|
||||
(file: string) => {
|
||||
// Ignore tmp files
|
||||
if(!file.endsWith('~') && !Number.isNaN(file)) {
|
||||
if(!watchDelay && !file.endsWith('~') && !Number.isNaN(file)) {
|
||||
watchDelay = timeout(250, () => watchDelay = null);
|
||||
console.log(`[LOG] Stylesheet ${file} file updated`)
|
||||
compileStyle();
|
||||
applyStyle();
|
||||
@@ -54,7 +57,7 @@ function watch(): void {
|
||||
monitorFile(
|
||||
`${getUserDirs().cache}/wal/colors.scss`,
|
||||
(file: string) => {
|
||||
Process.exec(`bash -c "cp -f ${file} ./style/_wal.scss"`)
|
||||
exec(`bash -c "cp -f ${file} ./style/_wal.scss"`)
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { GLib } from "astal";
|
||||
import { execAsync, GLib } from "astal";
|
||||
|
||||
export function getUserDirs() {
|
||||
return {
|
||||
@@ -7,5 +7,13 @@ export function getUserDirs() {
|
||||
cache: GLib.getenv("XDG_CACHE_HOME"),
|
||||
config: GLib.getenv("XDG_CONFIG_HOME"),
|
||||
data: GLib.getenv("XDG_DATA_HOME")
|
||||
} as const;
|
||||
};
|
||||
}
|
||||
|
||||
export function makeDirectory(dir: string): void {
|
||||
execAsync([ "mkdir", "-p", dir ]);
|
||||
}
|
||||
|
||||
export function deleteFile(path: string): void {
|
||||
execAsync([ "rm", "-r", path ]);
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import { Subscribable } from "astal/binding";
|
||||
|
||||
export class VarMap<K, V> implements Subscribable {
|
||||
|
||||
#subs = new Set<(v: Map<K, V>) => void>();
|
||||
#map: Map<K, V>;
|
||||
|
||||
constructor(initial?: Map<K, V>) {
|
||||
this.#map = initial || new Map<K, V>();
|
||||
}
|
||||
|
||||
private notifyMap() {
|
||||
const subs = this.#subs;
|
||||
for(const sub of subs) {
|
||||
sub(this.#map);
|
||||
}
|
||||
}
|
||||
|
||||
public get(): Map<K, V> {
|
||||
return this.#map;
|
||||
}
|
||||
|
||||
public get size(): number {
|
||||
return this.#map.size;
|
||||
}
|
||||
|
||||
public getValue(key: K): (V|undefined) {
|
||||
return this.#map.get(key);
|
||||
}
|
||||
|
||||
public getKeyAt(index: number): (K|undefined) {
|
||||
return [...this.#map.keys()][index];
|
||||
}
|
||||
|
||||
public getValueAt(index: number): (V|undefined) {
|
||||
return [...this.#map.values()][index];
|
||||
}
|
||||
|
||||
public set(key: K, value: V): Map<K, V> {
|
||||
const newMap: Map<K, V> = this.#map.set(key, value);
|
||||
this.notifyMap();
|
||||
|
||||
return newMap;
|
||||
}
|
||||
|
||||
public delete(key: K): boolean {
|
||||
const deleted: boolean = this.#map.delete(key);
|
||||
this.notifyMap();
|
||||
return deleted;
|
||||
}
|
||||
|
||||
public has(key: K): boolean {
|
||||
return this.#map.has(key);
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this.#map.clear();
|
||||
this.notifyMap();
|
||||
}
|
||||
|
||||
public entries(): MapIterator<[K, V]> {
|
||||
return this.#map.entries();
|
||||
}
|
||||
|
||||
public keys(): MapIterator<K> {
|
||||
return this.#map.keys();
|
||||
}
|
||||
|
||||
public values(): MapIterator<V> {
|
||||
return this.#map.values();
|
||||
}
|
||||
|
||||
public forEach<ReturnType = any> (callback: (value: V, key: K, map: Map<K, V>) => ReturnType): ReturnType[] {
|
||||
const result: Array<ReturnType> = [];
|
||||
for(const entry of this.#map.entries()) {
|
||||
result.push(callback(entry[1], entry[0], this.#map));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public subscribe(callback: (v: Map<K, V>) => void): () => void {
|
||||
this.#subs.add(callback);
|
||||
|
||||
return () => {
|
||||
this.#subs.delete(callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user