diff --git a/src/webpage/channel.ts b/src/webpage/channel.ts index d6ed6d2..5213746 100644 --- a/src/webpage/channel.ts +++ b/src/webpage/channel.ts @@ -73,56 +73,73 @@ class Channel extends SnowFlake { this.mute_config = settings.mute_config; } static setupcontextmenu() { - this.contextmenu.addbutton( - () => I18n.getTranslation("channel.copyId"), - function (this: Channel) { - navigator.clipboard.writeText(this.id); - }, - ); - - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("channel.markRead"), function (this: Channel) { this.readbottom(); }, ); - this.contextmenu.addbutton( - () => I18n.getTranslation("channel.settings"), + //TODO invite icon + this.contextmenu.addButton( + () => I18n.getTranslation("channel.makeInvite"), function (this: Channel) { - this.generateSettings(); + this.createInvite(); }, - null, - function () { - return this.hasPermission("MANAGE_CHANNELS"); + { + visable: function () { + return this.hasPermission("CREATE_INSTANT_INVITE") && this.type !== 4; + }, + color: "blue", }, ); - - this.contextmenu.addbutton( - () => I18n.getTranslation("channel.delete"), - function (this: Channel) { - this.deleteChannel(); - }, - null, - function () { - return this.isAdmin(); - }, - ); - this.contextmenu.addbutton( + this.contextmenu.addSeperator(); + //TODO notifcations icon + this.contextmenu.addButton( () => I18n.getTranslation("guild.notifications"), function () { this.setnotifcation(); }, ); - this.contextmenu.addbutton( - () => I18n.getTranslation("channel.makeInvite"), + this.contextmenu.addButton( + () => I18n.getTranslation("channel.settings"), function (this: Channel) { - this.createInvite(); + this.generateSettings(); }, - null, - function () { - return this.hasPermission("CREATE_INSTANT_INVITE") && this.type !== 4; + { + visable: function () { + return this.hasPermission("MANAGE_CHANNELS"); + }, + icon: { + css: "svg-settings", + }, + }, + ); + + this.contextmenu.addButton( + () => I18n.getTranslation("channel.delete"), + function (this: Channel) { + this.deleteChannel(); + }, + { + visable: function () { + //TODO there is no way that this is correct + return this.isAdmin(); + }, + icon: { + css: "svg-delete", + }, + color: "red", + }, + ); + + this.contextmenu.addSeperator(); + //TODO copy ID icon + this.contextmenu.addButton( + () => I18n.getTranslation("channel.copyId"), + function (this: Channel) { + navigator.clipboard.writeText(this.id); }, ); } diff --git a/src/webpage/contextmenu.ts b/src/webpage/contextmenu.ts index fe73808..e4bbbff 100644 --- a/src/webpage/contextmenu.ts +++ b/src/webpage/contextmenu.ts @@ -1,15 +1,114 @@ -import {iOS} from "./utils/utils.js"; +import {mobile} from "./utils/utils.js"; +type iconJson = + | { + src: string; + } + | { + css: string; + } + | { + html: HTMLElement; + }; + +interface menuPart { + makeContextHTML(obj1: x, obj2: y, menu: HTMLDivElement): void; +} + +class ContextButton implements menuPart { + private text: string | (() => string); + private onClick: (this: x, arg: y, e: MouseEvent) => void; + private icon?: iconJson; + private visable?: (this: x, arg: y) => boolean; + private enabled?: (this: x, arg: y) => boolean; + //TODO there *will* be more colors + private color?: "red" | "blue"; + constructor( + text: ContextButton["text"], + onClick: ContextButton["onClick"], + addProps: { + icon?: iconJson; + visable?: (this: x, arg: y) => boolean; + enabled?: (this: x, arg: y) => boolean; + color?: "red" | "blue"; + } = {}, + ) { + this.text = text; + this.onClick = onClick; + this.icon = addProps.icon; + this.visable = addProps.visable; + this.enabled = addProps.enabled; + this.color = addProps.color; + } + isVisable(obj1: x, obj2: y): boolean { + if (!this.visable) return true; + return this.visable.call(obj1, obj2); + } + makeContextHTML(obj1: x, obj2: y, menu: HTMLDivElement) { + if (!this.isVisable(obj1, obj2)) { + return; + } + + const intext = document.createElement("button"); + intext.classList.add("contextbutton"); + intext.append(this.textContent); + + intext.disabled = !!this.enabled && !this.enabled.call(obj1, obj2); + + if (this.icon) { + if ("src" in this.icon) { + const icon = document.createElement("img"); + icon.classList.add("svgicon"); + icon.src = this.icon.src; + intext.append(icon); + } else if ("css" in this.icon) { + const icon = document.createElement("span"); + icon.classList.add(this.icon.css, "svgicon"); + switch (this.color) { + case "red": + icon.style.background = "var(--red)"; + break; + case "blue": + icon.style.background = "var(--blue)"; + break; + } + intext.append(icon); + } else { + intext.append(this.icon.html); + } + } + + switch (this.color) { + case "red": + intext.style.color = "var(--red)"; + break; + case "blue": + intext.style.color = "var(--blue)"; + break; + } + + intext.onclick = (e) => { + menu.remove(); + this.onClick.call(obj1, obj2, e); + }; + + menu.append(intext); + } + get textContent() { + if (this.text instanceof Function) { + return this.text(); + } + return this.text; + } +} +class Seperator implements menuPart { + makeContextHTML(_obj1: x, _obj2: y, menu: HTMLDivElement): void { + menu.append(document.createElement("hr")); + } +} class Contextmenu { static currentmenu: HTMLElement | ""; name: string; - buttons: [ - string | (() => string), - (this: x, arg: y, e: MouseEvent) => void, - string | null, - (this: x, arg: y) => boolean, - (this: x, arg: y) => boolean, - string, - ][]; + buttons: menuPart[]; div!: HTMLDivElement; static setup() { Contextmenu.currentmenu = ""; @@ -27,56 +126,33 @@ class Contextmenu { this.name = name; this.buttons = []; } - addbutton( - text: string | (() => string), - onclick: (this: x, arg: y, e: MouseEvent) => void, - img: null | string = null, - shown: (this: x, arg: y) => boolean = (_) => true, - enabled: (this: x, arg: y) => boolean = (_) => true, + + addButton( + text: ContextButton["text"], + onClick: ContextButton["onClick"], + addProps: { + icon?: iconJson; + visable?: (this: x, arg: y) => boolean; + enabled?: (this: x, arg: y) => boolean; + color?: "red" | "blue"; + } = {}, ) { - this.buttons.push([text, onclick, img, shown, enabled, "button"]); - return {}; + this.buttons.push(new ContextButton(text, onClick, addProps)); } - addsubmenu( - text: string | (() => string), - onclick: (this: x, arg: y, e: MouseEvent) => void, - img = null, - shown: (this: x, arg: y) => boolean = (_) => true, - enabled: (this: x, arg: y) => boolean = (_) => true, - ) { - this.buttons.push([text, onclick, img, shown, enabled, "submenu"]); - return {}; + addSeperator() { + this.buttons.push(new Seperator()); } private makemenu(x: number, y: number, addinfo: x, other: y) { const div = document.createElement("div"); div.classList.add("contextmenu", "flexttb"); - let visibleButtons = 0; - for (const thing of this.buttons) { - if (!thing[3].call(addinfo, other)) continue; - visibleButtons++; - - const intext = document.createElement("button"); - intext.disabled = !thing[4].call(addinfo, other); - intext.classList.add("contextbutton"); - if (thing[0] instanceof Function) { - intext.textContent = thing[0](); - } else { - intext.textContent = thing[0]; - } - console.log(thing); - if (thing[5] === "button" || thing[5] === "submenu") { - intext.onclick = (e) => { - div.remove(); - thing[1].call(addinfo, other, e); - }; - } - - div.appendChild(intext); + for (const button of this.buttons) { + button.makeContextHTML(addinfo, other, div); } - if (visibleButtons == 0) return; + //NOTE I don't know if this'll ever actually happen in reality + if (div.childNodes.length === 0) return; - if (Contextmenu.currentmenu != "") { + if (Contextmenu.currentmenu !== "") { Contextmenu.currentmenu.remove(); } div.style.top = y + "px"; @@ -100,7 +176,8 @@ class Contextmenu { this.makemenu(event.clientX, event.clientY, addinfo, other); }; obj.addEventListener("contextmenu", func); - if (iOS) { + //NOTE not sure if this code is correct, seems fine at least for now + if (mobile) { let hold: NodeJS.Timeout | undefined; let x!: number; let y!: number; diff --git a/src/webpage/direct.ts b/src/webpage/direct.ts index 922644d..533b10e 100644 --- a/src/webpage/direct.ts +++ b/src/webpage/direct.ts @@ -342,28 +342,28 @@ class Group extends Channel { user: User; static contextmenu = new Contextmenu("channel menu"); static setupcontextmenu() { - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("DMs.copyId"), function (this: Group) { navigator.clipboard.writeText(this.id); }, ); - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("DMs.markRead"), function (this: Group) { this.readbottom(); }, ); - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("DMs.close"), function (this: Group) { this.deleteChannel(); }, ); - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("user.copyId"), function () { navigator.clipboard.writeText(this.user.id); diff --git a/src/webpage/guild.ts b/src/webpage/guild.ts index a305f71..f76093e 100644 --- a/src/webpage/guild.ts +++ b/src/webpage/guild.ts @@ -39,89 +39,96 @@ class Guild extends SnowFlake { members = new Set(); static contextmenu = new Contextmenu("guild menu"); static setupcontextmenu() { - Guild.contextmenu.addbutton( - () => I18n.getTranslation("guild.copyId"), - function (this: Guild) { - navigator.clipboard.writeText(this.id); - }, - ); - - Guild.contextmenu.addbutton( - () => I18n.getTranslation("guild.markRead"), - function (this: Guild) { - this.markAsRead(); - }, - ); - - Guild.contextmenu.addbutton( - () => I18n.getTranslation("guild.notifications"), - function (this: Guild) { - this.setnotifcation(); - }, - ); - - this.contextmenu.addbutton( - () => I18n.getTranslation("user.editServerProfile"), - function () { - this.member.showEditProfile(); - }, - ); - - Guild.contextmenu.addbutton( - () => I18n.getTranslation("guild.leave"), - function (this: Guild) { - this.confirmleave(); - }, - null, - function (_) { - return this.properties.owner_id !== this.member.user.id; - }, - ); - - Guild.contextmenu.addbutton( - () => I18n.getTranslation("guild.delete"), - function (this: Guild) { - this.confirmDelete(); - }, - null, - function (_) { - return this.properties.owner_id === this.member.user.id; - }, - ); - - Guild.contextmenu.addbutton( + Guild.contextmenu.addButton( () => I18n.getTranslation("guild.makeInvite"), function (this: Guild) { const d = new Dialog(""); this.makeInviteMenu(d.options); d.show(); }, - null, - (_) => true, - function () { - return this.member.hasPermission("CREATE_INSTANT_INVITE"); + { + enabled: function () { + return this.member.hasPermission("CREATE_INSTANT_INVITE"); + }, + color: "blue", }, ); - Guild.contextmenu.addbutton( + Guild.contextmenu.addSeperator(); + + Guild.contextmenu.addButton( + () => I18n.getTranslation("guild.markRead"), + function (this: Guild) { + this.markAsRead(); + }, + ); + + Guild.contextmenu.addButton( + () => I18n.getTranslation("guild.notifications"), + function (this: Guild) { + this.setnotifcation(); + }, + ); + Guild.contextmenu.addSeperator(); + this.contextmenu.addButton( + () => I18n.getTranslation("user.editServerProfile"), + function () { + this.member.showEditProfile(); + }, + ); + Guild.contextmenu.addSeperator(); + + Guild.contextmenu.addButton( + () => I18n.getTranslation("guild.leave"), + function (this: Guild) { + this.confirmleave(); + }, + { + visable: function (_) { + return this.properties.owner_id !== this.member.user.id; + }, + color: "red", + }, + ); + + Guild.contextmenu.addButton( + () => I18n.getTranslation("guild.delete"), + function (this: Guild) { + this.confirmDelete(); + }, + { + visable: function (_) { + return this.properties.owner_id === this.member.user.id; + }, + color: "red", + icon: { + css: "svg-delete", + }, + }, + ); + + Guild.contextmenu.addButton( () => I18n.getTranslation("guild.settings"), function (this: Guild) { this.generateSettings(); }, - null, - function () { - return this.member.hasPermission("MANAGE_GUILD"); + { + visable: function () { + return this.member.hasPermission("MANAGE_GUILD"); + }, + icon: { + css: "svg-settings", + }, }, ); - /* -----things left for later----- - guild.contextmenu.addbutton("Leave Guild",function(){ - console.log(this) - this.deleteChannel(); - },null,_=>{return thisuser.isAdmin()}) - guild.contextmenu.addbutton("Mute Guild",function(){ - editchannelf(this); - },null,_=>{return thisuser.isAdmin()}) - */ + Guild.contextmenu.addSeperator(); + Guild.contextmenu.addButton( + () => I18n.getTranslation("guild.copyId"), + function (this: Guild) { + navigator.clipboard.writeText(this.id); + }, + ); + //TODO mute guild button } generateSettings() { const settings = new Settings(I18n.getTranslation("guild.settingsFor", this.properties.name)); diff --git a/src/webpage/index.ts b/src/webpage/index.ts index 9542de7..f400d97 100644 --- a/src/webpage/index.ts +++ b/src/webpage/index.ts @@ -127,26 +127,24 @@ import {I18n} from "./i18n.js"; } const menu = new Contextmenu("create rightclick"); - menu.addbutton( + menu.addButton( I18n.getTranslation("channel.createChannel"), () => { if (thisUser.lookingguild) { thisUser.lookingguild.createchannels(); } }, - null, - () => thisUser.isAdmin(), + {visable: () => thisUser.isAdmin()}, ); - menu.addbutton( + menu.addButton( I18n.getTranslation("channel.createCatagory"), () => { if (thisUser.lookingguild) { thisUser.lookingguild.createcategory(); } }, - null, - () => thisUser.isAdmin(), + {visable: () => thisUser.isAdmin()}, ); menu.bindContextmenu(document.getElementById("channels") as HTMLDivElement); diff --git a/src/webpage/message.ts b/src/webpage/message.ts index 627036a..7395fae 100644 --- a/src/webpage/message.ts +++ b/src/webpage/message.ts @@ -57,50 +57,81 @@ class Message extends SnowFlake { Message.setupcmenu(); } static setupcmenu() { - Message.contextmenu.addbutton( - () => I18n.getTranslation("copyrawtext"), - function (this: Message) { - navigator.clipboard.writeText(this.content.rawString); - }, - ); - Message.contextmenu.addbutton( + Message.contextmenu.addButton( () => I18n.getTranslation("reply"), function (this: Message) { this.channel.setReplying(this); }, - ); - Message.contextmenu.addbutton( - () => I18n.getTranslation("copymessageid"), - function (this: Message) { - navigator.clipboard.writeText(this.id); + { + icon: { + css: "svg-reply", + }, }, ); - Message.contextmenu.addsubmenu( + + Message.contextmenu.addButton( + () => I18n.getTranslation("message.edit"), + function (this: Message) { + this.setEdit(); + }, + { + visable: function () { + return this.author.id === this.localuser.user.id; + }, + + icon: { + css: "svg-edit", + }, + }, + ); + + Message.contextmenu.addButton( () => I18n.getTranslation("message.reactionAdd"), function (this: Message, _, e: MouseEvent) { Emoji.emojiPicker(e.x, e.y, this.localuser).then((_) => { this.reactionToggle(_); }); }, + { + icon: { + css: "svg-emoji", + }, + }, ); - Message.contextmenu.addbutton( - () => I18n.getTranslation("message.edit"), + + Message.contextmenu.addSeperator(); + Message.contextmenu.addButton( + () => I18n.getTranslation("copyrawtext"), function (this: Message) { - this.setEdit(); + navigator.clipboard.writeText(this.content.rawString); }, - null, - function () { - return this.author.id === this.localuser.user.id; + { + icon: { + css: "svg-copy", + }, }, ); - Message.contextmenu.addbutton( + Message.contextmenu.addButton( + () => I18n.getTranslation("copymessageid"), + function (this: Message) { + navigator.clipboard.writeText(this.id); + }, + ); + + Message.contextmenu.addSeperator(); + Message.contextmenu.addButton( () => I18n.getTranslation("message.delete"), function (this: Message) { this.confirmDelete(); }, - null, - function () { - return this.canDelete(); + { + visable: function () { + return this.canDelete(); + }, + icon: { + css: "svg-delete", + }, + color: "red", }, ); } diff --git a/src/webpage/role.ts b/src/webpage/role.ts index 6b9d98b..e4e3242 100644 --- a/src/webpage/role.ts +++ b/src/webpage/role.ts @@ -236,7 +236,7 @@ class RoleList extends Buttons { static guildrolemenu = this.GuildRoleMenu(); private static ChannelRoleMenu() { const menu = new Contextmenu("role settings"); - menu.addbutton( + menu.addButton( () => I18n.getTranslation("role.remove"), function (role) { if (!this.channel) return; @@ -246,13 +246,12 @@ class RoleList extends Buttons { headers: this.headers, }); }, - null, ); return menu; } private static GuildRoleMenu() { const menu = new Contextmenu("role settings"); - menu.addbutton( + menu.addButton( () => I18n.getTranslation("role.delete"), function (role) { if (!confirm(I18n.getTranslation("role.confirmDelete"))) return; @@ -262,7 +261,6 @@ class RoleList extends Buttons { headers: this.headers, }); }, - null, ); return menu; } diff --git a/src/webpage/style.css b/src/webpage/style.css index 05a1b03..5280610 100644 --- a/src/webpage/style.css +++ b/src/webpage/style.css @@ -318,6 +318,7 @@ textarea { width: 100%; background: var(--primary-text-soft); mask-repeat: no-repeat; + aspect-ratio: 1/1; } .selectarrow { position: absolute; @@ -1534,8 +1535,13 @@ img.bigembedimg { background: var(--secondary-bg); border-radius: 4px; box-shadow: 0 0 8px var(--shadow); + hr{ + width:90%; + height: 1px; + } } .contextbutton { + position: relative; width: 180px; padding: 8px; background: transparent; @@ -1544,6 +1550,15 @@ img.bigembedimg { color: var(--secondary-text); border: none; transition: none; + display: flex; + flex-direction: row; + .svgicon { + height: 0.2in; + width: 0.2in; + position: absolute; + right: 6px; + top: 6px; + } } .contextbutton:enabled:hover { background: var(--secondary-hover); diff --git a/src/webpage/themes.css b/src/webpage/themes.css index be06731..ae99dac 100644 --- a/src/webpage/themes.css +++ b/src/webpage/themes.css @@ -4,6 +4,7 @@ --red: #ff5555; --yellow: #ffc159; --green: #1c907b; + --blue: #779bff; } /* Themes. See themes.txt */ diff --git a/src/webpage/user.ts b/src/webpage/user.ts index a786663..19edd78 100644 --- a/src/webpage/user.ts +++ b/src/webpage/user.ts @@ -164,111 +164,118 @@ class User extends SnowFlake { this.relationshipType = type; } static setUpContextMenu(): void { - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("user.copyId"), function (this: User) { navigator.clipboard.writeText(this.id); }, ); - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("user.message"), function (this: User) { this.opendm(); }, ); - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("user.block"), function (this: User) { this.block(); }, - null, - function () { - return this.relationshipType !== 2 && this.id !== this.localuser.user.id; + { + visable: function () { + return this.relationshipType !== 2 && this.id !== this.localuser.user.id; + }, }, ); - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("user.unblock"), function (this: User) { this.unblock(); }, - null, - function () { - return this.relationshipType === 2 && this.id !== this.localuser.user.id; + { + visable: function () { + return this.relationshipType === 2 && this.id !== this.localuser.user.id; + }, }, ); - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("user.friendReq"), function (this: User) { this.changeRelationship(1); }, - null, - function () { - return ( - (this.relationshipType === 0 || this.relationshipType === 3) && - this.id !== this.localuser.user.id - ); + { + visable: function () { + return ( + (this.relationshipType === 0 || this.relationshipType === 3) && + this.id !== this.localuser.user.id + ); + }, }, ); - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("friends.removeFriend"), function (this: User) { this.changeRelationship(0); }, - null, - function () { - return this.relationshipType === 1 && this.id !== this.localuser.user.id; + { + visable: function () { + return this.relationshipType === 1 && this.id !== this.localuser.user.id; + }, }, ); - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("user.kick"), function (this: User, member: Member | undefined) { member?.kick(); }, - null, - function (member) { - if (!member) return false; - const us = member.guild.member; - if (member.id === us.id) { - return false; - } - if (member.id === member.guild.properties.owner_id) { - return false; - } - return us.hasPermission("KICK_MEMBERS") && this.id !== this.localuser.user.id; + { + visable: function (member) { + if (!member) return false; + const us = member.guild.member; + if (member.id === us.id) { + return false; + } + if (member.id === member.guild.properties.owner_id) { + return false; + } + return us.hasPermission("KICK_MEMBERS") && this.id !== this.localuser.user.id; + }, }, ); - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("user.editServerProfile"), function (this: User, member: Member | undefined) { if (!member) return; member.showEditProfile(); }, - null, - function (member) { - return member?.id === this.localuser.user.id; + { + visable: function (member) { + return member?.id === this.localuser.user.id; + }, }, ); - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("user.ban"), function (this: User, member: Member | undefined) { member?.ban(); }, - null, - function (member) { - if (!member) return false; - const us = member.guild.member; - if (member.id === us.id) { - return false; - } - if (member.id === member.guild.properties.owner_id) { - return false; - } - return us.hasPermission("BAN_MEMBERS") && this.id !== this.localuser.user.id; + { + visable: function (member) { + if (!member) return false; + const us = member.guild.member; + if (member.id === us.id) { + return false; + } + if (member.id === member.guild.properties.owner_id) { + return false; + } + return us.hasPermission("BAN_MEMBERS") && this.id !== this.localuser.user.id; + }, }, ); - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("user.addRole"), async function (this: User, member: Member | undefined, e) { if (member) { @@ -286,15 +293,16 @@ class User extends SnowFlake { member.addRole(result); } }, - null, - (member) => { - if (!member) return false; - const us = member.guild.member; - console.log(us.hasPermission("MANAGE_ROLES")); - return us.hasPermission("MANAGE_ROLES") || false; + { + visable: (member) => { + if (!member) return false; + const us = member.guild.member; + console.log(us.hasPermission("MANAGE_ROLES")); + return us.hasPermission("MANAGE_ROLES") || false; + }, }, ); - this.contextmenu.addbutton( + this.contextmenu.addButton( () => I18n.getTranslation("user.removeRole"), async function (this: User, member: Member | undefined, e) { if (member) { @@ -312,12 +320,13 @@ class User extends SnowFlake { member.removeRole(result); } }, - null, - (member) => { - if (!member) return false; - const us = member.guild.member; - console.log(us.hasPermission("MANAGE_ROLES")); - return us.hasPermission("MANAGE_ROLES") || false; + { + visable: (member) => { + if (!member) return false; + const us = member.guild.member; + console.log(us.hasPermission("MANAGE_ROLES")); + return us.hasPermission("MANAGE_ROLES") || false; + }, }, ); }