2020-08-03 17:26:16 +00:00
|
|
|
<template>
|
2020-08-03 20:12:53 +00:00
|
|
|
<div class="context-menu" v-show="menuOpen" ref="contextMenu" :style="{ top: yPos, left: xPos }">
|
|
|
|
<button
|
|
|
|
class="menu-option"
|
2020-08-11 20:08:56 +00:00
|
|
|
v-for="option of sortedOptions"
|
2020-08-03 20:12:53 +00:00
|
|
|
:key="option.label"
|
|
|
|
v-show="option.show"
|
|
|
|
@click.prevent="option.action"
|
|
|
|
>
|
2020-08-03 17:26:16 +00:00
|
|
|
<span class="menu-option__text">{{ option.label }}</span>
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script>
|
2020-08-11 19:12:47 +00:00
|
|
|
import Downloads from '@/utils/downloads'
|
|
|
|
import downloadQualities from '@js/qualities'
|
2020-08-19 15:35:25 +00:00
|
|
|
import { generatePath, copyToClipboard } from '@/utils/utils'
|
2020-08-11 19:12:47 +00:00
|
|
|
|
2020-08-03 17:26:16 +00:00
|
|
|
export default {
|
2020-08-11 19:12:47 +00:00
|
|
|
data() {
|
|
|
|
return {
|
|
|
|
menuOpen: false,
|
|
|
|
xPos: 0,
|
|
|
|
yPos: 0,
|
|
|
|
deezerHref: '',
|
2020-08-11 20:08:56 +00:00
|
|
|
generalHref: '',
|
|
|
|
imgSrc: ''
|
2020-08-11 19:12:47 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
options() {
|
|
|
|
// In the action property:
|
|
|
|
// Use arrow functions to keep the Vue instance in 'this'
|
|
|
|
// Use normal functions to keep the object instance in 'this'
|
2020-08-11 20:08:56 +00:00
|
|
|
const options = {
|
|
|
|
cut: {
|
2020-08-11 19:12:47 +00:00
|
|
|
label: this.$t('globals.cut'),
|
2020-08-20 17:41:46 +00:00
|
|
|
show: false,
|
2020-08-11 20:08:56 +00:00
|
|
|
position: 1,
|
2020-08-11 19:12:47 +00:00
|
|
|
action: () => {
|
|
|
|
document.execCommand('Cut')
|
|
|
|
}
|
|
|
|
},
|
2020-08-11 20:08:56 +00:00
|
|
|
copy: {
|
2020-08-11 19:12:47 +00:00
|
|
|
label: this.$t('globals.copy'),
|
2020-08-20 17:41:46 +00:00
|
|
|
show: false,
|
2020-08-11 20:08:56 +00:00
|
|
|
position: 2,
|
2020-08-11 19:12:47 +00:00
|
|
|
action: () => {
|
|
|
|
document.execCommand('Copy')
|
|
|
|
}
|
|
|
|
},
|
2020-08-11 20:08:56 +00:00
|
|
|
copyLink: {
|
2020-08-11 19:12:47 +00:00
|
|
|
label: this.$t('globals.copyLink'),
|
|
|
|
show: false,
|
2020-08-11 20:08:56 +00:00
|
|
|
position: 3,
|
2020-08-11 19:12:47 +00:00
|
|
|
action: () => {
|
2020-08-19 15:35:25 +00:00
|
|
|
copyToClipboard(this.generalHref)
|
2020-08-11 19:12:47 +00:00
|
|
|
}
|
|
|
|
},
|
2020-08-11 20:08:56 +00:00
|
|
|
copyImageLink: {
|
|
|
|
label: this.$t('globals.copyImageLink'),
|
|
|
|
show: false,
|
|
|
|
position: 4,
|
|
|
|
action: () => {
|
2020-08-19 15:35:25 +00:00
|
|
|
copyToClipboard(this.imgSrc)
|
2020-08-11 20:08:56 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
copyDeezerLink: {
|
2020-08-11 19:12:47 +00:00
|
|
|
label: this.$t('globals.copyDeezerLink'),
|
|
|
|
show: false,
|
2020-08-11 20:08:56 +00:00
|
|
|
position: 5,
|
2020-08-11 19:12:47 +00:00
|
|
|
action: () => {
|
2020-08-19 15:35:25 +00:00
|
|
|
copyToClipboard(this.deezerHref)
|
2020-08-11 19:12:47 +00:00
|
|
|
}
|
|
|
|
},
|
2020-08-11 20:08:56 +00:00
|
|
|
paste: {
|
2020-08-11 19:12:47 +00:00
|
|
|
label: this.$t('globals.paste'),
|
2020-08-20 17:41:46 +00:00
|
|
|
show: false,
|
2020-08-11 20:08:56 +00:00
|
|
|
position: 6,
|
2020-08-11 19:12:47 +00:00
|
|
|
action: () => {
|
2020-08-20 17:41:46 +00:00
|
|
|
// Paste does not always work
|
|
|
|
if (clipboard in navigator) {
|
|
|
|
navigator.clipboard.readText().then(text => {
|
|
|
|
document.execCommand('insertText', undefined, text)
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
document.execCommand('paste')
|
|
|
|
}
|
2020-08-11 19:12:47 +00:00
|
|
|
}
|
|
|
|
}
|
2020-08-11 20:08:56 +00:00
|
|
|
}
|
2020-08-03 20:12:53 +00:00
|
|
|
|
2020-08-11 20:08:56 +00:00
|
|
|
let nextValuePosition = Object.values(options).length + 1
|
|
|
|
|
|
|
|
downloadQualities.forEach((quality, index) => {
|
|
|
|
options[quality.objName] = {
|
2020-08-11 19:12:47 +00:00
|
|
|
label: `${this.$t('globals.download', [quality.label])}`,
|
|
|
|
show: false,
|
2020-08-11 20:08:56 +00:00
|
|
|
position: nextValuePosition + index,
|
2020-08-11 19:12:47 +00:00
|
|
|
action: this.tryToDownloadTrack.bind(null, quality.value)
|
2020-08-11 20:08:56 +00:00
|
|
|
}
|
2020-08-03 17:26:16 +00:00
|
|
|
})
|
2020-08-11 19:12:47 +00:00
|
|
|
|
|
|
|
return options
|
|
|
|
},
|
2020-08-20 17:41:46 +00:00
|
|
|
/**
|
|
|
|
* This computed property is used for rendering the options in the wanted order
|
|
|
|
* while keeping the options computed property an Object to make the properties
|
|
|
|
* accessible via property name (es this.options.copyLink)
|
|
|
|
*
|
|
|
|
* @return {object[]} Options in order according to position property
|
|
|
|
*/
|
2020-08-11 20:08:56 +00:00
|
|
|
sortedOptions() {
|
|
|
|
return Object.values(this.options).sort((first, second) => {
|
|
|
|
return first.position < second.position ? -1 : 1
|
|
|
|
})
|
2020-08-11 19:12:47 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
mounted() {
|
|
|
|
document.body.addEventListener('contextmenu', this.showMenu)
|
|
|
|
document.body.addEventListener('click', this.hideMenu)
|
2020-08-03 20:12:53 +00:00
|
|
|
},
|
|
|
|
methods: {
|
|
|
|
showMenu(contextMenuEvent) {
|
2020-08-20 17:41:46 +00:00
|
|
|
// contextMenuEvent.preventDefault()
|
2020-08-12 09:55:02 +00:00
|
|
|
const { pageX, pageY, target: elementClicked } = contextMenuEvent
|
|
|
|
const path = generatePath(elementClicked)
|
2020-08-11 20:08:56 +00:00
|
|
|
let deezerLink = null
|
2020-08-03 20:12:53 +00:00
|
|
|
|
2020-08-20 17:41:46 +00:00
|
|
|
// Searching for the first element with a data-link attribute
|
|
|
|
// let deezerLink = this.searchForDataLink(...)
|
2020-08-03 20:12:53 +00:00
|
|
|
for (let i = 0; i < path.length; i++) {
|
|
|
|
if (path[i] == document) break
|
|
|
|
|
|
|
|
if (path[i].matches('[data-link]')) {
|
2020-08-11 20:08:56 +00:00
|
|
|
deezerLink = path[i].dataset.link
|
2020-08-03 20:12:53 +00:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-20 17:41:46 +00:00
|
|
|
const isLink = elementClicked.matches('a')
|
|
|
|
const isImage = elementClicked.matches('img')
|
|
|
|
const hasDeezerLink = !!deezerLink
|
|
|
|
|
|
|
|
if (!isLink && !isImage && !hasDeezerLink) return
|
|
|
|
|
|
|
|
contextMenuEvent.preventDefault()
|
|
|
|
this.menuOpen = true
|
|
|
|
this.positionMenu(pageX, pageY)
|
|
|
|
|
|
|
|
if (isLink) {
|
|
|
|
// Show 'Copy Link' option
|
|
|
|
this.generalHref = elementClicked.href
|
|
|
|
this.options.copyLink.show = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isImage) {
|
|
|
|
// Show 'Copy Image Link' option
|
|
|
|
this.imgSrc = elementClicked.src
|
|
|
|
this.options.copyImageLink.show = true
|
|
|
|
}
|
|
|
|
|
2020-08-11 20:08:56 +00:00
|
|
|
if (deezerLink) {
|
2020-08-20 17:41:46 +00:00
|
|
|
// Show 'Copy Deezer Link' option
|
2020-08-11 20:08:56 +00:00
|
|
|
this.deezerHref = deezerLink
|
|
|
|
this.showDeezerOptions()
|
2020-08-03 20:12:53 +00:00
|
|
|
}
|
|
|
|
},
|
2020-08-11 19:12:47 +00:00
|
|
|
hideMenu() {
|
|
|
|
if (!this.menuOpen) return
|
|
|
|
|
|
|
|
// Finish all operations before closing (may be not necessary)
|
2020-08-11 20:08:56 +00:00
|
|
|
this.$nextTick()
|
|
|
|
.then(() => {
|
|
|
|
this.menuOpen = false
|
2020-08-11 19:12:47 +00:00
|
|
|
|
2020-08-11 20:08:56 +00:00
|
|
|
this.options.copyLink.show = false
|
|
|
|
this.options.copyDeezerLink.show = false
|
|
|
|
this.options.copyImageLink.show = false
|
2020-08-11 19:12:47 +00:00
|
|
|
|
2020-08-11 20:08:56 +00:00
|
|
|
downloadQualities.forEach(quality => {
|
|
|
|
this.options[quality.objName].show = false
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.catch(err => {
|
|
|
|
console.error(err)
|
|
|
|
})
|
2020-08-11 19:12:47 +00:00
|
|
|
},
|
2020-08-03 20:12:53 +00:00
|
|
|
positionMenu(newX, newY) {
|
|
|
|
this.xPos = `${newX}px`
|
|
|
|
this.yPos = `${newY}px`
|
2020-08-12 17:03:53 +00:00
|
|
|
|
|
|
|
this.$nextTick().then(() => {
|
|
|
|
const { innerHeight, innerWidth } = window
|
|
|
|
const menuXOffest = newX + this.$refs.contextMenu.getBoundingClientRect().width
|
|
|
|
const menuYOffest = newY + this.$refs.contextMenu.getBoundingClientRect().height
|
|
|
|
|
|
|
|
if (menuXOffest > innerWidth) {
|
|
|
|
const difference = menuXOffest - innerWidth + 15
|
|
|
|
this.xPos = `${newX - difference}px`
|
|
|
|
}
|
|
|
|
|
|
|
|
if (menuYOffest > innerHeight) {
|
|
|
|
const difference = menuYOffest - innerHeight + 15
|
|
|
|
this.yPos = `${newY - difference}px`
|
|
|
|
}
|
|
|
|
})
|
2020-08-03 20:12:53 +00:00
|
|
|
},
|
2020-08-11 19:12:47 +00:00
|
|
|
showDeezerOptions() {
|
2020-08-11 20:08:56 +00:00
|
|
|
this.options.copyDeezerLink.show = true
|
2020-08-11 19:12:47 +00:00
|
|
|
|
2020-08-11 20:08:56 +00:00
|
|
|
downloadQualities.forEach(quality => {
|
|
|
|
this.options[quality.objName].show = true
|
|
|
|
})
|
2020-08-11 19:12:47 +00:00
|
|
|
},
|
|
|
|
tryToDownloadTrack(qualityValue) {
|
|
|
|
Downloads.sendAddToQueue(this.deezerHref, qualityValue)
|
2020-08-03 20:12:53 +00:00
|
|
|
}
|
2020-08-03 17:26:16 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
.context-menu {
|
|
|
|
position: absolute;
|
|
|
|
top: 0;
|
|
|
|
left: 0;
|
|
|
|
min-width: 100px;
|
|
|
|
border-radius: 7px;
|
2020-08-12 17:03:53 +00:00
|
|
|
background: var(--foreground-inverted);
|
2020-08-03 17:26:16 +00:00
|
|
|
box-shadow: 4px 10px 18px 0px hsla(0, 0%, 0%, 0.15);
|
2020-08-12 17:03:53 +00:00
|
|
|
overflow: hidden;
|
2020-08-03 17:26:16 +00:00
|
|
|
z-index: 10000;
|
|
|
|
}
|
|
|
|
|
|
|
|
.menu-option {
|
|
|
|
display: flex;
|
|
|
|
align-items: center;
|
|
|
|
width: 100%;
|
2020-08-03 20:12:53 +00:00
|
|
|
height: 40px;
|
2020-08-03 17:26:16 +00:00
|
|
|
padding-left: 10px;
|
2020-08-03 20:12:53 +00:00
|
|
|
padding-right: 10px;
|
2020-08-03 17:26:16 +00:00
|
|
|
color: var(--foreground);
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
background: var(--table-highlight);
|
|
|
|
filter: brightness(150%);
|
|
|
|
}
|
2020-08-11 19:12:47 +00:00
|
|
|
|
|
|
|
&__text {
|
|
|
|
text-transform: capitalize;
|
|
|
|
}
|
2020-08-03 17:26:16 +00:00
|
|
|
}
|
|
|
|
|
2020-08-03 20:12:53 +00:00
|
|
|
// Resetting buttons only for this component (because the style is scoped)
|
2020-08-03 17:26:16 +00:00
|
|
|
button {
|
|
|
|
color: var(--accent-text);
|
|
|
|
color: unset;
|
|
|
|
background-color: var(--accent-color);
|
|
|
|
background-color: unset;
|
|
|
|
min-width: unset;
|
|
|
|
position: unset;
|
|
|
|
border: unset;
|
|
|
|
border-radius: unset;
|
|
|
|
font-family: unset;
|
|
|
|
font-weight: unset;
|
|
|
|
font-size: unset;
|
|
|
|
padding: unset;
|
|
|
|
margin-right: unset;
|
|
|
|
height: unset;
|
|
|
|
text-transform: unset;
|
|
|
|
cursor: unset;
|
|
|
|
transition: unset;
|
|
|
|
|
|
|
|
&:focus {
|
|
|
|
outline: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
&[disabled] {
|
|
|
|
background-color: unset;
|
|
|
|
color: unset;
|
|
|
|
opacity: unset;
|
|
|
|
}
|
|
|
|
|
|
|
|
&.selective {
|
|
|
|
background-color: unset;
|
|
|
|
color: unset;
|
|
|
|
|
|
|
|
&.active {
|
|
|
|
background-color: unset;
|
|
|
|
color: unset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&.with_icon {
|
|
|
|
display: unset;
|
|
|
|
align-items: unset;
|
|
|
|
|
|
|
|
i {
|
|
|
|
margin-left: unset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
&:active {
|
|
|
|
background-color: unset;
|
|
|
|
transform: unset;
|
|
|
|
}
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
background: unset;
|
|
|
|
border: unset;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</style>
|