mirror of
https://github.com/MathMan05/Fermi.git
synced 2026-08-29 09:58:45 +00:00
stacked context menus and more group improvements
This commit is contained in:
+115
-11
@@ -11,7 +11,14 @@ type iconJson =
|
||||
};
|
||||
|
||||
interface menuPart<x, y> {
|
||||
makeContextHTML(obj1: x, obj2: y, menu: HTMLDivElement): void;
|
||||
group?: string;
|
||||
makeContextHTML(
|
||||
obj1: x,
|
||||
obj2: y,
|
||||
menu: HTMLDivElement,
|
||||
layered: contextCluster<unknown, unknown>[],
|
||||
processed: WeakSet<menuPart<unknown, unknown>>,
|
||||
): void;
|
||||
}
|
||||
|
||||
class ContextButton<x, y> implements menuPart<x, y> {
|
||||
@@ -22,6 +29,7 @@ class ContextButton<x, y> implements menuPart<x, y> {
|
||||
private enabled?: (this: x, arg: y) => boolean;
|
||||
//TODO there *will* be more colors
|
||||
private color?: "red" | "blue";
|
||||
group?: string;
|
||||
constructor(
|
||||
text: ContextButton<x, y>["text"],
|
||||
onClick: ContextButton<x, y>["onClick"],
|
||||
@@ -30,6 +38,7 @@ class ContextButton<x, y> implements menuPart<x, y> {
|
||||
visable?: (this: x, arg: y) => boolean;
|
||||
enabled?: (this: x, arg: y) => boolean;
|
||||
color?: "red" | "blue";
|
||||
group?: string;
|
||||
} = {},
|
||||
) {
|
||||
this.text = text;
|
||||
@@ -38,6 +47,7 @@ class ContextButton<x, y> implements menuPart<x, y> {
|
||||
this.visable = addProps.visable;
|
||||
this.enabled = addProps.enabled;
|
||||
this.color = addProps.color;
|
||||
this.group = addProps.group;
|
||||
}
|
||||
isVisable(obj1: x, obj2: y): boolean {
|
||||
if (!this.visable) return true;
|
||||
@@ -102,10 +112,50 @@ class ContextButton<x, y> implements menuPart<x, y> {
|
||||
return this.text;
|
||||
}
|
||||
}
|
||||
class ContextGroup<x, y> implements menuPart<x, y> {
|
||||
private visable?: (this: x, arg: y) => boolean;
|
||||
groupSel: string;
|
||||
group = undefined;
|
||||
constructor(
|
||||
group: string,
|
||||
addProps: {
|
||||
visable?: (this: x, arg: y) => boolean;
|
||||
} = {},
|
||||
) {
|
||||
this.visable = addProps.visable;
|
||||
|
||||
this.groupSel = group;
|
||||
}
|
||||
isVisable(obj1: x, obj2: y): boolean {
|
||||
if (!this.visable) return true;
|
||||
return this.visable.call(obj1, obj2);
|
||||
}
|
||||
makeContextHTML(
|
||||
x: x,
|
||||
y: y,
|
||||
menuHtml: HTMLDivElement,
|
||||
layered: contextCluster<unknown, unknown>[],
|
||||
processed: WeakSet<menuPart<unknown, unknown>>,
|
||||
) {
|
||||
if (!this.isVisable(x, y)) {
|
||||
return;
|
||||
}
|
||||
for (const [menu, x, y] of layered) {
|
||||
for (const part of menu.buttons) {
|
||||
if (part.group === this.groupSel && !processed.has(part)) {
|
||||
processed.add(part);
|
||||
part.makeContextHTML(x, y, menuHtml, [], processed);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
class Seperator<x, y> implements menuPart<x, y> {
|
||||
private visable?: (obj1: x, obj2: y) => boolean;
|
||||
constructor(visable?: (obj1: x, obj2: y) => boolean) {
|
||||
group?: string;
|
||||
constructor(visable?: (obj1: x, obj2: y) => boolean, group?: string) {
|
||||
this.visable = visable;
|
||||
this.group = group;
|
||||
}
|
||||
makeContextHTML(obj1: x, obj2: y, menu: HTMLDivElement): void {
|
||||
if (!this.visable || this.visable(obj1, obj2)) {
|
||||
@@ -116,6 +166,28 @@ class Seperator<x, y> implements menuPart<x, y> {
|
||||
}
|
||||
}
|
||||
}
|
||||
declare global {
|
||||
interface HTMLElementEventMap {
|
||||
layered: LayeredEvent;
|
||||
}
|
||||
}
|
||||
type contextCluster<X, Y> = [Contextmenu<X, Y>, X, Y];
|
||||
class LayeredEvent extends CustomEvent<unknown> {
|
||||
menus: contextCluster<unknown, unknown>[];
|
||||
primary?: contextCluster<unknown, unknown>;
|
||||
constructor(mouse: MouseEvent, menus: LayeredEvent["menus"]) {
|
||||
super("layered", {bubbles: true});
|
||||
this.menus = menus;
|
||||
queueMicrotask(() => {
|
||||
console.log(this);
|
||||
const pop = this.primary || menus.pop();
|
||||
if (!pop) return;
|
||||
const [menu, addinfo, other] = pop;
|
||||
menu.makemenu(mouse.clientX, mouse.clientY, addinfo, other, undefined, menus);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class Contextmenu<x, y> {
|
||||
static currentmenu: HTMLElement | "" = "";
|
||||
static prevmenus: HTMLElement[] = [];
|
||||
@@ -152,8 +224,10 @@ class Contextmenu<x, y> {
|
||||
}
|
||||
});
|
||||
}
|
||||
constructor(name: string) {
|
||||
private layered = false;
|
||||
constructor(name: string, layered = false) {
|
||||
this.name = name;
|
||||
this.layered = layered;
|
||||
this.buttons = [];
|
||||
}
|
||||
|
||||
@@ -165,24 +239,45 @@ class Contextmenu<x, y> {
|
||||
visable?: (this: x, arg: y) => boolean;
|
||||
enabled?: (this: x, arg: y) => boolean;
|
||||
color?: "red" | "blue";
|
||||
group?: string;
|
||||
} = {},
|
||||
) {
|
||||
this.buttons.push(new ContextButton(text, onClick, addProps));
|
||||
}
|
||||
addSeperator(visable?: (obj1: x, obj2: y) => boolean) {
|
||||
this.buttons.push(new Seperator(visable));
|
||||
addSeperator(visable?: (obj1: x, obj2: y) => boolean, group?: string) {
|
||||
this.buttons.push(new Seperator(visable, group));
|
||||
}
|
||||
makemenu(x: number, y: number, addinfo: x, other: y, keep: boolean | HTMLElement = false) {
|
||||
addGroup(
|
||||
group: string,
|
||||
addprops?: {
|
||||
visable?: (this: x, arg: y) => boolean;
|
||||
},
|
||||
) {
|
||||
this.buttons.push(new ContextGroup<x, y>(group, addprops));
|
||||
}
|
||||
makemenu(
|
||||
x: number,
|
||||
y: number,
|
||||
addinfo: x,
|
||||
other: y,
|
||||
keep: boolean | HTMLElement = false,
|
||||
layered: LayeredEvent["menus"] = [],
|
||||
) {
|
||||
const div = document.createElement("div");
|
||||
div.classList.add("contextmenu", "flexttb");
|
||||
const processed = new WeakSet<menuPart<unknown, unknown>>();
|
||||
|
||||
for (const button of this.buttons) {
|
||||
button.makeContextHTML(addinfo, other, div);
|
||||
button.makeContextHTML(addinfo, other, div, layered, processed);
|
||||
}
|
||||
if (div.children[div.children.length - 1]?.tagName === "HR") {
|
||||
if (div.children[div.children.length - 1]?.tagName !== "HR") {
|
||||
div.append(document.createElement("hr"));
|
||||
}
|
||||
new ContextGroup<x, y>("default").makeContextHTML(addinfo, other, div, layered, processed);
|
||||
|
||||
while (div.children[div.children.length - 1]?.tagName === "HR") {
|
||||
div.children[div.children.length - 1].remove();
|
||||
}
|
||||
//NOTE I don't know if this'll ever actually happen in reality
|
||||
if (div.childNodes.length === 0) return;
|
||||
|
||||
Contextmenu.declareMenu(div, keep);
|
||||
@@ -223,10 +318,19 @@ class Contextmenu<x, y> {
|
||||
}
|
||||
}
|
||||
}
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
this.makemenu(event.clientX, event.clientY, addinfo, other);
|
||||
event.preventDefault();
|
||||
const layered = new LayeredEvent(event, []);
|
||||
obj.dispatchEvent(layered);
|
||||
};
|
||||
obj.addEventListener("layered", (layered) => {
|
||||
if (this.layered) {
|
||||
layered.menus.push([this, addinfo, other]);
|
||||
} else if (!layered.primary) {
|
||||
layered.primary = [this, addinfo, other];
|
||||
}
|
||||
return;
|
||||
});
|
||||
if (click === "right") {
|
||||
obj.addEventListener("contextmenu", func);
|
||||
} else {
|
||||
|
||||
+25
-1
@@ -434,6 +434,30 @@ class Group extends Channel {
|
||||
users: User[];
|
||||
owner_id?: string;
|
||||
static contextmenu = new Contextmenu<Group, undefined>("channel menu");
|
||||
static groupMenu = this.makeGroupMenu();
|
||||
static makeGroupMenu() {
|
||||
const menu = new Contextmenu<Group, User>("group menu", true);
|
||||
menu.addButton(
|
||||
function (user) {
|
||||
return I18n.member.kick(user.name, this.name);
|
||||
},
|
||||
function (user) {
|
||||
fetch(this.info.api + `/channels/${this.id}/recipients/${user.id}`, {
|
||||
method: "DELETE",
|
||||
headers: this.headers,
|
||||
});
|
||||
},
|
||||
{
|
||||
group: "default",
|
||||
visable: function (user) {
|
||||
return this.localuser.user.id !== user.id && this.owner_id === this.localuser.user.id;
|
||||
},
|
||||
color: "red",
|
||||
},
|
||||
);
|
||||
return menu;
|
||||
}
|
||||
|
||||
static setupcontextmenu() {
|
||||
this.contextmenu.addButton(
|
||||
() => I18n.DMs.markRead(),
|
||||
@@ -451,7 +475,7 @@ class Group extends Channel {
|
||||
},
|
||||
{
|
||||
visable: function () {
|
||||
return this.users.length !== 1;
|
||||
return this.type !== 1;
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1119,7 +1119,7 @@ class Localuser {
|
||||
if (channel.voice && this.voiceAllowed) {
|
||||
return;
|
||||
}
|
||||
if (guild.id === "@me" && (channel as Group).users.length === 1) {
|
||||
if (guild.id === "@me" && (channel as Group).type === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1221,17 +1221,19 @@ class Localuser {
|
||||
bot.textContent = I18n.bot();
|
||||
username.appendChild(bot);
|
||||
}
|
||||
member.bind(username);
|
||||
user.bind(memberdiv, member instanceof Member ? member.guild : undefined, false);
|
||||
|
||||
memberdiv.append(pfp, username);
|
||||
if (channel instanceof Group) {
|
||||
console.log(channel.owner_id);
|
||||
Group.groupMenu.bindContextmenu(memberdiv, channel, user);
|
||||
if (channel.owner_id === user.id) {
|
||||
const crown = document.createElement("span");
|
||||
crown.classList.add("svg-crown");
|
||||
memberdiv.append(crown);
|
||||
}
|
||||
}
|
||||
member.bind(username);
|
||||
user.bind(memberdiv, member instanceof Member ? member.guild : undefined, false);
|
||||
|
||||
memberdiv.classList.add("flexltr", "liststyle", "memberListStyle");
|
||||
membershtml.append(memberdiv);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user