Add context menu for users and channels
This commit is contained in:
parent
54b62b9964
commit
af5a134081
127
app/index.html
127
app/index.html
|
@ -179,6 +179,129 @@
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<!-- /ko -->
|
<!-- /ko -->
|
||||||
|
<!-- ko with: userContextMenu -->
|
||||||
|
<ul class="context-menu" data-bind="if: target,
|
||||||
|
style: { left: posX() + 'px',
|
||||||
|
top: posY() + 'px' }">
|
||||||
|
|
||||||
|
<!-- ko with: target -->
|
||||||
|
<li data-bind="css: { disabled: !canChangeMute() }">
|
||||||
|
Mute
|
||||||
|
</li>
|
||||||
|
<li data-bind="css: { disabled: !canChangeDeafen() }">
|
||||||
|
Deafen
|
||||||
|
</li>
|
||||||
|
<li data-bind="css: { disabled: !canChangePrioritySpeaker() }">
|
||||||
|
Priority Speaker
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-bind="css: { disabled: !canLocalMute() }">
|
||||||
|
Local Mute
|
||||||
|
</li>
|
||||||
|
<li data-bind="css: { disabled: !canIgnoreMessages() }">
|
||||||
|
Ignore Messages
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-bind="css: { disabled: !canChangeComment() }, visible: comment">
|
||||||
|
View Comment
|
||||||
|
</li>
|
||||||
|
<!-- ko if: $data === $root.thisUser() -->
|
||||||
|
<li data-bind="css: { disabled: !canChangeComment() }, visible: true">
|
||||||
|
Change Comment
|
||||||
|
</li>
|
||||||
|
<!-- /ko -->
|
||||||
|
<!-- ko if: $data !== $root.thisUser() -->
|
||||||
|
<li data-bind="css: { disabled: !canChangeComment() }, visible: comment">
|
||||||
|
Reset Comment
|
||||||
|
</li>
|
||||||
|
<!-- /ko -->
|
||||||
|
|
||||||
|
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: texture">
|
||||||
|
View Avatar
|
||||||
|
</li>
|
||||||
|
<!-- ko if: $data === $root.thisUser() -->
|
||||||
|
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: true">
|
||||||
|
Change Avatar
|
||||||
|
</li>
|
||||||
|
<!-- /ko -->
|
||||||
|
<li data-bind="css: { disabled: !canChangeAvatar() }, visible: texture">
|
||||||
|
Reset Avatar
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-bind="css: { disabled: true }, visible: true">
|
||||||
|
Send Message
|
||||||
|
</li>
|
||||||
|
<li data-bind="css: { disabled: true }, visible: true">
|
||||||
|
Information
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-bind="visible: $data === $root.thisUser(),
|
||||||
|
css: { checked: selfMute },
|
||||||
|
click: toggleMute">
|
||||||
|
Self Mute
|
||||||
|
</li>
|
||||||
|
<li data-bind="visible: $data === $root.thisUser(),
|
||||||
|
css: { checked: selfDeaf },
|
||||||
|
click: toggleDeaf">
|
||||||
|
Self Deafen
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- ko if: $data !== $root.thisUser() -->
|
||||||
|
<li data-bind="css: { disabled: true }, visible: true">
|
||||||
|
Add Friend
|
||||||
|
</li>
|
||||||
|
<li data-bind="css: { disabled: true }, visible: false">
|
||||||
|
Remove Friend
|
||||||
|
</li>
|
||||||
|
<!-- /ko -->
|
||||||
|
|
||||||
|
<!-- /ko -->
|
||||||
|
</ul>
|
||||||
|
<!-- /ko -->
|
||||||
|
<!-- ko with: channelContextMenu -->
|
||||||
|
<ul class="context-menu" data-bind="if: target,
|
||||||
|
style: { left: posX() + 'px',
|
||||||
|
top: posY() + 'px' }">
|
||||||
|
|
||||||
|
<!-- ko with: target -->
|
||||||
|
<li data-bind="visible: users.indexOf($root.thisUser()) === -1,
|
||||||
|
css: { disabled: !canJoin() },
|
||||||
|
click: $root.requestMove.bind($root, $root.thisUser())">
|
||||||
|
Join Channel
|
||||||
|
</li>
|
||||||
|
<li data-bind="css: { disabled: !canAdd() }">
|
||||||
|
Add
|
||||||
|
</li>
|
||||||
|
<li data-bind="css: { disabled: !canEdit() }">
|
||||||
|
Edit
|
||||||
|
</li>
|
||||||
|
<li data-bind="css: { disabled: !canRemove() }">
|
||||||
|
Remove
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-bind="css: { disabled: !canLink() }">
|
||||||
|
Link
|
||||||
|
</li>
|
||||||
|
<li data-bind="css: { disabled: !canUnlink() }">
|
||||||
|
Unlink
|
||||||
|
</li>
|
||||||
|
<li data-bind="css: { disabled: !canUnlink() }">
|
||||||
|
Unlink All
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li data-bind="css: { disabled: true }">
|
||||||
|
Copy Mumble URL
|
||||||
|
</li>
|
||||||
|
<li data-bind="css: { disabled: true }">
|
||||||
|
Copy Mumble-Web URL
|
||||||
|
</li>
|
||||||
|
<li data-bind="css: { disabled: !canSendMessage() }">
|
||||||
|
Send Message
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<!-- /ko -->
|
||||||
|
</ul>
|
||||||
|
<!-- /ko -->
|
||||||
<script type="text/html" id="user-tag">
|
<script type="text/html" id="user-tag">
|
||||||
<span class="user-tag" data-bind="text: name"></span>
|
<span class="user-tag" data-bind="text: name"></span>
|
||||||
</script>
|
</script>
|
||||||
|
@ -265,6 +388,7 @@
|
||||||
<div class="channel" data-bind="
|
<div class="channel" data-bind="
|
||||||
click: $root.select,
|
click: $root.select,
|
||||||
event: {
|
event: {
|
||||||
|
contextmenu: openContextMenu,
|
||||||
dblclick: $root.requestMove.bind($root, $root.thisUser())
|
dblclick: $root.requestMove.bind($root, $root.thisUser())
|
||||||
},
|
},
|
||||||
css: {
|
css: {
|
||||||
|
@ -292,6 +416,9 @@
|
||||||
<div class="user-tree"></div>
|
<div class="user-tree"></div>
|
||||||
<div class="user" data-bind="
|
<div class="user" data-bind="
|
||||||
click: $root.select,
|
click: $root.select,
|
||||||
|
event: {
|
||||||
|
contextmenu: openContextMenu
|
||||||
|
},
|
||||||
css: {
|
css: {
|
||||||
thisClient: $root.thisUser() === $data,
|
thisClient: $root.thisUser() === $data,
|
||||||
selected: $root.selected() === $data
|
selected: $root.selected() === $data
|
||||||
|
|
90
app/index.js
90
app/index.js
|
@ -19,8 +19,34 @@ function sanitize (html) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openContextMenu (event, contextMenu, target) {
|
||||||
|
contextMenu.posX(event.clientX)
|
||||||
|
contextMenu.posY(event.clientY)
|
||||||
|
contextMenu.target(target)
|
||||||
|
|
||||||
|
const closeListener = (event) => {
|
||||||
|
// Always close, no matter where they clicked
|
||||||
|
setTimeout(() => { // delay to allow click to be actually processed
|
||||||
|
contextMenu.target(null)
|
||||||
|
unregister()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const unregister = () => document.removeEventListener('click', closeListener)
|
||||||
|
document.addEventListener('click', closeListener)
|
||||||
|
|
||||||
|
event.stopPropagation()
|
||||||
|
event.preventDefault()
|
||||||
|
}
|
||||||
|
|
||||||
// GUI
|
// GUI
|
||||||
|
|
||||||
|
function ContextMenu () {
|
||||||
|
var self = this
|
||||||
|
self.posX = ko.observable()
|
||||||
|
self.posY = ko.observable()
|
||||||
|
self.target = ko.observable()
|
||||||
|
}
|
||||||
|
|
||||||
function ConnectDialog () {
|
function ConnectDialog () {
|
||||||
var self = this
|
var self = this
|
||||||
self.address = ko.observable('')
|
self.address = ko.observable('')
|
||||||
|
@ -152,6 +178,8 @@ class GlobalBindings {
|
||||||
constructor () {
|
constructor () {
|
||||||
this.settings = new Settings()
|
this.settings = new Settings()
|
||||||
this.client = null
|
this.client = null
|
||||||
|
this.userContextMenu = new ContextMenu()
|
||||||
|
this.channelContextMenu = new ContextMenu()
|
||||||
this.connectDialog = new ConnectDialog()
|
this.connectDialog = new ConnectDialog()
|
||||||
this.connectErrorDialog = new ConnectErrorDialog(this.connectDialog)
|
this.connectErrorDialog = new ConnectErrorDialog(this.connectDialog)
|
||||||
this.connectionInfo = new ConnectionInfo()
|
this.connectionInfo = new ConnectionInfo()
|
||||||
|
@ -342,6 +370,46 @@ class GlobalBindings {
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
ui.openContextMenu = (_, event) => openContextMenu(event, this.userContextMenu, ui)
|
||||||
|
ui.canChangeMute = () => {
|
||||||
|
return false // TODO check for perms and implement
|
||||||
|
}
|
||||||
|
ui.canChangeDeafen = () => {
|
||||||
|
return false // TODO check for perms and implement
|
||||||
|
}
|
||||||
|
ui.canChangePrioritySpeaker = () => {
|
||||||
|
return false // TODO check for perms and implement
|
||||||
|
}
|
||||||
|
ui.canLocalMute = () => {
|
||||||
|
return false // TODO implement local mute
|
||||||
|
// return this.thisUser() !== ui
|
||||||
|
}
|
||||||
|
ui.canIgnoreMessages = () => {
|
||||||
|
return false // TODO implement ignore messages
|
||||||
|
// return this.thisUser() !== ui
|
||||||
|
}
|
||||||
|
ui.canChangeComment = () => {
|
||||||
|
return false // TODO implement changing of comments
|
||||||
|
// return this.thisUser() === ui // TODO check for perms
|
||||||
|
}
|
||||||
|
ui.canChangeAvatar = () => {
|
||||||
|
return false // TODO implement changing of avatar
|
||||||
|
// return this.thisUser() === ui // TODO check for perms
|
||||||
|
}
|
||||||
|
ui.toggleMute = () => {
|
||||||
|
if (ui.selfMute()) {
|
||||||
|
this.requestUnmute(ui)
|
||||||
|
} else {
|
||||||
|
this.requestMute(ui)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui.toggleDeaf = () => {
|
||||||
|
if (ui.selfDeaf()) {
|
||||||
|
this.requestUndeaf(ui)
|
||||||
|
} else {
|
||||||
|
this.requestDeaf(ui)
|
||||||
|
}
|
||||||
|
}
|
||||||
Object.entries(simpleProperties).forEach(key => {
|
Object.entries(simpleProperties).forEach(key => {
|
||||||
ui[key[1]] = ko.observable(user[key[0]])
|
ui[key[1]] = ko.observable(user[key[0]])
|
||||||
})
|
})
|
||||||
|
@ -421,6 +489,28 @@ class GlobalBindings {
|
||||||
users: ko.observableArray(),
|
users: ko.observableArray(),
|
||||||
linked: ko.observable(false)
|
linked: ko.observable(false)
|
||||||
}
|
}
|
||||||
|
ui.openContextMenu = (_, event) => openContextMenu(event, this.channelContextMenu, ui)
|
||||||
|
ui.canJoin = () => {
|
||||||
|
return true // TODO check for perms
|
||||||
|
}
|
||||||
|
ui.canAdd = () => {
|
||||||
|
return false // TODO check for perms and implement
|
||||||
|
}
|
||||||
|
ui.canEdit = () => {
|
||||||
|
return false // TODO check for perms and implement
|
||||||
|
}
|
||||||
|
ui.canRemove = () => {
|
||||||
|
return false // TODO check for perms and implement
|
||||||
|
}
|
||||||
|
ui.canLink = () => {
|
||||||
|
return false // TODO check for perms and implement
|
||||||
|
}
|
||||||
|
ui.canUnlink = () => {
|
||||||
|
return false // TODO check for perms and implement
|
||||||
|
}
|
||||||
|
ui.canSendMessage = () => {
|
||||||
|
return false // TODO check for perms and implement
|
||||||
|
}
|
||||||
Object.entries(simpleProperties).forEach(key => {
|
Object.entries(simpleProperties).forEach(key => {
|
||||||
ui[key[1]] = ko.observable(channel[key[0]])
|
ui[key[1]] = ko.observable(channel[key[0]])
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,6 +6,7 @@ $bg-color: #eee !default
|
||||||
$white: #fff !default
|
$white: #fff !default
|
||||||
|
|
||||||
$font-color: $black !default
|
$font-color: $black !default
|
||||||
|
$font-disabled-color: $gray !default
|
||||||
$panel-bg-color: $white !default
|
$panel-bg-color: $white !default
|
||||||
$panel-border-color: $lightgray !default
|
$panel-border-color: $lightgray !default
|
||||||
$channel-tree-color: $lightgray !default
|
$channel-tree-color: $lightgray !default
|
||||||
|
@ -38,6 +39,9 @@ $dialog-button-color: $dialog-color !default
|
||||||
$dialog-input-border-color: $darkgray !default
|
$dialog-input-border-color: $darkgray !default
|
||||||
$dialog-input-bg-color: $white !default
|
$dialog-input-bg-color: $white !default
|
||||||
$dialog-input-color: $dialog-color !default
|
$dialog-input-color: $dialog-color !default
|
||||||
|
$context-menu-bg-color: $bg-color !default
|
||||||
|
$context-menu-border-color: $panel-border-color !default
|
||||||
|
$context-menu-hover-bg-color: $toolbar-hover-bg-color !default
|
||||||
|
|
||||||
$tooltip-bg-color: $panel-bg-color !default
|
$tooltip-bg-color: $panel-bg-color !default
|
||||||
$channels-bg-color: $panel-bg-color !default
|
$channels-bg-color: $panel-bg-color !default
|
||||||
|
@ -219,6 +223,36 @@ html, body {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
z-index: 100;
|
z-index: 100;
|
||||||
}
|
}
|
||||||
|
.context-menu {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 50;
|
||||||
|
background: $context-menu-bg-color;
|
||||||
|
border: 1px solid $context-menu-border-color;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
list-style: none;
|
||||||
|
|
||||||
|
& > li {
|
||||||
|
padding: 5px 20px;
|
||||||
|
padding-left: 10px;
|
||||||
|
&::before {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px;
|
||||||
|
padding-right: 5px;
|
||||||
|
content: '';
|
||||||
|
}
|
||||||
|
&.checked::before {
|
||||||
|
content: '✓';
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background: $context-menu-hover-bg-color;
|
||||||
|
}
|
||||||
|
&.disabled {
|
||||||
|
background: $context-menu-bg-color;
|
||||||
|
color: $font-disabled-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
Loading…
Reference in a new issue