This commit is contained in:
Ben Arc 2021-06-24 01:40:24 +01:00
parent e7824e0e8d
commit e51ef5b722
8 changed files with 449 additions and 226 deletions

View File

@ -82,7 +82,7 @@
<script> <script>
Vue.component(VueQrcode.name, VueQrcode) Vue.component(VueQrcode.name, VueQrcode)
Vue.use(VueQrcodeReader) Vue.use(VueQrcodeReader)
var mapEvents = function(obj) { var mapEvents = function (obj) {
obj.date = Quasar.utils.date.formatDate( obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000), new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm' 'YYYY-MM-DD HH:mm'
@ -94,7 +94,7 @@
new Vue({ new Vue({
el: '#vue', el: '#vue',
mixins: [windowMixin], mixins: [windowMixin],
data: function() { data: function () {
return { return {
tickets: [], tickets: [],
ticketsTable: { ticketsTable: {
@ -119,35 +119,35 @@
} }
}, },
methods: { methods: {
hoverEmail: function(tmp) { hoverEmail: function (tmp) {
this.tickets.data.emailtemp = tmp this.tickets.data.emailtemp = tmp
}, },
closeCamera: function() { closeCamera: function () {
this.sendCamera.show = false this.sendCamera.show = false
}, },
showCamera: function() { showCamera: function () {
this.sendCamera.show = true this.sendCamera.show = true
}, },
decodeQR: function(res) { decodeQR: function (res) {
this.sendCamera.show = false this.sendCamera.show = false
var self = this var self = this
LNbits.api LNbits.api
.request('GET', '/events/api/v1/register/ticket/' + res) .request('GET', '/events/api/v1/register/ticket/' + res)
.then(function(response) { .then(function (response) {
self.$q.notify({ self.$q.notify({
type: 'positive', type: 'positive',
message: 'Registered!' message: 'Registered!'
}) })
setTimeout(function() { setTimeout(function () {
window.location.reload() window.location.reload()
}, 2000) }, 2000)
}) })
.catch(function(error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}, },
getEventTickets: function() { getEventTickets: function () {
var self = this var self = this
console.log('obj') console.log('obj')
LNbits.api LNbits.api
@ -155,17 +155,17 @@
'GET', 'GET',
'/events/api/v1/eventtickets/{{ wallet_id }}/{{ event_id }}' '/events/api/v1/eventtickets/{{ wallet_id }}/{{ event_id }}'
) )
.then(function(response) { .then(function (response) {
self.tickets = response.data.map(function(obj) { self.tickets = response.data.map(function (obj) {
return mapEvents(obj) return mapEvents(obj)
}) })
}) })
.catch(function(error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
} }
}, },
created: function() { created: function () {
this.getEventTickets() this.getEventTickets()
} }
}) })

View File

@ -93,7 +93,11 @@ new Vue({
getJukeboxes() { getJukeboxes() {
self = this self = this
LNbits.api LNbits.api
.request('GET', '/jukebox/api/v1/jukebox', self.g.user.wallets[0].adminkey) .request(
'GET',
'/jukebox/api/v1/jukebox',
self.g.user.wallets[0].adminkey
)
.then(function (response) { .then(function (response) {
self.JukeboxLinks = response.data.map(mapJukebox) self.JukeboxLinks = response.data.map(mapJukebox)
}) })
@ -165,10 +169,10 @@ new Vue({
LNbits.utils.notifyApiError(err) LNbits.utils.notifyApiError(err)
}) })
}, },
authAccess() { authAccess() {
self = this self = this
self.requestAuthorization() self.requestAuthorization()
self.getSpotifyTokens() self.getSpotifyTokens()
self.$q.notify({ self.$q.notify({
spinner: true, spinner: true,
message: 'Processing', message: 'Processing',
@ -195,37 +199,37 @@ new Vue({
if (self.jukeboxDialog.data.sp_access_token) { if (self.jukeboxDialog.data.sp_access_token) {
self.refreshPlaylists() self.refreshPlaylists()
self.refreshDevices() self.refreshDevices()
console.log("this.devices") console.log('this.devices')
console.log(self.devices) console.log(self.devices)
console.log("this.devices") console.log('this.devices')
setTimeout(function () { setTimeout(function () {
if (self.devices.length < 1 || self.playlists.length < 1) { if (self.devices.length < 1 || self.playlists.length < 1) {
self.$q.notify({ self.$q.notify({
spinner: true, spinner: true,
color: 'red', color: 'red',
message: message:
'Error! Make sure Spotify is open on the device you wish to use, has playlists, and is playing something', 'Error! Make sure Spotify is open on the device you wish to use, has playlists, and is playing something',
timeout: 10000 timeout: 10000
})
LNbits.api
.request(
'DELETE',
'/jukebox/api/v1/jukebox/' + response.data.id,
self.g.user.wallets[0].adminkey
)
.then(function (response) {
self.getJukeboxes()
}) })
.catch(err => { LNbits.api
LNbits.utils.notifyApiError(err) .request(
}) 'DELETE',
clearInterval(timerId) '/jukebox/api/v1/jukebox/' + response.data.id,
self.closeFormDialog() self.g.user.wallets[0].adminkey
} else { )
self.step = 4 .then(function (response) {
clearInterval(timerId) self.getJukeboxes()
} })
}, 2000) .catch(err => {
LNbits.utils.notifyApiError(err)
})
clearInterval(timerId)
self.closeFormDialog()
} else {
self.step = 4
clearInterval(timerId)
}
}, 2000)
} }
} }
}) })
@ -347,15 +351,15 @@ new Vue({
} }
} }
}, },
refreshDevices() { refreshDevices() {
self = this self = this
self.deviceApi( self.deviceApi(
'GET', 'GET',
'https://api.spotify.com/v1/me/player/devices', 'https://api.spotify.com/v1/me/player/devices',
null null
) )
}, },
fetchAccessToken(code) { fetchAccessToken(code) {
self = this self = this
let body = 'grant_type=authorization_code' let body = 'grant_type=authorization_code'
body += '&code=' + code body += '&code=' + code
@ -363,16 +367,16 @@ new Vue({
'&redirect_uri=' + '&redirect_uri=' +
encodeURI(self.locationcbPath + self.jukeboxDialog.data.sp_id) encodeURI(self.locationcbPath + self.jukeboxDialog.data.sp_id)
self.callAuthorizationApi(body) self.callAuthorizationApi(body)
}, },
refreshAccessToken() { refreshAccessToken() {
self = this self = this
let body = 'grant_type=refresh_token' let body = 'grant_type=refresh_token'
body += '&refresh_token=' + self.jukeboxDialog.data.sp_refresh_token body += '&refresh_token=' + self.jukeboxDialog.data.sp_refresh_token
body += '&client_id=' + self.jukeboxDialog.data.sp_user body += '&client_id=' + self.jukeboxDialog.data.sp_user
self.callAuthorizationApi(body) self.callAuthorizationApi(body)
}, },
callAuthorizationApi(body) { callAuthorizationApi(body) {
self = this self = this
console.log( console.log(
btoa( btoa(

View File

@ -6,14 +6,9 @@ new Vue({
el: '#vue', el: '#vue',
mixins: [windowMixin], mixins: [windowMixin],
data() { data() {
return { return {}
}
}, },
computed: {}, computed: {},
methods: { methods: {},
created() {}
},
created() {
}
}) })

View File

@ -1,24 +1,33 @@
<q-card-section> <q-card-section>
To use this extension you need a Spotify client ID and client secret. You To use this extension you need a Spotify client ID and client secret. You get
get these by creating an app in the Spotify developers dashboard these by creating an app in the Spotify developers dashboard
<a style="color:#43a047" href="https://developer.spotify.com/dashboard/applications">here </a> <a
<br /><br />Select the playlists you want people to be able to pay for, style="color: #43a047"
share the frontend page, profit :) <br /><br /> href="https://developer.spotify.com/dashboard/applications"
Made by, <a style="color:#43a047" href="https://twitter.com/arcbtc">benarc</a>. Inspired by, >here
<a style="color:#43a047" href="https://twitter.com/pirosb3/status/1056263089128161280">pirosb3</a>. </a>
<br /><br />Select the playlists you want people to be able to pay for, share
the frontend page, profit :) <br /><br />
Made by,
<a style="color: #43a047" href="https://twitter.com/arcbtc">benarc</a>.
Inspired by,
<a
style="color: #43a047"
href="https://twitter.com/pirosb3/status/1056263089128161280"
>pirosb3</a
>.
</q-card-section> </q-card-section>
<q-expansion-item
group="extras"
icon="swap_vertical_circle"
label="API info"
:content-inset-level="0.5"
<q-expansion-item group="extras" icon="swap_vertical_circle" label="API info" :content-inset-level="0.5"> >
<q-expansion-item group="api" dense expand-separator label="List jukeboxes"> <q-expansion-item group="api" dense expand-separator label="List jukeboxes">
<q-card> <q-card>
<q-card-section> <q-card-section>
<code><span class="text-blue">GET</span> <code><span class="text-blue">GET</span> /jukebox/api/v1/jukebox</code>
/jukebox/api/v1/jukebox</code>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5> <h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br /> <code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5> <h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
@ -27,7 +36,8 @@
</h5> </h5>
<code>[&lt;jukebox_object&gt;, ...]</code> <code>[&lt;jukebox_object&gt;, ...]</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> <h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X GET {{ request.url_root }}api/v1/jukebox -H "X-Api-Key: {{ <code
>curl -X GET {{ request.url_root }}api/v1/jukebox -H "X-Api-Key: {{
g.user.wallets[0].adminkey }}" g.user.wallets[0].adminkey }}"
</code> </code>
</q-card-section> </q-card-section>
@ -36,8 +46,10 @@
<q-expansion-item group="api" dense expand-separator label="Get jukebox"> <q-expansion-item group="api" dense expand-separator label="Get jukebox">
<q-card> <q-card>
<q-card-section> <q-card-section>
<code><span class="text-blue">GET</span> <code
/jukebox/api/v1/jukebox/&lt;juke_id&gt;</code> ><span class="text-blue">GET</span>
/jukebox/api/v1/jukebox/&lt;juke_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5> <h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br /> <code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5> <h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
@ -46,36 +58,44 @@
</h5> </h5>
<code>&lt;jukebox_object&gt;</code> <code>&lt;jukebox_object&gt;</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> <h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X GET {{ request.url_root }}api/v1/jukebox/&lt;juke_id&gt; -H "X-Api-Key: {{ <code
g.user.wallets[0].adminkey }}" >curl -X GET {{ request.url_root }}api/v1/jukebox/&lt;juke_id&gt; -H
"X-Api-Key: {{ g.user.wallets[0].adminkey }}"
</code> </code>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item>
<q-expansion-item group="api" dense expand-separator label="Create/update track"> <q-expansion-item
group="api"
dense
expand-separator
label="Create/update track"
>
<q-card> <q-card>
<q-card-section> <q-card-section>
<code><span class="text-green">POST/PUT</span> <code
/jukebox/api/v1/jukebox/</code> ><span class="text-green">POST/PUT</span>
/jukebox/api/v1/jukebox/</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5> <h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br /> <code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none"> <h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
Body (application/json)
</h5>
<h5 class="text-caption q-mt-sm q-mb-none"> <h5 class="text-caption q-mt-sm q-mb-none">
Returns 200 OK (application/json) Returns 200 OK (application/json)
</h5> </h5>
<code>&lt;jukbox_object&gt;</code> <code>&lt;jukbox_object&gt;</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> <h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X POST {{ request.url_root }}api/v1/jukebox/ -d <code
'{"user": &lt;string, user_id&gt;, >curl -X POST {{ request.url_root }}api/v1/jukebox/ -d '{"user":
"title": &lt;string&gt;, "wallet":&lt;string&gt;, "sp_user": &lt;string, user_id&gt;, "title": &lt;string&gt;,
&lt;string, spotify_user_account&gt;, "sp_secret": &lt;string, spotify_user_secret&gt;, "sp_access_token": "wallet":&lt;string&gt;, "sp_user": &lt;string,
&lt;string, not_required&gt;, "sp_refresh_token": spotify_user_account&gt;, "sp_secret": &lt;string,
&lt;string, not_required&gt;, "sp_device": &lt;string, spotify_user_secret&gt;, "sp_playlists": spotify_user_secret&gt;, "sp_access_token": &lt;string,
&lt;string, not_required&gt;, "price": not_required&gt;, "sp_refresh_token": &lt;string, not_required&gt;,
&lt;integer, not_required&gt;}' -H "Content-type: "sp_device": &lt;string, spotify_user_secret&gt;, "sp_playlists":
application/json" -H "X-Api-Key: {{g.user.wallets[0].adminkey }}" &lt;string, not_required&gt;, "price": &lt;integer, not_required&gt;}'
-H "Content-type: application/json" -H "X-Api-Key:
{{g.user.wallets[0].adminkey }}"
</code> </code>
</q-card-section> </q-card-section>
</q-card> </q-card>
@ -83,8 +103,10 @@
<q-expansion-item group="api" dense expand-separator label="Delete jukebox"> <q-expansion-item group="api" dense expand-separator label="Delete jukebox">
<q-card> <q-card>
<q-card-section> <q-card-section>
<code><span class="text-red">DELETE</span> <code
/jukebox/api/v1/jukebox/&lt;juke_id&gt;</code> ><span class="text-red">DELETE</span>
/jukebox/api/v1/jukebox/&lt;juke_id&gt;</code
>
<h5 class="text-caption q-mt-sm q-mb-none">Headers</h5> <h5 class="text-caption q-mt-sm q-mb-none">Headers</h5>
<code>{"X-Api-Key": &lt;admin_key&gt;}</code><br /> <code>{"X-Api-Key": &lt;admin_key&gt;}</code><br />
<h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5> <h5 class="text-caption q-mt-sm q-mb-none">Body (application/json)</h5>
@ -93,9 +115,11 @@
</h5> </h5>
<code>&lt;jukebox_object&gt;</code> <code>&lt;jukebox_object&gt;</code>
<h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5> <h5 class="text-caption q-mt-sm q-mb-none">Curl example</h5>
<code>curl -X DELETE {{ request.url_root }}api/v1/jukebox/&lt;juke_id&gt; -H "X-Api-Key: {{ <code
g.user.wallets[0].adminkey }}" >curl -X DELETE {{ request.url_root }}api/v1/jukebox/&lt;juke_id&gt;
-H "X-Api-Key: {{ g.user.wallets[0].adminkey }}"
</code> </code>
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-expansion-item> </q-expansion-item></q-expansion-item
>

View File

@ -12,7 +12,9 @@
style="font-size: 20rem" style="font-size: 20rem"
></q-icon> ></q-icon>
<h5 class="q-my-none">Ask the host to turn on the device and launch spotify</h5> <h5 class="q-my-none">
Ask the host to turn on the device and launch spotify
</h5>
<br /> <br />
</center> </center>
</q-card-section> </q-card-section>

View File

@ -4,18 +4,36 @@
<div class="col-12 col-md-7 q-gutter-y-md"> <div class="col-12 col-md-7 q-gutter-y-md">
<q-card> <q-card>
<q-card-section> <q-card-section>
<q-btn unelevated color="green-7" class="q-ma-lg" @click="openNewDialog()">Add Spotify Jukebox</q-btn> <q-btn
unelevated
color="green-7"
class="q-ma-lg"
@click="openNewDialog()"
>Add Spotify Jukebox</q-btn
>
{% raw %} {% raw %}
<q-table flat dense :data="JukeboxLinks" row-key="id" :columns="JukeboxTable.columns" <q-table
:pagination.sync="JukeboxTable.pagination" :filter="filter"> flat
dense
:data="JukeboxLinks"
row-key="id"
:columns="JukeboxTable.columns"
:pagination.sync="JukeboxTable.pagination"
:filter="filter"
>
<template v-slot:header="props"> <template v-slot:header="props">
<q-tr :props="props"> <q-tr :props="props">
<q-th auto-width></q-th> <q-th auto-width></q-th>
<q-th auto-width></q-th> <q-th auto-width></q-th>
<q-th v-for="col in props.cols" :key="col.name" :props="props" auto-width> <q-th
v-for="col in props.cols"
:key="col.name"
:props="props"
auto-width
>
<div v-if="col.name == 'id'"></div> <div v-if="col.name == 'id'"></div>
<div v-else>{{ col.label }}</div> <div v-else>{{ col.label }}</div>
</q-th> </q-th>
@ -26,18 +44,43 @@
<template v-slot:body="props"> <template v-slot:body="props">
<q-tr :props="props"> <q-tr :props="props">
<q-td auto-width> <q-td auto-width>
<q-btn unelevated dense size="xs" icon="launch" :color="($q.dark.isActive) ? 'grey-7' : 'grey-5'" <q-btn
@click="openQrCodeDialog(props.row.sp_id)"> unelevated
dense
size="xs"
icon="launch"
:color="($q.dark.isActive) ? 'grey-7' : 'grey-5'"
@click="openQrCodeDialog(props.row.sp_id)"
>
<q-tooltip> Jukebox QR </q-tooltip> <q-tooltip> Jukebox QR </q-tooltip>
</q-btn> </q-btn>
</q-td> </q-td>
<q-td auto-width> <q-td auto-width>
<q-btn flat dense size="xs" @click="updateJukebox(props.row.id)" icon="edit" color="light-blue"></q-btn> <q-btn
<q-btn flat dense size="xs" @click="deleteJukebox(props.row.id)" icon="cancel" color="pink"> flat
dense
size="xs"
@click="updateJukebox(props.row.id)"
icon="edit"
color="light-blue"
></q-btn>
<q-btn
flat
dense
size="xs"
@click="deleteJukebox(props.row.id)"
icon="cancel"
color="pink"
>
<q-tooltip> Delete Jukebox </q-tooltip> <q-tooltip> Delete Jukebox </q-tooltip>
</q-btn> </q-btn>
</q-td> </q-td>
<q-td v-for="col in props.cols" :key="col.name" :props="props" auto-width> <q-td
v-for="col in props.cols"
:key="col.name"
:props="props"
auto-width
>
<div v-if="col.name == 'id'"></div> <div v-if="col.name == 'id'"></div>
<div v-else>{{ col.value }}</div> <div v-else>{{ col.value }}</div>
</q-td> </q-td>
@ -63,23 +106,62 @@
<q-dialog v-model="jukeboxDialog.show" position="top" @hide="closeFormDialog"> <q-dialog v-model="jukeboxDialog.show" position="top" @hide="closeFormDialog">
<q-card class="q-pa-md q-pt-lg q-mt-md" style="width: 100%"> <q-card class="q-pa-md q-pt-lg q-mt-md" style="width: 100%">
<q-stepper v-model="step" active-color="green-7" inactive-color="green-10" vertical animated> <q-stepper
<q-step :name="1" title="Pick wallet, price" icon="account_balance_wallet" :done="step > 1"> v-model="step"
<q-input filled class="q-pt-md" dense v-model.trim="jukeboxDialog.data.title" label="Jukebox name"></q-input> active-color="green-7"
<q-select class="q-pb-md q-pt-md" filled dense emit-value v-model="jukeboxDialog.data.wallet" inactive-color="green-10"
:options="g.user.walletOptions" label="Wallet to use"></q-select> vertical
<q-input filled dense v-model.trim="jukeboxDialog.data.price" type="number" max="1440" label="Price per track" animated
class="q-pb-lg"> >
<q-step
:name="1"
title="Pick wallet, price"
icon="account_balance_wallet"
:done="step > 1"
>
<q-input
filled
class="q-pt-md"
dense
v-model.trim="jukeboxDialog.data.title"
label="Jukebox name"
></q-input>
<q-select
class="q-pb-md q-pt-md"
filled
dense
emit-value
v-model="jukeboxDialog.data.wallet"
:options="g.user.walletOptions"
label="Wallet to use"
></q-select>
<q-input
filled
dense
v-model.trim="jukeboxDialog.data.price"
type="number"
max="1440"
label="Price per track"
class="q-pb-lg"
>
</q-input> </q-input>
<div class="row"> <div class="row">
<div class="col-4"> <div class="col-4">
<q-btn <q-btn
v-if="jukeboxDialog.data.title != null && jukeboxDialog.data.price != null && jukeboxDialog.data.wallet != null" v-if="jukeboxDialog.data.title != null && jukeboxDialog.data.price != null && jukeboxDialog.data.wallet != null"
color="green-7" @click="step = 2">Continue</q-btn> color="green-7"
@click="step = 2"
>Continue</q-btn
>
<q-btn v-else color="green-7" disable>Continue</q-btn> <q-btn v-else color="green-7" disable>Continue</q-btn>
</div> </div>
<div class="col-8"> <div class="col-8">
<q-btn color="green-7" class="float-right" @click="closeFormDialog">Cancel</q-btn> <q-btn
color="green-7"
class="float-right"
@click="closeFormDialog"
>Cancel</q-btn
>
</div> </div>
</div> </div>
@ -90,26 +172,57 @@
<img src="/jukebox/static/spotapi.gif" /> <img src="/jukebox/static/spotapi.gif" />
To use this extension you need a Spotify client ID and client secret. To use this extension you need a Spotify client ID and client secret.
You get these by creating an app in the Spotify developers dashboard You get these by creating an app in the Spotify developers dashboard
<a target="_blank" style="color:#43a047" href="https://developer.spotify.com/dashboard/applications">here</a>. <a
<q-input filled class="q-pb-md q-pt-md" dense v-model.trim="jukeboxDialog.data.sp_user" label="Client ID"> target="_blank"
style="color: #43a047"
href="https://developer.spotify.com/dashboard/applications"
>here</a
>.
<q-input
filled
class="q-pb-md q-pt-md"
dense
v-model.trim="jukeboxDialog.data.sp_user"
label="Client ID"
>
</q-input> </q-input>
<q-input dense v-model="jukeboxDialog.data.sp_secret" filled :type="isPwd ? 'password' : 'text'" <q-input
label="Client secret"> dense
v-model="jukeboxDialog.data.sp_secret"
filled
:type="isPwd ? 'password' : 'text'"
label="Client secret"
>
<template #append> <template #append>
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer" @click="isPwd = !isPwd"> <q-icon
:name="isPwd ? 'visibility_off' : 'visibility'"
class="cursor-pointer"
@click="isPwd = !isPwd"
>
</q-icon> </q-icon>
</template> </template>
</q-input> </q-input>
<div class="row q-mt-md"> <div class="row q-mt-md">
<div class="col-4"> <div class="col-4">
<q-btn v-if="jukeboxDialog.data.sp_secret != null && jukeboxDialog.data.sp_user != null && tokenFetched" <q-btn
color="green-7" @click="submitSpotifyKeys">Submit keys</q-btn> v-if="jukeboxDialog.data.sp_secret != null && jukeboxDialog.data.sp_user != null && tokenFetched"
<q-btn v-else color="green-7" disable color="green-7">Submit keys</q-btn> color="green-7"
@click="submitSpotifyKeys"
>Submit keys</q-btn
>
<q-btn v-else color="green-7" disable color="green-7"
>Submit keys</q-btn
>
</div> </div>
<div class="col-8"> <div class="col-8">
<q-btn color="green-7" class="float-right" @click="closeFormDialog">Cancel</q-btn> <q-btn
color="green-7"
class="float-right"
@click="closeFormDialog"
>Cancel</q-btn
>
</div> </div>
</div> </div>
@ -120,42 +233,93 @@
<img src="/jukebox/static/spotapi1.gif" /> <img src="/jukebox/static/spotapi1.gif" />
In the app go to edit-settings, set the redirect URI to this link In the app go to edit-settings, set the redirect URI to this link
<br /> <br />
<q-btn dense outline unelevated color="green-7" size="xs" <q-btn
@click="copyText(locationcb + jukeboxDialog.data.sp_id, 'Link copied to clipboard!')">{% raw %}{{ locationcb dense
}}{{ jukeboxDialog.data.sp_id }}{% endraw outline
unelevated
color="green-7"
size="xs"
@click="copyText(locationcb + jukeboxDialog.data.sp_id, 'Link copied to clipboard!')"
>{% raw %}{{ locationcb }}{{ jukeboxDialog.data.sp_id }}{% endraw
%}<q-tooltip> Click to copy URL </q-tooltip> %}<q-tooltip> Click to copy URL </q-tooltip>
</q-btn> </q-btn>
<br /> <br />
Settings can be found Settings can be found
<a target="_blank" style="color:#43a047" href="https://developer.spotify.com/dashboard/applications">here</a>. <a
target="_blank"
style="color: #43a047"
href="https://developer.spotify.com/dashboard/applications"
>here</a
>.
<div class="row q-mt-md"> <div class="row q-mt-md">
<div class="col-4"> <div class="col-4">
<q-btn v-if="jukeboxDialog.data.sp_secret != null && jukeboxDialog.data.sp_user != null && tokenFetched" <q-btn
color="green-7" @click="authAccess">Authorise access</q-btn> v-if="jukeboxDialog.data.sp_secret != null && jukeboxDialog.data.sp_user != null && tokenFetched"
<q-btn v-else color="green-7" disable color="green-7">Authorise access</q-btn> color="green-7"
@click="authAccess"
>Authorise access</q-btn
>
<q-btn v-else color="green-7" disable color="green-7"
>Authorise access</q-btn
>
</div> </div>
<div class="col-8"> <div class="col-8">
<q-btn color="green-7" class="float-right" @click="closeFormDialog">Cancel</q-btn> <q-btn
color="green-7"
class="float-right"
@click="closeFormDialog"
>Cancel</q-btn
>
</div> </div>
</div> </div>
<br /> <br />
</q-step> </q-step>
<q-step :name="4" title="Select playlists" icon="queue_music" active-color="green-8" :done="step > 4"> <q-step
<q-select class="q-pb-md q-pt-md" filled dense emit-value v-model="jukeboxDialog.data.sp_device" :name="4"
:options="devices" label="Device jukebox will play to"></q-select> title="Select playlists"
<q-select class="q-pb-md" filled dense multiple emit-value v-model="jukeboxDialog.data.sp_playlists" icon="queue_music"
:options="playlists" label="Playlists available to the jukebox"></q-select> active-color="green-8"
:done="step > 4"
>
<q-select
class="q-pb-md q-pt-md"
filled
dense
emit-value
v-model="jukeboxDialog.data.sp_device"
:options="devices"
label="Device jukebox will play to"
></q-select>
<q-select
class="q-pb-md"
filled
dense
multiple
emit-value
v-model="jukeboxDialog.data.sp_playlists"
:options="playlists"
label="Playlists available to the jukebox"
></q-select>
<div class="row q-mt-md"> <div class="row q-mt-md">
<div class="col-5"> <div class="col-5">
<q-btn v-if="jukeboxDialog.data.sp_device != null && jukeboxDialog.data.sp_playlists != null" <q-btn
color="green-7" @click="createJukebox">Create Jukebox</q-btn> v-if="jukeboxDialog.data.sp_device != null && jukeboxDialog.data.sp_playlists != null"
color="green-7"
@click="createJukebox"
>Create Jukebox</q-btn
>
<q-btn v-else color="green-7" disable>Create Jukebox</q-btn> <q-btn v-else color="green-7" disable>Create Jukebox</q-btn>
</div> </div>
<div class="col-7"> <div class="col-7">
<q-btn color="green-7" class="float-right" @click="closeFormDialog">Cancel</q-btn> <q-btn
color="green-7"
class="float-right"
@click="closeFormDialog"
>Cancel</q-btn
>
</div> </div>
</div> </div>
</q-step> </q-step>
@ -169,15 +333,28 @@
<h5 class="q-my-none">Shareable Jukebox QR</h5> <h5 class="q-my-none">Shareable Jukebox QR</h5>
</center> </center>
<q-responsive :ratio="1" class="q-mx-xl q-mb-md"> <q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode :value="qrCodeDialog.data.url + '/jukebox/' + qrCodeDialog.data.id" :options="{width: 800}" <qrcode
class="rounded-borders"></qrcode> :value="qrCodeDialog.data.url + '/jukebox/' + qrCodeDialog.data.id"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive> </q-responsive>
<div class="row q-mt-lg q-gutter-sm"> <div class="row q-mt-lg q-gutter-sm">
<q-btn outline color="grey" <q-btn
@click="copyText(qrCodeDialog.data.url + '/jukebox/' + qrCodeDialog.data.id, 'Link copied to clipboard!')"> outline
Copy jukebox link</q-btn> color="grey"
<q-btn outline color="grey" type="a" :href="qrCodeDialog.data.url + '/jukebox/' + qrCodeDialog.data.id" @click="copyText(qrCodeDialog.data.url + '/jukebox/' + qrCodeDialog.data.id, 'Link copied to clipboard!')"
target="_blank">Open jukebox</q-btn> >
Copy jukebox link</q-btn
>
<q-btn
outline
color="grey"
type="a"
:href="qrCodeDialog.data.url + '/jukebox/' + qrCodeDialog.data.id"
target="_blank"
>Open jukebox</q-btn
>
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn> <q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
</div> </div>
</q-card> </q-card>
@ -186,4 +363,4 @@
{% endblock %} {% block scripts %} {{ window_vars(user) }} {% endblock %} {% block scripts %} {{ window_vars(user) }}
<script src="https://cdn.jsdelivr.net/npm/pica@6.1.1/dist/pica.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/pica@6.1.1/dist/pica.min.js"></script>
<script src="/jukebox/static/js/index.js"></script> <script src="/jukebox/static/js/index.js"></script>
{% endblock %} {% endblock %}

View File

@ -9,7 +9,8 @@
<img style="width: 100px" :src="currentPlay.image" /> <img style="width: 100px" :src="currentPlay.image" />
</div> </div>
<div class="col-8"> <div class="col-8">
<strong style="font-size: 20px">{{ currentPlay.name }}</strong><br /> <strong style="font-size: 20px">{{ currentPlay.name }}</strong
><br />
<strong style="font-size: 15px">{{ currentPlay.artist }}</strong> <strong style="font-size: 15px">{{ currentPlay.artist }}</strong>
</div> </div>
</div> </div>
@ -19,15 +20,30 @@
<q-card class="q-mt-lg"> <q-card class="q-mt-lg">
<q-card-section> <q-card-section>
<p style="font-size: 22px">Pick a song</p> <p style="font-size: 22px">Pick a song</p>
<q-select outlined v-model="playlist" :options="playlists" label="playlists" @input="selectPlaylist()"> <q-select
outlined
v-model="playlist"
:options="playlists"
label="playlists"
@input="selectPlaylist()"
>
</q-select> </q-select>
</q-card-section> </q-card-section>
<q-card-section class="q-pa-none"> <q-card-section class="q-pa-none">
<q-separator></q-separator> <q-separator></q-separator>
<q-virtual-scroll style="max-height: 300px" :items="currentPlaylist" separator> <q-virtual-scroll
style="max-height: 300px"
:items="currentPlaylist"
separator
>
<template v-slot="{ item, index }"> <template v-slot="{ item, index }">
<q-item :key="index" dense clickable v-ripple <q-item
@click="payForSong(item.id, item.name, item.artist, item.image)"> :key="index"
dense
clickable
v-ripple
@click="payForSong(item.id, item.name, item.artist, item.image)"
>
<q-item-section> <q-item-section>
<q-item-label> <q-item-label>
{{ item.name }} - ({{ item.artist }}) {{ item.name }} - ({{ item.artist }})
@ -55,7 +71,8 @@
</q-card-section> </q-card-section>
<br /> <br />
<div class="row q-mt-lg q-gutter-sm"> <div class="row q-mt-lg q-gutter-sm">
<q-btn outline color="grey" @click="getInvoice(receive.id)">Play for {% endraw %}{{ price }}{% raw %} sats <q-btn outline color="grey" @click="getInvoice(receive.id)"
>Play for {% endraw %}{{ price }}{% raw %} sats
</q-btn> </q-btn>
</div> </div>
</q-card> </q-card>
@ -63,10 +80,16 @@
<q-dialog v-model="receive.dialogues.second" position="top"> <q-dialog v-model="receive.dialogues.second" position="top">
<q-card class="q-pa-lg lnbits__dialog-card"> <q-card class="q-pa-lg lnbits__dialog-card">
<q-responsive :ratio="1" class="q-mx-xl q-mb-md"> <q-responsive :ratio="1" class="q-mx-xl q-mb-md">
<qrcode :value="'lightning:' + receive.paymentReq" :options="{width: 800}" class="rounded-borders"></qrcode> <qrcode
:value="'lightning:' + receive.paymentReq"
:options="{width: 800}"
class="rounded-borders"
></qrcode>
</q-responsive> </q-responsive>
<div class="row q-mt-lg q-gutter-sm"> <div class="row q-mt-lg q-gutter-sm">
<q-btn outline color="grey" @click="copyText(receive.paymentReq)">Copy invoice</q-btn> <q-btn outline color="grey" @click="copyText(receive.paymentReq)"
>Copy invoice</q-btn
>
</div> </div>
</q-card> </q-card>
</q-dialog> </q-dialog>
@ -84,7 +107,7 @@
return { return {
currentPlaylist: [], currentPlaylist: [],
currentlyPlaying: {}, currentlyPlaying: {},
cancelListener: () => { }, cancelListener: () => {},
playlists: {}, playlists: {},
playlist: '', playlist: '',
heavyList: [], heavyList: [],
@ -118,7 +141,7 @@
this.paymentDialog.dismissMsg() this.paymentDialog.dismissMsg()
} }
}, },
closeReceiveDialog() { }, closeReceiveDialog() {},
payForSong(song_id, name, artist, image) { payForSong(song_id, name, artist, image) {
self = this self = this
self.receive.name = name self.receive.name = name
@ -133,12 +156,11 @@
.request( .request(
'GET', 'GET',
'/jukebox/api/v1/jukebox/jb/invoice/' + '/jukebox/api/v1/jukebox/jb/invoice/' +
'{{ juke_id }}' + '{{ juke_id }}' +
'/' + '/' +
song_id song_id
) )
.then(function (response) { .then(function (response) {
self.receive.paymentReq = response.data[0][1] self.receive.paymentReq = response.data[0][1]
self.receive.paymentHash = response.data[0][0] self.receive.paymentHash = response.data[0][0]
self.receive.dialogues.second = true self.receive.dialogues.second = true
@ -153,21 +175,27 @@
self.receive.dialogues.first = false self.receive.dialogues.first = false
self.receive.dialogues.second = false self.receive.dialogues.second = false
self.$q.notify({ self.$q.notify({
message: message: 'Processing'
'Processing',
}) })
LNbits.api LNbits.api
.request( .request(
'GET', 'GET',
'/jukebox/api/v1/jukebox/jb/invoicep/' + song_id + '/{{ juke_id }}/' + self.receive.paymentHash) '/jukebox/api/v1/jukebox/jb/invoicep/' +
song_id +
'/{{ juke_id }}/' +
self.receive.paymentHash
)
.then(function (response1) { .then(function (response1) {
if (response1.data[2] == song_id) { if (response1.data[2] == song_id) {
setTimeout(function () { self.getCurrent() }, 500) setTimeout(function () {
self.getCurrent()
}, 500)
self.$q.notify({ self.$q.notify({
color: 'green', color: 'green',
message: message:
'Success! "' + self.receive.name + '" will be played soon', 'Success! "' +
self.receive.name +
'" will be played soon',
timeout: 3000 timeout: 3000
}) })
@ -176,17 +204,14 @@
} }
}) })
.catch(err => { .catch(err => {
LNbits.utils.notifyApiError(err) LNbits.utils.notifyApiError(err)
self.paid = false self.paid = false
response1 = [] response1 = []
}) })
} }
}, 3000) }, 3000)
}) })
.catch(err => { .catch(err => {
self.$q.notify({ self.$q.notify({
color: 'warning', color: 'warning',
html: true, html: true,
@ -197,16 +222,17 @@
}) })
}, },
checkInvoice(juke_id, paymentHash) { checkInvoice(juke_id, paymentHash) {
var self = this var self = this
LNbits.api LNbits.api
.request( .request(
'GET', 'GET',
'/jukebox/api/v1/jukebox/jb/checkinvoice/' + juke_id + '/' + paymentHash, '/jukebox/api/v1/jukebox/jb/checkinvoice/' +
juke_id +
'/' +
paymentHash,
'filla' 'filla'
) )
.then(function (response) { .then(function (response) {
self.paid = response.data.paid self.paid = response.data.paid
}) })
.catch(function (error) { .catch(function (error) {
@ -214,21 +240,16 @@
}) })
}, },
getCurrent() { getCurrent() {
LNbits.api LNbits.api
.request( .request('GET', '/jukebox/api/v1/jukebox/jb/currently/{{juke_id}}')
'GET',
'/jukebox/api/v1/jukebox/jb/currently/{{juke_id}}')
.then(function (res) { .then(function (res) {
if (res.data.id) { if (res.data.id) {
self.currentlyPlaying = res.data self.currentlyPlaying = res.data
} }
}) })
.catch(function (error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}, },
selectPlaylist() { selectPlaylist() {
self = this self = this
@ -236,9 +257,9 @@
.request( .request(
'GET', 'GET',
'/jukebox/api/v1/jukebox/jb/playlist/' + '/jukebox/api/v1/jukebox/jb/playlist/' +
'{{ juke_id }}' + '{{ juke_id }}' +
'/' + '/' +
self.playlist.split(',')[0].split('-')[1] self.playlist.split(',')[0].split('-')[1]
) )
.then(function (response) { .then(function (response) {
self.currentPlaylist = response.data self.currentPlaylist = response.data
@ -247,7 +268,7 @@
LNbits.utils.notifyApiError(err) LNbits.utils.notifyApiError(err)
}) })
}, },
currentSong() { } currentSong() {}
}, },
created() { created() {
this.getCurrent() this.getCurrent()
@ -258,9 +279,9 @@
.request( .request(
'GET', 'GET',
'/jukebox/api/v1/jukebox/jb/playlist/' + '/jukebox/api/v1/jukebox/jb/playlist/' +
'{{ juke_id }}' + '{{ juke_id }}' +
'/' + '/' +
self.playlists[0].split(',')[0].split('-')[1] self.playlists[0].split(',')[0].split('-')[1]
) )
.then(function (response) { .then(function (response) {
self.currentPlaylist = response.data self.currentPlaylist = response.data
@ -273,4 +294,4 @@
} }
}) })
</script> </script>
{% endblock %} {% endblock %}

View File

@ -214,7 +214,7 @@
</div> </div>
{% endblock %} {% block scripts %} {{ window_vars(user) }} {% endblock %} {% block scripts %} {{ window_vars(user) }}
<script> <script>
var mapUserManager = function(obj) { var mapUserManager = function (obj) {
obj.date = Quasar.utils.date.formatDate( obj.date = Quasar.utils.date.formatDate(
new Date(obj.time * 1000), new Date(obj.time * 1000),
'YYYY-MM-DD HH:mm' 'YYYY-MM-DD HH:mm'
@ -228,7 +228,7 @@
new Vue({ new Vue({
el: '#vue', el: '#vue',
mixins: [windowMixin], mixins: [windowMixin],
data: function() { data: function () {
return { return {
wallets: [], wallets: [],
users: [], users: [],
@ -277,8 +277,8 @@
} }
}, },
computed: { computed: {
userOptions: function() { userOptions: function () {
return this.users.map(function(obj) { return this.users.map(function (obj) {
console.log(obj.id) console.log(obj.id)
return { return {
value: String(obj.id), value: String(obj.id),
@ -290,7 +290,7 @@
methods: { methods: {
///////////////Users//////////////////////////// ///////////////Users////////////////////////////
getUsers: function() { getUsers: function () {
var self = this var self = this
LNbits.api LNbits.api
@ -299,20 +299,20 @@
'/usermanager/api/v1/users', '/usermanager/api/v1/users',
this.g.user.wallets[0].inkey this.g.user.wallets[0].inkey
) )
.then(function(response) { .then(function (response) {
self.users = response.data.map(function(obj) { self.users = response.data.map(function (obj) {
return mapUserManager(obj) return mapUserManager(obj)
}) })
}) })
}, },
openUserUpdateDialog: function(linkId) { openUserUpdateDialog: function (linkId) {
var link = _.findWhere(this.users, {id: linkId}) var link = _.findWhere(this.users, {id: linkId})
this.userDialog.data = _.clone(link._data) this.userDialog.data = _.clone(link._data)
this.userDialog.show = true this.userDialog.show = true
}, },
sendUserFormData: function() { sendUserFormData: function () {
if (this.userDialog.data.id) { if (this.userDialog.data.id) {
} else { } else {
var data = { var data = {
@ -329,7 +329,7 @@
} }
}, },
createUser: function(data) { createUser: function (data) {
var self = this var self = this
LNbits.api LNbits.api
.request( .request(
@ -338,48 +338,48 @@
this.g.user.wallets[0].inkey, this.g.user.wallets[0].inkey,
data data
) )
.then(function(response) { .then(function (response) {
self.users.push(mapUserManager(response.data)) self.users.push(mapUserManager(response.data))
self.userDialog.show = false self.userDialog.show = false
self.userDialog.data = {} self.userDialog.data = {}
data = {} data = {}
self.getWallets() self.getWallets()
}) })
.catch(function(error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}, },
deleteUser: function(userId) { deleteUser: function (userId) {
var self = this var self = this
console.log(userId) console.log(userId)
LNbits.utils LNbits.utils
.confirmDialog('Are you sure you want to delete this User link?') .confirmDialog('Are you sure you want to delete this User link?')
.onOk(function() { .onOk(function () {
LNbits.api LNbits.api
.request( .request(
'DELETE', 'DELETE',
'/usermanager/api/v1/users/' + userId, '/usermanager/api/v1/users/' + userId,
self.g.user.wallets[0].inkey self.g.user.wallets[0].inkey
) )
.then(function(response) { .then(function (response) {
self.users = _.reject(self.users, function(obj) { self.users = _.reject(self.users, function (obj) {
return obj.id == userId return obj.id == userId
}) })
}) })
.catch(function(error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}) })
}, },
exportUsersCSV: function() { exportUsersCSV: function () {
LNbits.utils.exportCSV(this.usersTable.columns, this.users) LNbits.utils.exportCSV(this.usersTable.columns, this.users)
}, },
///////////////Wallets//////////////////////////// ///////////////Wallets////////////////////////////
getWallets: function() { getWallets: function () {
var self = this var self = this
LNbits.api LNbits.api
@ -388,19 +388,19 @@
'/usermanager/api/v1/wallets', '/usermanager/api/v1/wallets',
this.g.user.wallets[0].inkey this.g.user.wallets[0].inkey
) )
.then(function(response) { .then(function (response) {
self.wallets = response.data.map(function(obj) { self.wallets = response.data.map(function (obj) {
return mapUserManager(obj) return mapUserManager(obj)
}) })
}) })
}, },
openWalletUpdateDialog: function(linkId) { openWalletUpdateDialog: function (linkId) {
var link = _.findWhere(this.users, {id: linkId}) var link = _.findWhere(this.users, {id: linkId})
this.walletDialog.data = _.clone(link._data) this.walletDialog.data = _.clone(link._data)
this.walletDialog.show = true this.walletDialog.show = true
}, },
sendWalletFormData: function() { sendWalletFormData: function () {
if (this.walletDialog.data.id) { if (this.walletDialog.data.id) {
} else { } else {
var data = { var data = {
@ -415,7 +415,7 @@
} }
}, },
createWallet: function(data) { createWallet: function (data) {
var self = this var self = this
LNbits.api LNbits.api
.request( .request(
@ -424,43 +424,43 @@
this.g.user.wallets[0].inkey, this.g.user.wallets[0].inkey,
data data
) )
.then(function(response) { .then(function (response) {
self.wallets.push(mapUserManager(response.data)) self.wallets.push(mapUserManager(response.data))
self.walletDialog.show = false self.walletDialog.show = false
self.walletDialog.data = {} self.walletDialog.data = {}
data = {} data = {}
}) })
.catch(function(error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}, },
deleteWallet: function(userId) { deleteWallet: function (userId) {
var self = this var self = this
LNbits.utils LNbits.utils
.confirmDialog('Are you sure you want to delete this wallet link?') .confirmDialog('Are you sure you want to delete this wallet link?')
.onOk(function() { .onOk(function () {
LNbits.api LNbits.api
.request( .request(
'DELETE', 'DELETE',
'/usermanager/api/v1/wallets/' + userId, '/usermanager/api/v1/wallets/' + userId,
self.g.user.wallets[0].inkey self.g.user.wallets[0].inkey
) )
.then(function(response) { .then(function (response) {
self.wallets = _.reject(self.wallets, function(obj) { self.wallets = _.reject(self.wallets, function (obj) {
return obj.id == userId return obj.id == userId
}) })
}) })
.catch(function(error) { .catch(function (error) {
LNbits.utils.notifyApiError(error) LNbits.utils.notifyApiError(error)
}) })
}) })
}, },
exportWalletsCSV: function() { exportWalletsCSV: function () {
LNbits.utils.exportCSV(this.walletsTable.columns, this.wallets) LNbits.utils.exportCSV(this.walletsTable.columns, this.wallets)
} }
}, },
created: function() { created: function () {
if (this.g.user.wallets.length) { if (this.g.user.wallets.length) {
this.getUsers() this.getUsers()
this.getWallets() this.getWallets()