Custom context menu
This commit is contained in:
parent
3c38ecdd97
commit
4c5300be12
|
@ -8,7 +8,7 @@
|
|||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import { DOM, BdUI, BdMenu, Modals, Reflection, Toasts, Notifications } from 'ui';
|
||||
import { DOM, BdUI, BdMenu, Modals, Reflection, Toasts, Notifications, BdContextMenu } from 'ui';
|
||||
import BdCss from './styles/index.scss';
|
||||
import { Events, CssEditor, Globals, Settings, Database, Updater, ModuleManager, PluginManager, ThemeManager, ExtModuleManager, Vendor, WebpackModules, Patcher, MonkeyPatch, ReactComponents, ReactHelpers, ReactAutoPatcher, DiscordApi, BdWebApi, Connectivity, Cache } from 'modules';
|
||||
import { ClientLogger as Logger, ClientIPC, Utils } from 'common';
|
||||
|
@ -28,7 +28,7 @@ class BetterDiscord {
|
|||
Logger.log('main', 'BetterDiscord starting');
|
||||
|
||||
this._bd = {
|
||||
DOM, BdUI, BdMenu, Modals, Reflection, Toasts, Notifications,
|
||||
DOM, BdUI, BdMenu, Modals, Reflection, Toasts, Notifications, BdContextMenu,
|
||||
|
||||
Events, CssEditor, Globals, Settings, Database, Updater,
|
||||
ModuleManager, PluginManager, ThemeManager, ExtModuleManager,
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
.bd-cm {
|
||||
background: #18191c;
|
||||
box-shadow: 0 0 1px rgba(0,0,0,.82), 0 1px 4px rgba(0,0,0,.1);
|
||||
border-radius: 5px;
|
||||
position: fixed;
|
||||
width: 170px;
|
||||
z-index: 1005;
|
||||
user-select: none;
|
||||
|
||||
&.bd-cmRenderLeft {
|
||||
.bd-cm {
|
||||
margin-left: -170px;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-cm {
|
||||
left: 170px;
|
||||
max-height: 270px;
|
||||
overflow-y: auto;
|
||||
contain: layout;
|
||||
flex: 1;
|
||||
min-height: 1px;
|
||||
margin-left: 170px;
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background-clip: padding-box;
|
||||
background-color: rgba(32,34,37,.6);
|
||||
border: 2px solid transparent;
|
||||
border-radius: 4px;
|
||||
cursor: move;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
background-clip: padding-box;
|
||||
border-radius: 7px;
|
||||
border: 2px solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-cmGroup:not(:first-child):not(:empty) {
|
||||
&:not(:first-child) {
|
||||
&:not(:empty) {
|
||||
border-top: 1px solid hsla(0,0%, 96.1%, .08);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-cmSub {
|
||||
.bd-materialDesignIcon {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 2px;
|
||||
fill: hsla(0, 0%, 100%, .6);
|
||||
|
||||
svg {
|
||||
height: 20px;
|
||||
transform: rotate(-90deg);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
svg {
|
||||
fill: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bd-cmItem {
|
||||
cursor: default;
|
||||
color: hsla(0,0%,100%,.6);
|
||||
border-radius: 5px;
|
||||
box-sizing: border-box;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
line-height: 16px;
|
||||
margin: 2px 0;
|
||||
overflow: hidden;
|
||||
padding: 6px 9px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
|
||||
.bd-cmHint {
|
||||
opacity: .8;
|
||||
color: hsla(0,0%,100%,.6);
|
||||
}
|
||||
|
||||
span {
|
||||
max-width: 140px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
img {
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: #040405;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.bd-cmToggle {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 5px 9px;
|
||||
|
||||
.bd-cmLabel {
|
||||
overflow: hidden;
|
||||
padding-right: 4px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.bd-cmCheckbox {
|
||||
cursor: pointer;
|
||||
margin-left: 3px;
|
||||
pointer-events: none;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
|
||||
.bd-cmCheckboxInner {
|
||||
flex-shrink: 0;
|
||||
height: 18px;
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
width: 18px;
|
||||
|
||||
&:before,
|
||||
&:after {
|
||||
content: '';
|
||||
}
|
||||
|
||||
input {
|
||||
display: none;
|
||||
|
||||
&:checked {
|
||||
+ span {
|
||||
background-color: #7289da;
|
||||
border-color: #7289da;
|
||||
|
||||
&:after {
|
||||
border-color: #fff;
|
||||
border-style: solid;
|
||||
border-width: 0 2px 2px 0;
|
||||
content: "";
|
||||
display: table;
|
||||
height: 10px;
|
||||
left: 4px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
transform: rotate(45deg);
|
||||
width: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
border: 2px solid hsla(0,0%,100%,.2);
|
||||
border-radius: 2px;
|
||||
bottom: 0;
|
||||
box-sizing: border-box;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
transition: .24s;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,3 +13,4 @@
|
|||
@import './toasts';
|
||||
@import './badges';
|
||||
@import './notifications';
|
||||
@import './contextmenu';
|
||||
|
|
|
@ -29,3 +29,7 @@
|
|||
.bd-inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.bd-hidden {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { Events, DiscordApi, Settings } from 'modules';
|
|||
import { remote } from 'electron';
|
||||
import DOM from './dom';
|
||||
import Vue from './vue';
|
||||
import { BdSettingsWrapper, BdModals, BdToasts, BdNotifications } from './components';
|
||||
import { BdSettingsWrapper, BdModals, BdToasts, BdNotifications, BdContextMenu } from './components';
|
||||
|
||||
export default class {
|
||||
|
||||
|
@ -53,6 +53,7 @@ export default class {
|
|||
DOM.createElement('div', null, 'bd-modals').appendTo(DOM.bdModals);
|
||||
DOM.createElement('div', null, 'bd-toasts').appendTo(DOM.bdToasts);
|
||||
DOM.createElement('div', null, 'bd-notifications').appendTo(DOM.bdNotifications);
|
||||
DOM.createElement('div', null, 'bd-contextmenu').appendTo(DOM.bdContextMenu);
|
||||
DOM.createElement('bd-tooltips').appendTo(DOM.bdBody);
|
||||
|
||||
this.toasts = new (Vue.extend(BdToasts))({
|
||||
|
@ -71,6 +72,10 @@ export default class {
|
|||
el: '#bd-notifications'
|
||||
});
|
||||
|
||||
this.contextmenu = new (Vue.extend(BdContextMenu))({
|
||||
el: '#bd-contextmenu'
|
||||
});
|
||||
|
||||
return this.vueInstance;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* BetterDiscord Context Menu Component
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-cm" :class="{'bd-cmRenderLeft': renderLeft}" v-if="activeMenu && activeMenu.menu" :style="calculatePosition()">
|
||||
<CMGroup v-for="(group, index) in activeMenu.menu.groups" :items="group.items" :key="index" :closeMenu="() => {activeMenu.menu = null}" :left="left" :top="top"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Imports
|
||||
import { BdContextMenu } from 'ui';
|
||||
import CMGroup from './contextmenu/Group.vue';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
activeMenu: BdContextMenu.activeMenu,
|
||||
visibleSub: -1,
|
||||
left: -1,
|
||||
top: -1,
|
||||
renderLeft: false
|
||||
};
|
||||
},
|
||||
components: { CMGroup },
|
||||
methods: {
|
||||
calculatePosition() {
|
||||
if (!this.activeMenu.menu.groups.length) return {};
|
||||
const height = this.activeMenu.menu.groups.reduce((total, group) => total + group.items.length, 0) * 28;
|
||||
this.top = window.innerHeight - this.mouseY - height < 0 ? this.mouseY - height : this.mouseY;
|
||||
this.left = window.innerWidth - this.mouseX - 170 < 0 ? this.mouseX - 170 : this.mouseX;
|
||||
this.renderLeft = (this.left + 170 * 2) > window.innerWidth;
|
||||
return { top: `${this.top}px`, left: `${this.left}px` };
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
window.addEventListener('contextmenu', e => {
|
||||
this.mouseX = e.clientX;
|
||||
this.mouseY = e.clientY;
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,23 @@
|
|||
/**
|
||||
* BetterDiscord Context Menu Button Component
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-cmItem" :style="{color: item.color || ''}" @click="onClick">
|
||||
<span>{{item.text}}</span>
|
||||
<div class="bd-cmHint" v-if="item.hint">{{item.hint}}</div>
|
||||
<img :src="item.icon" v-else-if="item.icon"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['item', 'onClick']
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* BetterDiscord Context Menu Group Component
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-cmGroup" ref="test">
|
||||
<template v-for="(item, index) in items">
|
||||
<CMButton v-if="!item.type || item.type === 'button'" :item="item" :onClick="() => { item.onClick(); closeMenu(); }" />
|
||||
<CMToggle v-else-if="item.type === 'toggle'" :item="item" :onClick="() => { item.checked = item.onChange(!item.checked) }" />
|
||||
<div v-else-if="item.type === 'sub'" class="bd-cmItem bd-cmSub" @mouseenter="e => subMenuMouseEnter(e, index, item)" @mouseleave="e => subMenuMouseLeave(e, index, item)">
|
||||
{{item.text}}
|
||||
<MiChevronDown />
|
||||
<div ref="test2" class="bd-cm" v-if="index === visibleSub" :style="subStyle">
|
||||
<template v-for="(item, index) in item.items">
|
||||
<CMButton v-if="!item.type || item.type === 'button'" :item="item" :onClick="() => { item.onClick(); closeMenu(); }" />
|
||||
<CMToggle v-else-if="item.type === 'toggle'" :item="item" :onClick="() => { item.checked = item.onChange(!item.checked) }" />
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// Imports
|
||||
import CMButton from './Button.vue';
|
||||
import CMToggle from './Toggle.vue';
|
||||
import { MiChevronDown } from '../common';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
visibleSub: -1,
|
||||
subStyle: {}
|
||||
}
|
||||
},
|
||||
props: ['items', 'closeMenu', 'left', 'top'],
|
||||
components: { CMButton, CMToggle, MiChevronDown },
|
||||
methods: {
|
||||
subMenuMouseEnter(e, index, sub) {
|
||||
const subHeight = sub.items.length > 9 ? 270 : sub.items.length * e.target.offsetHeight;
|
||||
const top = this.top + subHeight + e.target.offsetTop > window.innerHeight ?
|
||||
this.top - subHeight + e.target.offsetTop + e.target.offsetHeight :
|
||||
this.top + e.target.offsetTop;
|
||||
this.subStyle = { top: `${top}px`, left: `${this.left}px` };
|
||||
this.visibleSub = index;
|
||||
},
|
||||
subMenuMouseLeave(e, index, sub) {
|
||||
this.visibleSub = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* BetterDiscord Context Menu Toggle Component
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
<template>
|
||||
<div class="bd-cmItem bd-cmToggle" @click="onClick">
|
||||
<div class="bd-cmLabel">{{item.text}}</div>
|
||||
<div class="bd-cmCheckbox">
|
||||
<div class="bd-cmCheckboxInner">
|
||||
<input type="checkbox" :checked="item.checked || item.enabled"/>
|
||||
<span></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['item', 'onClick']
|
||||
}
|
||||
</script>
|
|
@ -3,3 +3,4 @@ export { default as BdSettings } from './BdSettings.vue';
|
|||
export { default as BdModals } from './BdModals.vue';
|
||||
export { default as BdToasts } from './BdToasts.vue';
|
||||
export { default as BdNotifications } from './BdNotifications.vue';
|
||||
export { default as BdContextMenu } from './BdContextMenu.vue';
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* BetterDiscord Context Menus
|
||||
* Copyright (c) 2015-present Jiiks/JsSucks - https://github.com/Jiiks / https://github.com/JsSucks
|
||||
* All rights reserved.
|
||||
* https://betterdiscord.net
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export class BdContextMenu {
|
||||
|
||||
/**
|
||||
* Show a context menu
|
||||
* @param {Object[]} grops Groups of items to show in context menu
|
||||
*/
|
||||
static show(groups) {
|
||||
this.activeMenu.menu = { groups };
|
||||
}
|
||||
|
||||
static get activeMenu() {
|
||||
return this._activeMenu || (this._activeMenu = { menu: null });
|
||||
}
|
||||
|
||||
}
|
|
@ -186,6 +186,7 @@ export default class DOM {
|
|||
static get bdModals() { return this.getElement('bd-modals') || this.createElement('bd-modals').appendTo(this.bdBody) }
|
||||
static get bdToasts() { return this.getElement('bd-toasts') || this.createElement('bd-toasts').appendTo(this.bdBody) }
|
||||
static get bdNotifications() { return this.getElement('bd-notifications') || this.createElement('bd-notifications').appendTo(this.bdBody) }
|
||||
static get bdContextMenu() { return this.getElement('bd-contextmenu') || this.createElement('bd-contextmenu').appendTo(this.bdBody) }
|
||||
|
||||
static getElement(e) {
|
||||
if (e instanceof BdNode) return e.element;
|
||||
|
|
|
@ -4,6 +4,7 @@ export { default as BdMenu, BdMenuItems } from './bdmenu';
|
|||
export { default as Modals } from './modals';
|
||||
export { default as Toasts } from './toasts';
|
||||
export { default as Notifications } from './notifications';
|
||||
export * from './contextmenus';
|
||||
|
||||
export { default as VueInjector } from './vueinjector';
|
||||
export { default as Reflection } from './reflection';
|
||||
|
|
Loading…
Reference in New Issue