added copy image link on context menu with relative italian translation

This commit is contained in:
Roberto Tonino 2020-08-11 22:08:56 +02:00
parent 76f354e3de
commit 6a0966034e
6 changed files with 100 additions and 60 deletions

View File

@ -4,33 +4,34 @@ This is just the WebUI for deemix, it should be used with deemix-pyweb or someth
## What's left to do? ## What's left to do?
- Use Vue as much as possible - [ ] Use Vue app-wide
- First step: rewrite the app in Single File Components way ✅ - First step: rewrite the app in Single File Components way ✅
- Second step: Implement custom contextmenu ⚒ - Second step: Implement routing for the whole app using Vue Router ⚒
- Copy and paste functions ✅ - Third step: Remove jQuery
- Copy Link where possible ✅ - [ ] Implement custom contextmenu ⚒
- Copy Image URL where possible - Copy and paste functions ✅
- Define cases - Copy Link where possible ✅
- Third step: Implement routing for the whole app using Vue Router - Download Quality ✅
- Fourth step: Remove jQuery - Copy Image URL where possible ✅
- Make i18n async (https://kazupon.github.io/vue-i18n/guide/lazy-loading.html) - Resolve problem when positioning out of window (e.g. clicking on the bottom of the window)
- [ ] Make i18n async (https://kazupon.github.io/vue-i18n/guide/lazy-loading.html)
- Use ES2020 async imports, if possible - Use ES2020 async imports, if possible
- Make the UI look coherent - [ ] Make the UI look coherent
- Style buttons - Style buttons
- Style text inputs - Style text inputs
- Style checkboxes - Style checkboxes
- Search tab - [ ] Search tab
- Better placeholer before search - Better placeholer before search
- Link Analyzer - [ ] Link Analyzer
- Better placeholer before analyzing and error feedback - Better placeholer before analyzing and error feedback
- Settings tab - [ ] Settings tab
- Maybe tabbing the section for easy navigation - Maybe tabbing the section for easy navigation
- Could use a carousel, but it's not worth adding a new dep - Could use a carousel, but it's not worth adding a new dep
- Variable selector near template inputs - Variable selector near template inputs
- Block selection where it's not needed (keep only titles artists albums labels and useful data) - [ ] Block selection where it's not needed (keep only titles artists albums labels and useful data)
- There's a SASS mixin for this. Need to use it in the proper classes - There's a SASS mixin for this. Need to use it in the proper classes
- Better feedback for socket.io possible errors - [ ] Better feedback for socket.io possible errors
- Remove images size limit and add warning if > 1200 - [ ] Remove images size limit and add warning if > 1200
- ? - ?
# License # License

File diff suppressed because one or more lines are too long

View File

@ -2,7 +2,7 @@
<div class="context-menu" v-show="menuOpen" ref="contextMenu" :style="{ top: yPos, left: xPos }"> <div class="context-menu" v-show="menuOpen" ref="contextMenu" :style="{ top: yPos, left: xPos }">
<button <button
class="menu-option" class="menu-option"
v-for="option of options" v-for="option of sortedOptions"
:key="option.label" :key="option.label"
v-show="option.show" v-show="option.show"
@click.prevent="option.action" @click.prevent="option.action"
@ -23,7 +23,8 @@ export default {
xPos: 0, xPos: 0,
yPos: 0, yPos: 0,
deezerHref: '', deezerHref: '',
generalHref: '' generalHref: '',
imgSrc: ''
} }
}, },
computed: { computed: {
@ -31,63 +32,85 @@ export default {
// In the action property: // In the action property:
// Use arrow functions to keep the Vue instance in 'this' // Use arrow functions to keep the Vue instance in 'this'
// Use normal functions to keep the object instance in 'this' // Use normal functions to keep the object instance in 'this'
const options = {
const options = [ cut: {
{
label: this.$t('globals.cut'), label: this.$t('globals.cut'),
show: true, show: true,
position: 1,
action: () => { action: () => {
document.execCommand('Cut') document.execCommand('Cut')
} }
}, },
{ copy: {
label: this.$t('globals.copy'), label: this.$t('globals.copy'),
show: true, show: true,
position: 2,
action: () => { action: () => {
document.execCommand('Copy') document.execCommand('Copy')
} }
}, },
{ copyLink: {
label: this.$t('globals.copyLink'), label: this.$t('globals.copyLink'),
show: false, show: false,
position: 3,
action: () => { action: () => {
navigator.clipboard.writeText(this.generalHref).catch(err => { navigator.clipboard.writeText(this.generalHref).catch(err => {
console.error('Link copying failed', err) console.error('Link copying failed', err)
}) })
} }
}, },
{ copyImageLink: {
label: this.$t('globals.copyDeezerLink'), label: this.$t('globals.copyImageLink'),
show: false, show: false,
position: 4,
action: () => { action: () => {
navigator.clipboard.writeText(this.deezerHref).catch(err => { navigator.clipboard.writeText(this.imgSrc).catch(err => {
console.error('Download link copying failed', err) console.error('Image copying failed', err)
}) })
} }
}, },
{ copyDeezerLink: {
label: this.$t('globals.copyDeezerLink'),
show: false,
position: 5,
action: () => {
navigator.clipboard.writeText(this.generalHref).catch(err => {
console.error('Deezer link copying failed', err)
})
}
},
paste: {
label: this.$t('globals.paste'), label: this.$t('globals.paste'),
show: true, show: true,
position: 6,
action: () => { action: () => {
navigator.clipboard.readText().then(text => { navigator.clipboard.readText().then(text => {
document.execCommand('insertText', undefined, text) document.execCommand('insertText', undefined, text)
}) })
} }
} }
] }
downloadQualities.forEach(quality => { let nextValuePosition = Object.values(options).length + 1
options.push({
downloadQualities.forEach((quality, index) => {
options[quality.objName] = {
label: `${this.$t('globals.download', [quality.label])}`, label: `${this.$t('globals.download', [quality.label])}`,
show: false, show: false,
position: nextValuePosition + index,
action: this.tryToDownloadTrack.bind(null, quality.value) action: this.tryToDownloadTrack.bind(null, quality.value)
}) }
}) })
return options return options
}, },
qualities() { // This computed property is used for rendering the options in the wanted order
return downloadQualities // while keeping the options computed property an Object to make the properties
// accessible via property name (es this.options.copy)
sortedOptions() {
return Object.values(this.options).sort((first, second) => {
return first.position < second.position ? -1 : 1
})
} }
}, },
mounted() { mounted() {
@ -110,24 +133,30 @@ export default {
// Show 'Copy Link' option // Show 'Copy Link' option
if (elementClicked.matches('a')) { if (elementClicked.matches('a')) {
this.generalHref = elementClicked.href this.generalHref = elementClicked.href
this.showCopyLinkOption() this.options.copyLink.show = true
} }
let link = null // Show 'Copy Image Link' option
if (elementClicked.matches('img')) {
this.imgSrc = elementClicked.src
this.options.copyImageLink.show = true
}
let deezerLink = null
for (let i = 0; i < path.length; i++) { for (let i = 0; i < path.length; i++) {
if (path[i] == document) break if (path[i] == document) break
if (path[i].matches('[data-link]')) { if (path[i].matches('[data-link]')) {
link = path[i].dataset.link deezerLink = path[i].dataset.link
break break
} }
} }
// Show 'Copy Deezer Link' option // Show 'Copy Deezer Link' option
if (link) { if (deezerLink) {
this.deezerHref = link this.deezerHref = deezerLink
this.showDeezerOptions(link) this.showDeezerOptions()
} }
this.menuOpen = true this.menuOpen = true
@ -136,30 +165,32 @@ export default {
if (!this.menuOpen) return if (!this.menuOpen) return
// Finish all operations before closing (may be not necessary) // Finish all operations before closing (may be not necessary)
this.$nextTick().then(() => { this.$nextTick()
this.menuOpen = false .then(() => {
this.menuOpen = false
this.options[2].show = false this.options.copyLink.show = false
this.options[3].show = false this.options.copyDeezerLink.show = false
this.options.copyImageLink.show = false
for (i = 5; i <= 10; i++) { downloadQualities.forEach(quality => {
this.options[i].show = false this.options[quality.objName].show = false
} })
}) })
.catch(err => {
console.error(err)
})
}, },
positionMenu(newX, newY) { positionMenu(newX, newY) {
this.xPos = `${newX}px` this.xPos = `${newX}px`
this.yPos = `${newY}px` this.yPos = `${newY}px`
}, },
showCopyLinkOption() {
this.options[2].show = true
},
showDeezerOptions() { showDeezerOptions() {
this.options[3].show = true this.options.copyDeezerLink.show = true
for (i = 5; i <= 10; i++) { downloadQualities.forEach(quality => {
this.options[i].show = true this.options[quality.objName].show = true
} })
}, },
tryToDownloadTrack(qualityValue) { tryToDownloadTrack(qualityValue) {
Downloads.sendAddToQueue(this.deezerHref, qualityValue) Downloads.sendAddToQueue(this.deezerHref, qualityValue)

View File

@ -1,25 +1,31 @@
export default [ export default [
{ {
objName: 'flac',
label: 'FLAC', label: 'FLAC',
value: 9 value: 9
}, },
{ {
objName: '320kbps',
label: 'MP3 320kbps', label: 'MP3 320kbps',
value: 3 value: 3
}, },
{ {
objName: '128kbps',
label: 'MP3 128kbps', label: 'MP3 128kbps',
value: 1 value: 1
}, },
{ {
objName: 'realityAudioHQ',
label: '360 Reality Audio [HQ]', label: '360 Reality Audio [HQ]',
value: 15 value: 15
}, },
{ {
objName: 'realityAudioMQ',
label: '360 Reality Audio [MQ]', label: '360 Reality Audio [MQ]',
value: 14 value: 14
}, },
{ {
objName: 'realityAudioLQ',
label: '360 Reality Audio [LQ]', label: '360 Reality Audio [LQ]',
value: 13 value: 13
} }

View File

@ -14,6 +14,7 @@ const en = {
cut: 'cut', cut: 'cut',
copy: 'copy', copy: 'copy',
copyLink: 'copy link', copyLink: 'copy link',
copyImageLink: 'copy image link',
copyDeezerLink: 'copy deezer link', copyDeezerLink: 'copy deezer link',
paste: 'paste', paste: 'paste',
listTabs: { listTabs: {

View File

@ -14,6 +14,7 @@ const it = {
cut: 'taglia', cut: 'taglia',
copy: 'copia', copy: 'copia',
copyLink: 'copia link', copyLink: 'copia link',
copyImageLink: 'copia link immagine',
copyDeezerLink: 'copia link deezer', copyDeezerLink: 'copia link deezer',
paste: 'incolla', paste: 'incolla',
listTabs: { listTabs: {