mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-13 07:24:55 +02:00
Add new connection page to support multiple server connection configs
This commit is contained in:
parent
7a091dd428
commit
f57f0e4e0d
30 changed files with 789 additions and 1284 deletions
|
@ -146,7 +146,7 @@ class AudioDownloader : Plugin() {
|
||||||
Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioFile.metadata.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}")
|
Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioFile.metadata.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}")
|
||||||
var destinationFile = File("$itemFolderPath/$destinationFilename")
|
var destinationFile = File("$itemFolderPath/$destinationFilename")
|
||||||
var destinationUri = Uri.fromFile(destinationFile)
|
var destinationUri = Uri.fromFile(destinationFile)
|
||||||
var downloadUri = Uri.parse("${apiHandler.serverUrl}${serverPath}?token=${apiHandler.token}")
|
var downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}")
|
||||||
Log.d(tag, "Audio File Destination Uri $destinationUri | Download URI $downloadUri")
|
Log.d(tag, "Audio File Destination Uri $destinationUri | Download URI $downloadUri")
|
||||||
var downloadItemPart = DownloadItemPart(UUID.randomUUID().toString(), destinationFilename, bookTitle, serverPath, localFolder.name
|
var downloadItemPart = DownloadItemPart(UUID.randomUUID().toString(), destinationFilename, bookTitle, serverPath, localFolder.name
|
||||||
?: "", localFolder.id, downloadUri, destinationUri, null, 0)
|
?: "", localFolder.id, downloadUri, destinationUri, null, 0)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.audiobookshelf.app.data
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.getcapacitor.JSObject
|
import com.getcapacitor.JSObject
|
||||||
import com.getcapacitor.Plugin
|
import com.getcapacitor.Plugin
|
||||||
|
@ -19,19 +20,19 @@ import java.io.File
|
||||||
class DbManager : Plugin() {
|
class DbManager : Plugin() {
|
||||||
val tag = "DbManager"
|
val tag = "DbManager"
|
||||||
|
|
||||||
fun loadDeviceData(): DeviceData {
|
fun getDeviceData(): DeviceData {
|
||||||
return Paper.book("device").read("data") ?: DeviceData(mutableListOf(), null)
|
return Paper.book("device").read("data") ?: DeviceData(mutableListOf(), null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveDeviceData(deviceData:DeviceData) {
|
fun saveDeviceData(deviceData:DeviceData) {
|
||||||
Paper.book("device").write("data", deviceData)
|
Paper.book("device").write("data", deviceData)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadLocalMediaItems():MutableList<LocalMediaItem> {
|
fun getLocalMediaItems():MutableList<LocalMediaItem> {
|
||||||
var localMediaItems:MutableList<LocalMediaItem> = mutableListOf()
|
var localMediaItems:MutableList<LocalMediaItem> = mutableListOf()
|
||||||
Paper.book("localMediaItems").allKeys.forEach {
|
Paper.book("localMediaItems").allKeys.forEach {
|
||||||
var localMediaItem:LocalMediaItem? = Paper.book("localMediaItems").read(it)
|
var localMediaItem:LocalMediaItem? = Paper.book("localMediaItems").read(it)
|
||||||
if (localMediaItem != null) {
|
if (localMediaItem != null) {
|
||||||
|
// TODO: Check to make sure all file paths exist
|
||||||
// if (localMediaItem.coverContentUrl != null) {
|
// if (localMediaItem.coverContentUrl != null) {
|
||||||
// var file = DocumentFile.fromSingleUri(ctx)
|
// var file = DocumentFile.fromSingleUri(ctx)
|
||||||
// if (!file.exists()) {
|
// if (!file.exists()) {
|
||||||
|
@ -45,15 +46,11 @@ class DbManager : Plugin() {
|
||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// localMediaItems = localMediaItems.filter {
|
|
||||||
//
|
|
||||||
// file.exists()
|
|
||||||
// }
|
|
||||||
return localMediaItems
|
return localMediaItems
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLocalMediaItemsInFolder(folderId:String):List<LocalMediaItem> {
|
fun getLocalMediaItemsInFolder(folderId:String):List<LocalMediaItem> {
|
||||||
var localMediaItems = loadLocalMediaItems()
|
var localMediaItems = getLocalMediaItems()
|
||||||
return localMediaItems.filter {
|
return localMediaItems.filter {
|
||||||
it.folderId == folderId
|
it.folderId == folderId
|
||||||
}
|
}
|
||||||
|
@ -111,35 +108,15 @@ class DbManager : Plugin() {
|
||||||
return json
|
return json
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Database calls from webview
|
||||||
|
//
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun saveFromWebview(call: PluginCall) {
|
fun getDeviceData_WV(call:PluginCall) {
|
||||||
var db = call.getString("db", "").toString()
|
|
||||||
var key = call.getString("key", "").toString()
|
|
||||||
var value = call.getObject("value")
|
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
if (db == "" || key == "" || value == null) {
|
var deviceData = getDeviceData()
|
||||||
Log.d(tag, "saveFromWebview Invalid key/value")
|
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(deviceData)))
|
||||||
} else {
|
|
||||||
var json = value as JSONObject
|
|
||||||
saveObject(db, key, json)
|
|
||||||
}
|
}
|
||||||
call.resolve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PluginMethod
|
|
||||||
fun loadFromWebview(call:PluginCall) {
|
|
||||||
var db = call.getString("db", "").toString()
|
|
||||||
var key = call.getString("key", "").toString()
|
|
||||||
if (db == "" || key == "") {
|
|
||||||
Log.d(tag, "loadFromWebview Invalid Key")
|
|
||||||
call.resolve()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var json = loadObject(db, key)
|
|
||||||
var jsobj = JSObject.fromJSONObject(json)
|
|
||||||
call.resolve(jsobj)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
|
@ -175,4 +152,108 @@ class DbManager : Plugin() {
|
||||||
call.resolve(jsobj)
|
call.resolve(jsobj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun setCurrentServerConnectionConfig_WV(call:PluginCall) {
|
||||||
|
var serverConnectionConfigId = call.getString("id", "").toString()
|
||||||
|
var serverConnectionConfig = DeviceManager.deviceData.serverConnectionConfigs.find { it.id == serverConnectionConfigId }
|
||||||
|
|
||||||
|
var username = call.getString("username", "").toString()
|
||||||
|
var token = call.getString("token", "").toString()
|
||||||
|
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
if (serverConnectionConfig == null) { // New Server Connection
|
||||||
|
var serverAddress = call.getString("address", "").toString()
|
||||||
|
|
||||||
|
// Create new server connection config
|
||||||
|
var sscId = DeviceManager.getBase64Id("$serverAddress@$username")
|
||||||
|
var sscIndex = DeviceManager.deviceData.serverConnectionConfigs.size
|
||||||
|
serverConnectionConfig = ServerConnectionConfig(sscId, sscIndex, "$serverAddress ($username)", serverAddress, username, token)
|
||||||
|
|
||||||
|
// Add and save
|
||||||
|
DeviceManager.deviceData.serverConnectionConfigs.add(serverConnectionConfig!!)
|
||||||
|
DeviceManager.deviceData.lastServerConnectionConfigId = serverConnectionConfig?.id
|
||||||
|
saveDeviceData(DeviceManager.deviceData)
|
||||||
|
} else {
|
||||||
|
var shouldSave = false
|
||||||
|
if (serverConnectionConfig?.username != username || serverConnectionConfig?.token != token) {
|
||||||
|
serverConnectionConfig?.username = username
|
||||||
|
serverConnectionConfig?.name = "${serverConnectionConfig?.address} (${serverConnectionConfig?.username})"
|
||||||
|
serverConnectionConfig?.token = token
|
||||||
|
shouldSave = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set last connection config
|
||||||
|
if (DeviceManager.deviceData.lastServerConnectionConfigId != serverConnectionConfigId) {
|
||||||
|
DeviceManager.deviceData.lastServerConnectionConfigId = serverConnectionConfigId
|
||||||
|
shouldSave = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldSave) saveDeviceData(DeviceManager.deviceData)
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceManager.serverConnectionConfig = serverConnectionConfig
|
||||||
|
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(DeviceManager.serverConnectionConfig)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun removeServerConnectionConfig_WV(call:PluginCall) {
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
var serverConnectionConfigId = call.getString("serverConnectionConfigId", "").toString()
|
||||||
|
DeviceManager.deviceData.serverConnectionConfigs = DeviceManager.deviceData.serverConnectionConfigs.filter { it.id != serverConnectionConfigId } as MutableList<ServerConnectionConfig>
|
||||||
|
if (DeviceManager.deviceData.lastServerConnectionConfigId == serverConnectionConfigId) {
|
||||||
|
DeviceManager.deviceData.lastServerConnectionConfigId = null
|
||||||
|
}
|
||||||
|
saveDeviceData(DeviceManager.deviceData)
|
||||||
|
if (DeviceManager.serverConnectionConfig?.id == serverConnectionConfigId) {
|
||||||
|
DeviceManager.serverConnectionConfig = null
|
||||||
|
}
|
||||||
|
call.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun logout_WV(call:PluginCall) {
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
DeviceManager.serverConnectionConfig = null
|
||||||
|
DeviceManager.deviceData.lastServerConnectionConfigId = null
|
||||||
|
saveDeviceData(DeviceManager.deviceData)
|
||||||
|
call.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Generic Webview calls to db
|
||||||
|
//
|
||||||
|
@PluginMethod
|
||||||
|
fun saveFromWebview(call: PluginCall) {
|
||||||
|
var db = call.getString("db", "").toString()
|
||||||
|
var key = call.getString("key", "").toString()
|
||||||
|
var value = call.getObject("value")
|
||||||
|
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
if (db == "" || key == "" || value == null) {
|
||||||
|
Log.d(tag, "saveFromWebview Invalid key/value")
|
||||||
|
} else {
|
||||||
|
var json = value as JSONObject
|
||||||
|
saveObject(db, key, json)
|
||||||
|
}
|
||||||
|
call.resolve()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun loadFromWebview(call:PluginCall) {
|
||||||
|
var db = call.getString("db", "").toString()
|
||||||
|
var key = call.getString("key", "").toString()
|
||||||
|
if (db == "" || key == "") {
|
||||||
|
Log.d(tag, "loadFromWebview Invalid Key")
|
||||||
|
call.resolve()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var json = loadObject(db, key)
|
||||||
|
var jsobj = JSObject.fromJSONObject(json)
|
||||||
|
call.resolve(jsobj)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
data class ServerConfig(
|
data class ServerConnectionConfig(
|
||||||
var id:String,
|
var id:String,
|
||||||
var index:Int,
|
var index:Int,
|
||||||
var name:String,
|
var name:String,
|
||||||
|
@ -14,8 +14,8 @@ data class ServerConfig(
|
||||||
)
|
)
|
||||||
|
|
||||||
data class DeviceData(
|
data class DeviceData(
|
||||||
var serverConfigs:MutableList<ServerConfig>,
|
var serverConnectionConfigs:MutableList<ServerConnectionConfig>,
|
||||||
var lastServerConfigId:String?
|
var lastServerConnectionConfigId:String?
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
|
|
@ -1,19 +1,18 @@
|
||||||
package com.audiobookshelf.app.device
|
package com.audiobookshelf.app.device
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.anggrayudi.storage.file.id
|
|
||||||
import com.audiobookshelf.app.data.DbManager
|
import com.audiobookshelf.app.data.DbManager
|
||||||
import com.audiobookshelf.app.data.DeviceData
|
import com.audiobookshelf.app.data.DeviceData
|
||||||
import com.audiobookshelf.app.data.ServerConfig
|
import com.audiobookshelf.app.data.ServerConnectionConfig
|
||||||
|
|
||||||
object DeviceManager {
|
object DeviceManager {
|
||||||
val tag = "DeviceManager"
|
val tag = "DeviceManager"
|
||||||
val dbManager:DbManager = DbManager()
|
val dbManager:DbManager = DbManager()
|
||||||
var deviceData:DeviceData = dbManager.loadDeviceData()
|
var deviceData:DeviceData = dbManager.getDeviceData()
|
||||||
var currentServerConfig: ServerConfig? = null
|
var serverConnectionConfig: ServerConnectionConfig? = null
|
||||||
|
|
||||||
val serverAddress get() = currentServerConfig?.address ?: ""
|
val serverAddress get() = serverConnectionConfig?.address ?: ""
|
||||||
val token get() = currentServerConfig?.token ?: ""
|
val token get() = serverConnectionConfig?.token ?: ""
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Log.d(tag, "Device Manager Singleton invoked")
|
Log.d(tag, "Device Manager Singleton invoked")
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
package com.audiobookshelf.app.server
|
package com.audiobookshelf.app.server
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.audiobookshelf.app.data.Library
|
import com.audiobookshelf.app.data.Library
|
||||||
import com.audiobookshelf.app.data.LibraryItem
|
import com.audiobookshelf.app.data.LibraryItem
|
||||||
import com.audiobookshelf.app.data.MediaTypeMetadata
|
|
||||||
import com.audiobookshelf.app.data.PlaybackSession
|
import com.audiobookshelf.app.data.PlaybackSession
|
||||||
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import com.getcapacitor.JSArray
|
import com.getcapacitor.JSArray
|
||||||
|
@ -21,26 +20,15 @@ class ApiHandler {
|
||||||
val tag = "ApiHandler"
|
val tag = "ApiHandler"
|
||||||
private var client = OkHttpClient()
|
private var client = OkHttpClient()
|
||||||
var ctx: Context
|
var ctx: Context
|
||||||
var serverUrl = ""
|
|
||||||
var token = ""
|
|
||||||
var storageSharedPreferences: SharedPreferences? = null
|
var storageSharedPreferences: SharedPreferences? = null
|
||||||
|
|
||||||
constructor(_ctx: Context) {
|
constructor(_ctx: Context) {
|
||||||
ctx = _ctx
|
ctx = _ctx
|
||||||
init()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun init() {
|
|
||||||
storageSharedPreferences = ctx.getSharedPreferences("CapacitorStorage", Activity.MODE_PRIVATE)
|
|
||||||
serverUrl = storageSharedPreferences?.getString("serverUrl", "").toString()
|
|
||||||
Log.d(tag, "SHARED PREF SERVERURL $serverUrl")
|
|
||||||
token = storageSharedPreferences?.getString("token", "").toString()
|
|
||||||
Log.d(tag, "SHARED PREF TOKEN $token")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getRequest(endpoint:String, cb: (JSObject) -> Unit) {
|
fun getRequest(endpoint:String, cb: (JSObject) -> Unit) {
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url("$serverUrl$endpoint").addHeader("Authorization", "Bearer $token")
|
.url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}")
|
||||||
.build()
|
.build()
|
||||||
makeRequest(request, cb)
|
makeRequest(request, cb)
|
||||||
}
|
}
|
||||||
|
@ -49,7 +37,7 @@ class ApiHandler {
|
||||||
val mediaType = "application/json; charset=utf-8".toMediaType()
|
val mediaType = "application/json; charset=utf-8".toMediaType()
|
||||||
val requestBody = payload.toString().toRequestBody(mediaType)
|
val requestBody = payload.toString().toRequestBody(mediaType)
|
||||||
val request = Request.Builder().post(requestBody)
|
val request = Request.Builder().post(requestBody)
|
||||||
.url("$serverUrl$endpoint").addHeader("Authorization", "Bearer $token")
|
.url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}")
|
||||||
.build()
|
.build()
|
||||||
makeRequest(request, cb)
|
makeRequest(request, cb)
|
||||||
}
|
}
|
||||||
|
@ -125,8 +113,8 @@ class ApiHandler {
|
||||||
else payload.put("forceTranscode", true)
|
else payload.put("forceTranscode", true)
|
||||||
|
|
||||||
postRequest("/api/items/$libraryItemId/play", payload) {
|
postRequest("/api/items/$libraryItemId/play", payload) {
|
||||||
it.put("serverUrl", serverUrl)
|
it.put("serverUrl", DeviceManager.serverAddress)
|
||||||
it.put("token", token)
|
it.put("token", DeviceManager.token)
|
||||||
val playbackSession = mapper.readValue<PlaybackSession>(it.toString())
|
val playbackSession = mapper.readValue<PlaybackSession>(it.toString())
|
||||||
cb(playbackSession)
|
cb(playbackSession)
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,15 +151,15 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setListeners() {
|
setListeners() {
|
||||||
if (!this.$server.socket) {
|
// if (!this.$server.socket) {
|
||||||
console.error('Invalid server socket not set')
|
// console.error('Invalid server socket not set')
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
this.$server.socket.on('stream_open', this.streamOpen)
|
// this.$server.socket.on('stream_open', this.streamOpen)
|
||||||
this.$server.socket.on('stream_closed', this.streamClosed)
|
// this.$server.socket.on('stream_closed', this.streamClosed)
|
||||||
this.$server.socket.on('stream_progress', this.streamProgress)
|
// this.$server.socket.on('stream_progress', this.streamProgress)
|
||||||
this.$server.socket.on('stream_ready', this.streamReady)
|
// this.$server.socket.on('stream_ready', this.streamReady)
|
||||||
this.$server.socket.on('stream_reset', this.streamReset)
|
// this.$server.socket.on('stream_reset', this.streamReset)
|
||||||
},
|
},
|
||||||
closeStreamOnly() {
|
closeStreamOnly() {
|
||||||
// If user logs out or disconnects from server and not playing local
|
// If user logs out or disconnects from server and not playing local
|
||||||
|
@ -203,13 +203,13 @@ export default {
|
||||||
if (this.onSleepTimerEndedListener) this.onSleepTimerEndedListener.remove()
|
if (this.onSleepTimerEndedListener) this.onSleepTimerEndedListener.remove()
|
||||||
if (this.onSleepTimerSetListener) this.onSleepTimerSetListener.remove()
|
if (this.onSleepTimerSetListener) this.onSleepTimerSetListener.remove()
|
||||||
|
|
||||||
if (this.$server.socket) {
|
// if (this.$server.socket) {
|
||||||
this.$server.socket.off('stream_open', this.streamOpen)
|
// this.$server.socket.off('stream_open', this.streamOpen)
|
||||||
this.$server.socket.off('stream_closed', this.streamClosed)
|
// this.$server.socket.off('stream_closed', this.streamClosed)
|
||||||
this.$server.socket.off('stream_progress', this.streamProgress)
|
// this.$server.socket.off('stream_progress', this.streamProgress)
|
||||||
this.$server.socket.off('stream_ready', this.streamReady)
|
// this.$server.socket.off('stream_ready', this.streamReady)
|
||||||
this.$server.socket.off('stream_reset', this.streamReset)
|
// this.$server.socket.off('stream_reset', this.streamReset)
|
||||||
}
|
// }
|
||||||
this.$eventBus.$off('play-item', this.playLibraryItem)
|
this.$eventBus.$off('play-item', this.playLibraryItem)
|
||||||
this.$eventBus.$off('play-local-item', this.playLocalItem)
|
this.$eventBus.$off('play-local-item', this.playLocalItem)
|
||||||
this.$eventBus.$off('close-stream', this.closeStreamOnly)
|
this.$eventBus.$off('close-stream', this.closeStreamOnly)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<div class="absolute top-0 left-0 w-full h-full bg-black transition-opacity duration-200" :class="show ? 'bg-opacity-60 pointer-events-auto' : 'bg-opacity-0'" @click="clickBackground" />
|
<div class="absolute top-0 left-0 w-full h-full bg-black transition-opacity duration-200" :class="show ? 'bg-opacity-60 pointer-events-auto' : 'bg-opacity-0'" @click="clickBackground" />
|
||||||
<div class="absolute top-0 right-0 w-64 h-full bg-primary transform transition-transform py-6 pointer-events-auto" :class="show ? '' : 'translate-x-64'" @click.stop>
|
<div class="absolute top-0 right-0 w-64 h-full bg-primary transform transition-transform py-6 pointer-events-auto" :class="show ? '' : 'translate-x-64'" @click.stop>
|
||||||
<div class="px-6 mb-4">
|
<div class="px-6 mb-4">
|
||||||
<p v-if="socketConnected" class="text-base">
|
<p v-if="user" class="text-base">
|
||||||
Welcome,
|
Welcome,
|
||||||
<strong>{{ username }}</strong>
|
<strong>{{ username }}</strong>
|
||||||
</p>
|
</p>
|
||||||
|
@ -16,16 +16,21 @@
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<div class="absolute bottom-0 left-0 w-full flex items-center py-6 px-6 text-gray-300">
|
<div class="absolute bottom-0 left-0 w-full py-6 px-6 text-gray-300">
|
||||||
|
<div v-if="serverConnectionConfig" class="mb-4 flex justify-center">
|
||||||
|
<p class="text-xs">{{ serverConnectionConfig.address }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center">
|
||||||
<p class="text-xs">{{ $config.version }}</p>
|
<p class="text-xs">{{ $config.version }}</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<div v-if="socketConnected" class="flex items-center" @click="logout">
|
<div v-if="user" class="flex items-center" @click="logout">
|
||||||
<p class="text-xs pr-2">Logout</p>
|
<p class="text-xs pr-2">Logout</p>
|
||||||
<span class="material-icons text-sm">logout</span>
|
<span class="material-icons text-sm">logout</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
@ -62,6 +67,9 @@ export default {
|
||||||
user() {
|
user() {
|
||||||
return this.$store.state.user.user
|
return this.$store.state.user.user
|
||||||
},
|
},
|
||||||
|
serverConnectionConfig() {
|
||||||
|
return this.$store.state.user.serverConnectionConfig
|
||||||
|
},
|
||||||
username() {
|
username() {
|
||||||
return this.user ? this.user.username : ''
|
return this.user ? this.user.username : ''
|
||||||
},
|
},
|
||||||
|
@ -112,7 +120,9 @@ export default {
|
||||||
await this.$axios.$post('/logout').catch((error) => {
|
await this.$axios.$post('/logout').catch((error) => {
|
||||||
console.error(error)
|
console.error(error)
|
||||||
})
|
})
|
||||||
this.$server.logout()
|
this.$socket.logout()
|
||||||
|
await this.$db.logout()
|
||||||
|
this.$store.commit('user/logout')
|
||||||
this.$router.push('/connect')
|
this.$router.push('/connect')
|
||||||
},
|
},
|
||||||
touchstart(e) {
|
touchstart(e) {
|
||||||
|
|
|
@ -448,15 +448,15 @@ export default {
|
||||||
this.$eventBus.$on('downloads-loaded', this.downloadsLoaded)
|
this.$eventBus.$on('downloads-loaded', this.downloadsLoaded)
|
||||||
this.$store.commit('user/addSettingsListener', { id: 'lazy-bookshelf', meth: this.settingsUpdated })
|
this.$store.commit('user/addSettingsListener', { id: 'lazy-bookshelf', meth: this.settingsUpdated })
|
||||||
|
|
||||||
if (this.$server.socket) {
|
// if (this.$server.socket) {
|
||||||
this.$server.socket.on('item_updated', this.libraryItemUpdated)
|
// this.$server.socket.on('item_updated', this.libraryItemUpdated)
|
||||||
this.$server.socket.on('item_added', this.libraryItemAdded)
|
// this.$server.socket.on('item_added', this.libraryItemAdded)
|
||||||
this.$server.socket.on('item_removed', this.libraryItemRemoved)
|
// this.$server.socket.on('item_removed', this.libraryItemRemoved)
|
||||||
this.$server.socket.on('items_updated', this.libraryItemsUpdated)
|
// this.$server.socket.on('items_updated', this.libraryItemsUpdated)
|
||||||
this.$server.socket.on('items_added', this.libraryItemsAdded)
|
// this.$server.socket.on('items_added', this.libraryItemsAdded)
|
||||||
} else {
|
// } else {
|
||||||
console.error('Bookshelf - Socket not initialized')
|
// console.error('Bookshelf - Socket not initialized')
|
||||||
}
|
// }
|
||||||
},
|
},
|
||||||
removeListeners() {
|
removeListeners() {
|
||||||
var bookshelf = document.getElementById('bookshelf-wrapper')
|
var bookshelf = document.getElementById('bookshelf-wrapper')
|
||||||
|
@ -468,28 +468,28 @@ export default {
|
||||||
this.$eventBus.$off('downloads-loaded', this.downloadsLoaded)
|
this.$eventBus.$off('downloads-loaded', this.downloadsLoaded)
|
||||||
this.$store.commit('user/removeSettingsListener', 'lazy-bookshelf')
|
this.$store.commit('user/removeSettingsListener', 'lazy-bookshelf')
|
||||||
|
|
||||||
if (this.$server.socket) {
|
// if (this.$server.socket) {
|
||||||
this.$server.socket.off('item_updated', this.libraryItemUpdated)
|
// this.$server.socket.off('item_updated', this.libraryItemUpdated)
|
||||||
this.$server.socket.off('item_added', this.libraryItemAdded)
|
// this.$server.socket.off('item_added', this.libraryItemAdded)
|
||||||
this.$server.socket.off('item_removed', this.libraryItemRemoved)
|
// this.$server.socket.off('item_removed', this.libraryItemRemoved)
|
||||||
this.$server.socket.off('items_updated', this.libraryItemsUpdated)
|
// this.$server.socket.off('items_updated', this.libraryItemsUpdated)
|
||||||
this.$server.socket.off('items_added', this.libraryItemsAdded)
|
// this.$server.socket.off('items_added', this.libraryItemsAdded)
|
||||||
} else {
|
// } else {
|
||||||
console.error('Bookshelf - Socket not initialized')
|
// console.error('Bookshelf - Socket not initialized')
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.$server.initialized) {
|
// if (this.$server.initialized) {
|
||||||
this.init()
|
// this.init()
|
||||||
} else {
|
// } else {
|
||||||
this.initDownloads()
|
// this.initDownloads()
|
||||||
}
|
// }
|
||||||
this.$server.on('initialized', this.socketInit)
|
this.$socket.on('initialized', this.socketInit)
|
||||||
this.initListeners()
|
this.initListeners()
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.$server.off('initialized', this.socketInit)
|
this.$socket.off('initialized', this.socketInit)
|
||||||
this.removeListeners()
|
this.removeListeners()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
312
components/connection/ServerConnectForm.vue
Normal file
312
components/connection/ServerConnectForm.vue
Normal file
|
@ -0,0 +1,312 @@
|
||||||
|
<template>
|
||||||
|
<div class="w-full max-w-md mx-auto px-4 sm:px-6 lg:px-8 z-10">
|
||||||
|
<div v-show="!loggedIn" class="mt-8 bg-primary overflow-hidden shadow rounded-lg p-6 w-full">
|
||||||
|
<template v-if="!showForm">
|
||||||
|
<div v-for="config in serverConnectionConfigs" :key="config.id" class="flex items-center py-4 my-1 border-b border-white border-opacity-10 relative" @click="connectToServer(config)">
|
||||||
|
<span class="material-icons-outlined text-xl text-gray-300">dns</span>
|
||||||
|
<p class="pl-3 pr-6 text-base text-gray-200">{{ config.name }}</p>
|
||||||
|
|
||||||
|
<div class="absolute top-0 right-0 h-full px-4 flex items-center" @click.stop="editServerConfig(config)">
|
||||||
|
<span class="material-icons text-lg text-gray-300">more_vert</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-1 py-4 w-full">
|
||||||
|
<ui-btn class="w-full" @click="newServerConfigClick">Add New Server</ui-btn>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<form v-show="!showAuth" @submit.prevent="submit" novalidate class="w-full">
|
||||||
|
<h2 class="text-lg leading-7 mb-2">Server address</h2>
|
||||||
|
<ui-text-input v-model="serverConfig.address" :disabled="processing || !networkConnected || serverConfig.id" placeholder="http://55.55.55.55:13378" type="url" class="w-full sm:w-72 h-10" />
|
||||||
|
<div class="flex justify-end">
|
||||||
|
<ui-btn :disabled="processing || !networkConnected" type="submit" :padding-x="3" class="h-10 mt-4">{{ networkConnected ? 'Submit' : 'No Internet' }}</ui-btn>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<template v-if="showAuth">
|
||||||
|
<div v-if="serverConfig.id" class="flex items-center mb-4" @click="showServerList">
|
||||||
|
<span class="material-icons text-gray-300">arrow_back</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="flex items-center">
|
||||||
|
<p class="text-gray-300">{{ serverConfig.address }}</p>
|
||||||
|
<div class="flex-grow" />
|
||||||
|
<span v-if="!serverConfig.id" class="material-icons" style="font-size: 1.1rem" @click="editServerAddress">edit</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-full h-px bg-white bg-opacity-10 my-2" />
|
||||||
|
<form @submit.prevent="submitAuth" class="pt-3">
|
||||||
|
<ui-text-input v-model="serverConfig.username" :disabled="processing" placeholder="username" class="w-full mb-2 text-lg" />
|
||||||
|
<ui-text-input v-model="password" type="password" :disabled="processing" placeholder="password" class="w-full mb-2 text-lg" />
|
||||||
|
|
||||||
|
<div class="flex items-center pt-2">
|
||||||
|
<ui-icon-btn v-if="serverConfig.id" small bg-color="error" icon="delete" @click="removeServerConfigClick" />
|
||||||
|
<div class="flex-grow" />
|
||||||
|
<ui-btn :disabled="processing || !networkConnected" type="submit" class="mt-1 h-10">{{ networkConnected ? 'Submit' : 'No Internet' }}</ui-btn>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-show="error" class="w-full rounded-lg bg-red-600 bg-opacity-10 border border-error border-opacity-50 py-3 px-2 flex items-center mt-4">
|
||||||
|
<span class="material-icons mr-2 text-error" style="font-size: 1.1rem">warning</span>
|
||||||
|
<p class="text-error">{{ error }}</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div :class="processing ? 'opacity-100' : 'opacity-0 pointer-events-none'" class="fixed w-full h-full top-0 left-0 bg-black bg-opacity-75 flex items-center justify-center z-30 transition-opacity duration-500">
|
||||||
|
<div>
|
||||||
|
<div class="absolute top-0 left-0 w-full p-6 flex items-center flex-col justify-center z-0 short:hidden">
|
||||||
|
<img src="/Logo.png" class="h-20 w-20 mb-2" />
|
||||||
|
</div>
|
||||||
|
<svg class="animate-spin w-16 h-16" viewBox="0 0 24 24">
|
||||||
|
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { Dialog } from '@capacitor/dialog'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
deviceData: null,
|
||||||
|
// serverConnectionConfigs: [
|
||||||
|
// {
|
||||||
|
// id: 'test1',
|
||||||
|
// name: 'http://192.168.0.1:3333 (root)',
|
||||||
|
// address: 'http://192.168.0.1:3333',
|
||||||
|
// username: 'root',
|
||||||
|
// token: 'asdf'
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// id: 'test2',
|
||||||
|
// name: 'https://someserver.com (user)',
|
||||||
|
// address: 'https://someserver.com',
|
||||||
|
// username: 'user',
|
||||||
|
// token: 'asdf'
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
loggedIn: false,
|
||||||
|
showAuth: false,
|
||||||
|
processing: false,
|
||||||
|
serverConfig: {
|
||||||
|
address: null,
|
||||||
|
username: null
|
||||||
|
},
|
||||||
|
password: null,
|
||||||
|
error: null,
|
||||||
|
showForm: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
networkConnected() {
|
||||||
|
return this.$store.state.networkConnected
|
||||||
|
},
|
||||||
|
serverConnectionConfigs() {
|
||||||
|
return this.deviceData ? this.deviceData.serverConnectionConfigs || [] : []
|
||||||
|
},
|
||||||
|
lastServerConnectionConfigId() {
|
||||||
|
return this.deviceData ? this.deviceData.lastServerConnectionConfigId : null
|
||||||
|
},
|
||||||
|
lastServerConnectionConfig() {
|
||||||
|
if (!this.lastServerConnectionConfigId || !this.serverConnectionConfigs.length) return null
|
||||||
|
return this.serverConnectionConfigs.find((s) => s.id == this.lastServerConnectionConfigId)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
showServerList() {
|
||||||
|
this.showForm = false
|
||||||
|
this.showAuth = false
|
||||||
|
this.error = null
|
||||||
|
this.serverConfig = {
|
||||||
|
address: null,
|
||||||
|
username: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async connectToServer(config) {
|
||||||
|
this.processing = true
|
||||||
|
this.serverConfig = {
|
||||||
|
...config
|
||||||
|
}
|
||||||
|
this.showForm = true
|
||||||
|
var success = await this.pingServerAddress(config.address)
|
||||||
|
if (!success) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.error = null
|
||||||
|
this.processing = false
|
||||||
|
var payload = await this.authenticateToken()
|
||||||
|
|
||||||
|
if (payload) {
|
||||||
|
this.setUserAndConnection(payload.user, payload.userDefaultLibraryId)
|
||||||
|
} else {
|
||||||
|
this.showAuth = true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async removeServerConfigClick() {
|
||||||
|
if (!this.serverConfig.id) return
|
||||||
|
|
||||||
|
const { value } = await Dialog.confirm({
|
||||||
|
title: 'Confirm',
|
||||||
|
message: `Remove this server config?`
|
||||||
|
})
|
||||||
|
if (value) {
|
||||||
|
this.processing = true
|
||||||
|
await this.$db.removeServerConnectionConfig(this.serverConfig.id)
|
||||||
|
this.deviceData.serverConnectionConfigs = this.deviceData.serverConnectionConfigs.filter((scc) => scc.id != this.serverConfig.id)
|
||||||
|
this.serverConfig = {
|
||||||
|
address: null,
|
||||||
|
username: null
|
||||||
|
}
|
||||||
|
this.password = null
|
||||||
|
this.processing = false
|
||||||
|
this.showAuth = false
|
||||||
|
this.showForm = !this.serverConnectionConfigs.length
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editServerConfig(serverConfig) {
|
||||||
|
this.serverConfig = {
|
||||||
|
...serverConfig
|
||||||
|
}
|
||||||
|
this.showForm = true
|
||||||
|
this.showAuth = true
|
||||||
|
console.log('Edit server config', serverConfig)
|
||||||
|
},
|
||||||
|
newServerConfigClick() {
|
||||||
|
this.showForm = true
|
||||||
|
this.showAuth = false
|
||||||
|
},
|
||||||
|
editServerAddress() {
|
||||||
|
this.error = null
|
||||||
|
this.showAuth = false
|
||||||
|
},
|
||||||
|
validateServerUrl(url) {
|
||||||
|
try {
|
||||||
|
var urlObject = new URL(url)
|
||||||
|
var address = `${urlObject.protocol}//${urlObject.hostname}`
|
||||||
|
if (urlObject.port) address += ':' + urlObject.port
|
||||||
|
return address
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Invalid URL', error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pingServerAddress(address) {
|
||||||
|
return this.$axios
|
||||||
|
.$get(`${address}/ping`, { timeout: 1000 })
|
||||||
|
.then((data) => data.success)
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Server check failed', error)
|
||||||
|
this.error = 'Failed to ping server'
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
requestServerLogin() {
|
||||||
|
return this.$axios
|
||||||
|
.$post(`${this.serverConfig.address}/login`, { username: this.serverConfig.username, password: this.password })
|
||||||
|
.then((data) => {
|
||||||
|
if (!data.user) {
|
||||||
|
console.error(data.error)
|
||||||
|
this.error = data.error || 'Unknown Error'
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('Server auth failed', error)
|
||||||
|
var errorMsg = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
||||||
|
this.error = errorMsg
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
async submit() {
|
||||||
|
if (!this.networkConnected) return
|
||||||
|
if (!this.serverConfig.address) return
|
||||||
|
if (!this.serverConfig.address.startsWith('http')) {
|
||||||
|
this.serverConfig.address = 'http://' + this.serverConfig.address
|
||||||
|
}
|
||||||
|
var validServerAddress = this.validateServerUrl(this.serverConfig.address)
|
||||||
|
if (!validServerAddress) {
|
||||||
|
this.error = 'Invalid server address'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.serverConfig.address = validServerAddress
|
||||||
|
this.processing = true
|
||||||
|
this.error = null
|
||||||
|
|
||||||
|
var success = await this.pingServerAddress(this.serverConfig.address)
|
||||||
|
this.processing = false
|
||||||
|
if (success) this.showAuth = true
|
||||||
|
},
|
||||||
|
async submitAuth() {
|
||||||
|
if (!this.networkConnected) return
|
||||||
|
if (!this.serverConfig.username) {
|
||||||
|
this.error = 'Invalid username'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.error = null
|
||||||
|
this.processing = true
|
||||||
|
|
||||||
|
var payload = await this.requestServerLogin()
|
||||||
|
this.processing = false
|
||||||
|
if (payload) {
|
||||||
|
this.setUserAndConnection(payload.user, payload.userDefaultLibraryId)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async setUserAndConnection(user, userDefaultLibraryId) {
|
||||||
|
if (user) {
|
||||||
|
console.log('Successfully logged in', JSON.stringify(user))
|
||||||
|
|
||||||
|
if (userDefaultLibraryId) {
|
||||||
|
this.$store.commit('libraries/setCurrentLibrary', userDefaultLibraryId)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.serverConfig.token = user.token
|
||||||
|
|
||||||
|
var serverConnectionConfig = await this.$db.setServerConnectionConfig(this.serverConfig)
|
||||||
|
|
||||||
|
this.$store.commit('user/setUser', user)
|
||||||
|
this.$store.commit('user/setServerConnectionConfig', serverConnectionConfig)
|
||||||
|
|
||||||
|
this.$socket.connect(this.serverConfig.address, this.serverConfig.token)
|
||||||
|
this.$router.replace('/bookshelf')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async authenticateToken() {
|
||||||
|
if (!this.networkConnected) return
|
||||||
|
if (!this.serverConfig.token) {
|
||||||
|
this.error = 'No token'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.error = null
|
||||||
|
this.processing = true
|
||||||
|
var authRes = await this.$axios.$post(`${this.serverConfig.address}/api/authorize`, null, { headers: { Authorization: `Bearer ${this.serverConfig.token}` } }).catch((error) => {
|
||||||
|
console.error('[Server] Server auth failed', error)
|
||||||
|
var errorMsg = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
||||||
|
this.error = errorMsg
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
this.processing = false
|
||||||
|
return authRes
|
||||||
|
},
|
||||||
|
async init() {
|
||||||
|
this.deviceData = await this.$db.getDeviceData()
|
||||||
|
|
||||||
|
if (this.lastServerConnectionConfig) {
|
||||||
|
this.connectToServer(this.lastServerConnectionConfig)
|
||||||
|
} else {
|
||||||
|
this.showForm = !this.serverConnectionConfigs.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.init()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -1,254 +0,0 @@
|
||||||
<template>
|
|
||||||
<modals-modal v-model="show" width="100%" height="100%">
|
|
||||||
<div class="w-full h-full overflow-hidden absolute top-0 left-0 flex items-center justify-center" @click="show = false">
|
|
||||||
<p class="absolute top-6 left-2 text-2xl">Downloads</p>
|
|
||||||
|
|
||||||
<div class="absolute top-16 left-0 right-0 w-full px-2 py-1" :class="hasStoragePermission ? '' : 'text-error'">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="material-icons" @click="changeDownloadFolderClick">{{ hasStoragePermission ? 'folder' : 'error' }}</span>
|
|
||||||
<p v-if="hasStoragePermission" class="text-sm px-4" @click="changeDownloadFolderClick">{{ downloadFolderSimplePath || 'No Download Folder Selected' }}</p>
|
|
||||||
<p v-else class="text-sm px-4" @click="changeDownloadFolderClick">No Storage Permissions. Click here</p>
|
|
||||||
</div>
|
|
||||||
<!-- <p v-if="hasStoragePermission" class="text-xs text-gray-400 break-all max-w-full">{{ downloadFolderUri }}</p> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="totalSize" class="absolute bottom-0 left-0 right-0 w-full py-3 text-center">
|
|
||||||
<p class="text-sm text-center text-gray-300">Total: {{ $bytesPretty(totalSize) }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="downloadFolder && hasStoragePermission" class="w-full relative mt-10" @click.stop>
|
|
||||||
<div class="w-full h-10 relative">
|
|
||||||
<div class="absolute top-px left-0 z-10 w-full h-full flex">
|
|
||||||
<div class="flex-grow h-full bg-primary rounded-t-md mr-px" @click="showingDownloads = true">
|
|
||||||
<div class="flex items-center justify-center rounded-t-md border-t border-l border-r border-white border-opacity-20 h-full" :class="showingDownloads ? 'text-gray-100' : 'border-b bg-black bg-opacity-20 text-gray-400'">
|
|
||||||
<p>Downloads</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow h-full bg-primary rounded-t-md ml-px" @click="showingDownloads = false">
|
|
||||||
<div class="flex items-center justify-center h-full rounded-t-md border-t border-l border-r border-white border-opacity-20" :class="!showingDownloads ? 'text-gray-100' : 'border-b bg-black bg-opacity-20 text-gray-400'">
|
|
||||||
<p>Files</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="list-content-body relative w-full overflow-x-hidden overflow-y-auto bg-primary rounded-b-lg border border-white border-opacity-20" style="max-height: 70vh; height: 70vh">
|
|
||||||
<template v-if="showingDownloads">
|
|
||||||
<div v-if="!totalDownloads" class="flex items-center justify-center h-40">
|
|
||||||
<p>No Downloads</p>
|
|
||||||
</div>
|
|
||||||
<ul v-else class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
|
||||||
<template v-for="download in downloadsDownloading">
|
|
||||||
<li :key="download.id" class="text-gray-400 select-none relative px-4 py-5 border-b border-white border-opacity-10 bg-black bg-opacity-10">
|
|
||||||
<div class="flex items-center justify-center">
|
|
||||||
<div class="w-3/4">
|
|
||||||
<span class="text-xs">({{ downloadingProgress[download.id] || 0 }}%) {{ download.isPreparing ? 'Preparing' : 'Downloading' }}...</span>
|
|
||||||
<p class="font-normal truncate text-sm">{{ download.audiobook.book.title }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow" />
|
|
||||||
|
|
||||||
<div class="shadow-sm text-white flex items-center justify-center rounded-full animate-spin">
|
|
||||||
<span class="material-icons">refresh</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
<template v-for="download in downloadsReady">
|
|
||||||
<li :key="download.id" class="text-gray-50 select-none relative pr-4 pl-2 py-5 border-b border-white border-opacity-10" @click="jumpToAudiobook(download)">
|
|
||||||
<modals-downloads-download-item :download="download" @play="playDownload" @delete="clickDeleteDownload" />
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
</ul>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<div class="w-full h-full">
|
|
||||||
<div class="w-full flex justify-around py-4 px-2">
|
|
||||||
<ui-btn small @click="searchFolder">Re-Scan</ui-btn>
|
|
||||||
<ui-btn small @click="changeDownloadFolderClick">Change Folder</ui-btn>
|
|
||||||
<ui-btn small color="error" @click="resetFolder">Reset</ui-btn>
|
|
||||||
</div>
|
|
||||||
<p v-if="isScanning" class="text-center my-8">Scanning Folder..</p>
|
|
||||||
<p v-else-if="!mediaScanResults" class="text-center my-8">No Files Found</p>
|
|
||||||
<template v-else>
|
|
||||||
<template v-for="mediaFolder in mediaScanResults.folders">
|
|
||||||
<div :key="mediaFolder.uri" class="w-full px-2 py-2">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="material-icons text-base text-white text-opacity-50">folder</span>
|
|
||||||
<p class="ml-1 py-0.5">{{ mediaFolder.name }}</p>
|
|
||||||
</div>
|
|
||||||
<div v-for="mediaFile in mediaFolder.files" :key="mediaFile.uri" class="ml-3 flex items-center">
|
|
||||||
<span class="material-icons text-base text-white text-opacity-50">{{ mediaFile.isAudio ? 'music_note' : 'image' }}</span>
|
|
||||||
<p class="ml-1 py-0.5">{{ mediaFile.name }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-for="mediaFile in mediaScanResults.files">
|
|
||||||
<div :key="mediaFile.uri" class="w-full px-2 py-2">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<span class="material-icons text-base text-white text-opacity-50">{{ mediaFile.isAudio ? 'music_note' : 'image' }}</span>
|
|
||||||
<p class="ml-1 py-0.5">{{ mediaFile.name }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-else class="list-content-body relative w-full overflow-x-hidden overflow-y-auto bg-primary rounded-b-lg border border-white border-opacity-20 py-8 px-4" @click.stop>
|
|
||||||
<ui-btn class="w-full" color="info" @click="changeDownloadFolderClick">Select Folder</ui-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</modals-modal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { Dialog } from '@capacitor/dialog'
|
|
||||||
import AudioDownloader from '@/plugins/audio-downloader'
|
|
||||||
import StorageManager from '@/plugins/storage-manager'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
downloadingProgress: {},
|
|
||||||
totalSize: 0,
|
|
||||||
showingDownloads: true,
|
|
||||||
isScanning: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
async show(newValue) {
|
|
||||||
if (newValue) {
|
|
||||||
await this.$localStore.getDownloadFolder()
|
|
||||||
this.setTotalSize()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
show: {
|
|
||||||
get() {
|
|
||||||
return this.$store.state.downloads.showModal
|
|
||||||
},
|
|
||||||
set(val) {
|
|
||||||
this.$store.commit('downloads/setShowModal', val)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hasStoragePermission() {
|
|
||||||
return this.$store.state.hasStoragePermission
|
|
||||||
},
|
|
||||||
downloadFolder() {
|
|
||||||
return this.$store.state.downloadFolder
|
|
||||||
},
|
|
||||||
downloadFolderSimplePath() {
|
|
||||||
return this.downloadFolder ? this.downloadFolder.simplePath : null
|
|
||||||
},
|
|
||||||
downloadFolderUri() {
|
|
||||||
return this.downloadFolder ? this.downloadFolder.uri : null
|
|
||||||
},
|
|
||||||
totalDownloads() {
|
|
||||||
return this.downloadsReady.length + this.downloadsDownloading.length
|
|
||||||
},
|
|
||||||
downloadsDownloading() {
|
|
||||||
return this.downloads.filter((d) => d.isDownloading || d.isPreparing)
|
|
||||||
},
|
|
||||||
downloadsReady() {
|
|
||||||
return this.downloads.filter((d) => !d.isDownloading && !d.isPreparing)
|
|
||||||
},
|
|
||||||
downloads() {
|
|
||||||
return this.$store.state.downloads.downloads
|
|
||||||
},
|
|
||||||
mediaScanResults() {
|
|
||||||
return this.$store.state.downloads.mediaScanResults
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
setTotalSize() {
|
|
||||||
var totalSize = 0
|
|
||||||
this.downloadsReady.forEach((dl) => {
|
|
||||||
totalSize += dl.size && !isNaN(dl.size) ? Number(dl.size) : 0
|
|
||||||
})
|
|
||||||
this.totalSize = totalSize
|
|
||||||
},
|
|
||||||
async changeDownloadFolderClick() {
|
|
||||||
if (!this.hasStoragePermission) {
|
|
||||||
console.log('Requesting Storage Permission')
|
|
||||||
StorageManager.requestStoragePermission()
|
|
||||||
} else {
|
|
||||||
var folderObj = await StorageManager.selectFolder()
|
|
||||||
if (folderObj.error) {
|
|
||||||
return this.$toast.error(`Error: ${folderObj.error || 'Unknown Error'}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
var permissionsGood = await StorageManager.checkFolderPermissions({ folderUrl: folderObj.uri })
|
|
||||||
console.log('Storage Permission check folder ' + permissionsGood)
|
|
||||||
|
|
||||||
if (!permissionsGood) {
|
|
||||||
this.$toast.error('Folder permissions failed')
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
this.$toast.success('Folder permission success')
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.$localStore.setDownloadFolder(folderObj)
|
|
||||||
|
|
||||||
this.searchFolder()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async searchFolder() {
|
|
||||||
this.isScanning = true
|
|
||||||
var response = await StorageManager.searchFolder({ folderUrl: this.downloadFolderUri })
|
|
||||||
var searchResults = response
|
|
||||||
searchResults.folders = JSON.parse(searchResults.folders)
|
|
||||||
searchResults.files = JSON.parse(searchResults.files)
|
|
||||||
|
|
||||||
if (searchResults.folders.length) {
|
|
||||||
console.log('Search results folders length', searchResults.folders.length)
|
|
||||||
|
|
||||||
searchResults.folders = searchResults.folders.map((sr) => {
|
|
||||||
if (sr.files) {
|
|
||||||
sr.files = JSON.parse(sr.files)
|
|
||||||
}
|
|
||||||
return sr
|
|
||||||
})
|
|
||||||
this.$store.commit('downloads/setMediaScanResults', searchResults)
|
|
||||||
} else {
|
|
||||||
this.$toast.warning('No audio or image files found')
|
|
||||||
}
|
|
||||||
this.isScanning = false
|
|
||||||
},
|
|
||||||
async resetFolder() {
|
|
||||||
await this.$localStore.setDownloadFolder(null)
|
|
||||||
this.$store.commit('downloads/setMediaScanResults', {})
|
|
||||||
this.$toast.info('Unlinked Folder')
|
|
||||||
},
|
|
||||||
updateDownloadProgress({ audiobookId, progress }) {
|
|
||||||
this.$set(this.downloadingProgress, audiobookId, progress)
|
|
||||||
},
|
|
||||||
jumpToAudiobook(download) {
|
|
||||||
this.show = false
|
|
||||||
this.$router.push(`/audiobook/${download.id}`)
|
|
||||||
},
|
|
||||||
async clickDeleteDownload(download) {
|
|
||||||
const { value } = await Dialog.confirm({
|
|
||||||
title: 'Confirm',
|
|
||||||
message: 'Delete this download?'
|
|
||||||
})
|
|
||||||
if (value) {
|
|
||||||
this.$emit('deleteDownload', download)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
playDownload(download) {
|
|
||||||
this.$store.commit('setPlayOnLoad', true)
|
|
||||||
this.$store.commit('setPlayingDownload', download)
|
|
||||||
this.show = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.list-content-body {
|
|
||||||
max-height: calc(75% - 40px);
|
|
||||||
}
|
|
||||||
</style>
|
|
|
@ -55,7 +55,6 @@ export default {
|
||||||
this.show = false
|
this.show = false
|
||||||
await this.$store.dispatch('libraries/fetch', lib.id)
|
await this.$store.dispatch('libraries/fetch', lib.id)
|
||||||
this.$eventBus.$emit('library-changed', lib.id)
|
this.$eventBus.$emit('library-changed', lib.id)
|
||||||
this.$localStore.setCurrentLibrary(lib)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
|
|
|
@ -53,7 +53,7 @@ export default {
|
||||||
this.$emit('select', folder)
|
this.$emit('select', folder)
|
||||||
},
|
},
|
||||||
async init() {
|
async init() {
|
||||||
var localFolders = (await this.$db.loadFolders()) || []
|
var localFolders = (await this.$db.getLocalFolders()) || []
|
||||||
this.localFolders = localFolders.filter((lf) => lf.mediaType == this.mediaType)
|
this.localFolders = localFolders.filter((lf) => lf.mediaType == this.mediaType)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -40,6 +40,9 @@ export default {
|
||||||
networkConnected() {
|
networkConnected() {
|
||||||
return this.$store.state.networkConnected
|
return this.$store.state.networkConnected
|
||||||
},
|
},
|
||||||
|
user() {
|
||||||
|
return this.$store.state.user.user
|
||||||
|
},
|
||||||
currentLibraryId() {
|
currentLibraryId() {
|
||||||
return this.$store.state.libraries.currentLibraryId
|
return this.$store.state.libraries.currentLibraryId
|
||||||
},
|
},
|
||||||
|
@ -48,24 +51,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async connected(isConnected) {
|
|
||||||
if (isConnected) {
|
|
||||||
console.log('[Default] Connected socket sync user ab data')
|
|
||||||
// this.$store.dispatch('user/syncUserAudiobookData')
|
|
||||||
|
|
||||||
this.initSocketListeners()
|
|
||||||
|
|
||||||
// Load libraries
|
|
||||||
await this.$store.dispatch('libraries/load')
|
|
||||||
this.$eventBus.$emit('library-changed')
|
|
||||||
this.$store.dispatch('libraries/fetch', this.currentLibraryId)
|
|
||||||
} else {
|
|
||||||
this.removeSocketListeners()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
socketConnectionFailed(err) {
|
|
||||||
this.$toast.error('Socket connection error: ' + err.message)
|
|
||||||
},
|
|
||||||
currentUserAudiobookUpdate({ id, data }) {
|
currentUserAudiobookUpdate({ id, data }) {
|
||||||
if (data) {
|
if (data) {
|
||||||
console.log(`Current User Audiobook Updated ${id} ${JSON.stringify(data)}`)
|
console.log(`Current User Audiobook Updated ${id} ${JSON.stringify(data)}`)
|
||||||
|
@ -218,41 +203,41 @@ export default {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async syncDownloads(downloads, downloadFolder) {
|
// async syncDownloads(downloads, downloadFolder) {
|
||||||
console.log('Syncing downloads ' + downloads.length)
|
// console.log('Syncing downloads ' + downloads.length)
|
||||||
var mediaScanResults = await this.searchFolder(downloadFolder)
|
// var mediaScanResults = await this.searchFolder(downloadFolder)
|
||||||
|
|
||||||
this.$store.commit('downloads/setMediaScanResults', mediaScanResults)
|
// this.$store.commit('downloads/setMediaScanResults', mediaScanResults)
|
||||||
|
|
||||||
// Filter out media folders without any audio files
|
// // Filter out media folders without any audio files
|
||||||
var mediaFolders = mediaScanResults.folders.filter((sr) => {
|
// var mediaFolders = mediaScanResults.folders.filter((sr) => {
|
||||||
if (!sr.files) return false
|
// if (!sr.files) return false
|
||||||
var audioFiles = sr.files.filter((mf) => !!mf.isAudio)
|
// var audioFiles = sr.files.filter((mf) => !!mf.isAudio)
|
||||||
return audioFiles.length
|
// return audioFiles.length
|
||||||
})
|
// })
|
||||||
|
|
||||||
downloads.forEach((download) => {
|
// downloads.forEach((download) => {
|
||||||
var mediaFolder = mediaFolders.find((mf) => mf.name === download.folderName)
|
// var mediaFolder = mediaFolders.find((mf) => mf.name === download.folderName)
|
||||||
if (mediaFolder) {
|
// if (mediaFolder) {
|
||||||
console.log('Found download ' + download.folderName)
|
// console.log('Found download ' + download.folderName)
|
||||||
if (download.isMissing) {
|
// if (download.isMissing) {
|
||||||
download.isMissing = false
|
// download.isMissing = false
|
||||||
this.$store.commit('downloads/addUpdateDownload', download)
|
// this.$store.commit('downloads/addUpdateDownload', download)
|
||||||
}
|
// }
|
||||||
} else {
|
// } else {
|
||||||
console.error('Download not found ' + download.folderName)
|
// console.error('Download not found ' + download.folderName)
|
||||||
if (!download.isMissing) {
|
// if (!download.isMissing) {
|
||||||
download.isMissing = true
|
// download.isMissing = true
|
||||||
this.$store.commit('downloads/addUpdateDownload', download)
|
// this.$store.commit('downloads/addUpdateDownload', download)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
|
|
||||||
// Match media scanned folders with books from server
|
// // Match media scanned folders with books from server
|
||||||
if (this.isSocketConnected) {
|
// if (this.isSocketConnected) {
|
||||||
await this.$store.dispatch('downloads/linkOrphanDownloads')
|
// await this.$store.dispatch('downloads/linkOrphanDownloads')
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
onItemDownloadUpdate(data) {
|
onItemDownloadUpdate(data) {
|
||||||
console.log('ON ITEM DOWNLOAD UPDATE', JSON.stringify(data))
|
console.log('ON ITEM DOWNLOAD UPDATE', JSON.stringify(data))
|
||||||
},
|
},
|
||||||
|
@ -274,22 +259,18 @@ export default {
|
||||||
// this.onDownloadProgress(data)
|
// this.onDownloadProgress(data)
|
||||||
// })
|
// })
|
||||||
|
|
||||||
var downloads = await this.$store.dispatch('downloads/loadFromStorage')
|
// var downloads = await this.$store.dispatch('downloads/loadFromStorage')
|
||||||
var downloadFolder = await this.$localStore.getDownloadFolder()
|
// var downloadFolder = await this.$localStore.getDownloadFolder()
|
||||||
|
// this.$eventBus.$emit('downloads-loaded')
|
||||||
|
|
||||||
if (downloadFolder) {
|
// var checkPermission = await StorageManager.checkStoragePermission()
|
||||||
// await this.syncDownloads(downloads, downloadFolder)
|
// console.log('Storage Permission is' + checkPermission.value)
|
||||||
}
|
// if (!checkPermission.value) {
|
||||||
this.$eventBus.$emit('downloads-loaded')
|
// console.log('Will require permissions')
|
||||||
|
// } else {
|
||||||
var checkPermission = await StorageManager.checkStoragePermission()
|
// console.log('Has Storage Permission')
|
||||||
console.log('Storage Permission is' + checkPermission.value)
|
// this.$store.commit('setHasStoragePermission', true)
|
||||||
if (!checkPermission.value) {
|
// }
|
||||||
console.log('Will require permissions')
|
|
||||||
} else {
|
|
||||||
console.log('Has Storage Permission')
|
|
||||||
this.$store.commit('setHasStoragePermission', true)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
async loadSavedSettings() {
|
async loadSavedSettings() {
|
||||||
var userSavedServerSettings = await this.$localStore.getServerSettings()
|
var userSavedServerSettings = await this.$localStore.getServerSettings()
|
||||||
|
@ -305,42 +286,44 @@ export default {
|
||||||
console.log('Loading offline user audiobook data')
|
console.log('Loading offline user audiobook data')
|
||||||
await this.$store.dispatch('user/loadOfflineUserAudiobookData')
|
await this.$store.dispatch('user/loadOfflineUserAudiobookData')
|
||||||
},
|
},
|
||||||
showErrorToast(message) {
|
|
||||||
this.$toast.error(message)
|
|
||||||
},
|
|
||||||
showSuccessToast(message) {
|
|
||||||
this.$toast.success(message)
|
|
||||||
},
|
|
||||||
async attemptConnection() {
|
async attemptConnection() {
|
||||||
if (!this.$server) return
|
|
||||||
if (!this.networkConnected) {
|
if (!this.networkConnected) {
|
||||||
console.warn('No network connection')
|
console.warn('No network connection')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var localServerUrl = await this.$localStore.getServerUrl()
|
var deviceData = await this.$db.getDeviceData()
|
||||||
var localUserToken = await this.$localStore.getToken()
|
var serverConfig = null
|
||||||
if (localServerUrl) {
|
if (deviceData && deviceData.lastServerConnectionConfigId && deviceData.serverConnectionConfigs.length) {
|
||||||
// Server and Token are stored
|
serverConfig = deviceData.serverConnectionConfigs.find((scc) => scc.id == deviceData.lastServerConnectionConfigId)
|
||||||
if (localUserToken) {
|
|
||||||
var isSocketAlreadyEstablished = this.$server.socket
|
|
||||||
var success = await this.$server.connect(localServerUrl, localUserToken)
|
|
||||||
if (!success && !this.$server.url) {
|
|
||||||
// Bad URL
|
|
||||||
} else if (!success) {
|
|
||||||
// Failed to connect
|
|
||||||
} else if (isSocketAlreadyEstablished) {
|
|
||||||
// No need to wait for connect event
|
|
||||||
}
|
}
|
||||||
|
if (!serverConfig) {
|
||||||
|
// No last server config set
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var authRes = await this.$axios.$post(`${serverConfig.address}/api/authorize`, null, { headers: { Authorization: `Bearer ${serverConfig.token}` } }).catch((error) => {
|
||||||
|
console.error('[Server] Server auth failed', error)
|
||||||
|
var errorMsg = error.response ? error.response.data || 'Unknown Error' : 'Unknown Error'
|
||||||
|
this.error = errorMsg
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if (!authRes) return
|
||||||
|
|
||||||
|
const { user, userDefaultLibraryId } = authRes
|
||||||
|
if (userDefaultLibraryId) {
|
||||||
|
this.$store.commit('libraries/setCurrentLibrary', userDefaultLibraryId)
|
||||||
}
|
}
|
||||||
|
var serverConnectionConfig = await this.$db.setServerConnectionConfig(serverConfig)
|
||||||
|
|
||||||
|
this.$store.commit('user/setUser', user)
|
||||||
|
this.$store.commit('user/setServerConnectionConfig', serverConnectionConfig)
|
||||||
|
|
||||||
|
this.$socket.connect(serverConnectionConfig.address, serverConnectionConfig.token)
|
||||||
|
|
||||||
|
console.log('Successful connection on last saved connection config', JSON.stringify(serverConnectionConfig))
|
||||||
|
await this.initLibraries()
|
||||||
},
|
},
|
||||||
// audiobookAdded(audiobook) {
|
|
||||||
// this.$store.commit('libraries/updateFilterDataWithAudiobook', audiobook)
|
|
||||||
// },
|
|
||||||
// audiobookUpdated(audiobook) {
|
|
||||||
// this.$store.commit('libraries/updateFilterDataWithAudiobook', audiobook)
|
|
||||||
// },
|
|
||||||
itemRemoved(libraryItem) {
|
itemRemoved(libraryItem) {
|
||||||
if (this.$route.name.startsWith('item')) {
|
if (this.$route.name.startsWith('item')) {
|
||||||
if (this.$route.params.id === libraryItem.id) {
|
if (this.$route.params.id === libraryItem.id) {
|
||||||
|
@ -348,60 +331,45 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// audiobooksAdded(audiobooks) {
|
|
||||||
// audiobooks.forEach((ab) => {
|
|
||||||
// this.audiobookAdded(ab)
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
// audiobooksUpdated(audiobooks) {
|
|
||||||
// audiobooks.forEach((ab) => {
|
|
||||||
// this.audiobookUpdated(ab)
|
|
||||||
// })
|
|
||||||
// },
|
|
||||||
userLoggedOut() {
|
userLoggedOut() {
|
||||||
// Only cancels stream if streamining not playing downloaded
|
// Only cancels stream if streamining not playing downloaded
|
||||||
this.$eventBus.$emit('close-stream')
|
this.$eventBus.$emit('close-stream')
|
||||||
},
|
},
|
||||||
initSocketListeners() {
|
socketConnectionUpdate(isConnected) {
|
||||||
if (this.$server.socket) {
|
console.log('Socket connection update', isConnected)
|
||||||
// this.$server.socket.on('audiobook_updated', this.audiobookUpdated)
|
|
||||||
// this.$server.socket.on('audiobook_added', this.audiobookAdded)
|
|
||||||
this.$server.socket.on('item_removed', this.itemRemoved)
|
|
||||||
// this.$server.socket.on('audiobooks_updated', this.audiobooksUpdated)
|
|
||||||
// this.$server.socket.on('audiobooks_added', this.audiobooksAdded)
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
removeSocketListeners() {
|
socketConnectionFailed(err) {
|
||||||
if (this.$server.socket) {
|
this.$toast.error('Socket connection error: ' + err.message)
|
||||||
// this.$server.socket.off('audiobook_updated', this.audiobookUpdated)
|
},
|
||||||
// this.$server.socket.off('audiobook_added', this.audiobookAdded)
|
socketInit(data) {},
|
||||||
this.$server.socket.off('item_removed', this.itemRemoved)
|
async initLibraries() {
|
||||||
// this.$server.socket.off('audiobooks_updated', this.audiobooksUpdated)
|
await this.$store.dispatch('libraries/load')
|
||||||
// this.$server.socket.off('audiobooks_added', this.audiobooksAdded)
|
this.$eventBus.$emit('library-changed')
|
||||||
}
|
this.$store.dispatch('libraries/fetch', this.currentLibraryId)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
if (!this.$server) return console.error('No Server')
|
// this.$server.on('logout', this.userLoggedOut)
|
||||||
// console.log(`Default Mounted set SOCKET listeners ${this.$server.connected}`)
|
// this.$server.on('connected', this.connected)
|
||||||
|
// this.$server.on('connectionFailed', this.socketConnectionFailed)
|
||||||
|
// this.$server.on('initialStream', this.initialStream)
|
||||||
|
// this.$server.on('currentUserAudiobookUpdate', this.currentUserAudiobookUpdate)
|
||||||
|
// this.$server.on('show_error_toast', this.showErrorToast)
|
||||||
|
// this.$server.on('show_success_toast', this.showSuccessToast)
|
||||||
|
|
||||||
if (this.$server.connected) {
|
this.$socket.on('connection-update', this.socketConnectionUpdate)
|
||||||
console.log('Syncing on default mount')
|
this.$socket.on('initialized', this.socketInit)
|
||||||
this.connected(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$server.on('logout', this.userLoggedOut)
|
|
||||||
this.$server.on('connected', this.connected)
|
|
||||||
this.$server.on('connectionFailed', this.socketConnectionFailed)
|
|
||||||
this.$server.on('initialStream', this.initialStream)
|
|
||||||
this.$server.on('currentUserAudiobookUpdate', this.currentUserAudiobookUpdate)
|
|
||||||
this.$server.on('show_error_toast', this.showErrorToast)
|
|
||||||
this.$server.on('show_success_toast', this.showSuccessToast)
|
|
||||||
|
|
||||||
if (this.$store.state.isFirstLoad) {
|
if (this.$store.state.isFirstLoad) {
|
||||||
this.$store.commit('setIsFirstLoad', false)
|
this.$store.commit('setIsFirstLoad', false)
|
||||||
await this.$store.dispatch('setupNetworkListener')
|
await this.$store.dispatch('setupNetworkListener')
|
||||||
this.attemptConnection()
|
|
||||||
|
if (this.$store.state.user.serverConnectionConfig) {
|
||||||
|
await this.initLibraries()
|
||||||
|
} else {
|
||||||
|
await this.attemptConnection()
|
||||||
|
}
|
||||||
|
|
||||||
this.checkForUpdate()
|
this.checkForUpdate()
|
||||||
this.loadSavedSettings()
|
this.loadSavedSettings()
|
||||||
this.initMediaStore()
|
this.initMediaStore()
|
||||||
|
@ -412,13 +380,9 @@ export default {
|
||||||
console.error('No Server beforeDestroy')
|
console.error('No Server beforeDestroy')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.removeSocketListeners()
|
|
||||||
this.$server.off('logout', this.userLoggedOut)
|
this.$socket.off('connection-update', this.socketConnectionUpdate)
|
||||||
this.$server.off('connected', this.connected)
|
this.$socket.off('initialized', this.socketInit)
|
||||||
this.$server.off('connectionFailed', this.socketConnectionFailed)
|
|
||||||
this.$server.off('initialStream', this.initialStream)
|
|
||||||
this.$server.off('show_error_toast', this.showErrorToast)
|
|
||||||
this.$server.off('show_success_toast', this.showSuccessToast)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -1,442 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="w-full h-full px-3 py-4 overflow-y-auto">
|
|
||||||
<div class="flex">
|
|
||||||
<div class="w-32">
|
|
||||||
<div class="relative">
|
|
||||||
<covers-book-cover :audiobook="audiobook" :download-cover="downloadedCover" :width="128" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
|
||||||
<div class="absolute bottom-0 left-0 h-1.5 bg-yellow-400 shadow-sm z-10" :style="{ width: 128 * progressPercent + 'px' }"></div>
|
|
||||||
</div>
|
|
||||||
<div class="flex my-4">
|
|
||||||
<p v-if="numTracks" class="text-sm">{{ numTracks }} Tracks</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow px-3">
|
|
||||||
<h1 class="text-lg">{{ title }}</h1>
|
|
||||||
<h3 v-if="series" class="font-book text-gray-300 text-lg leading-7">{{ seriesText }}</h3>
|
|
||||||
<p class="text-sm text-gray-400">by {{ author }}</p>
|
|
||||||
<p v-if="numTracks" class="text-gray-300 text-sm my-1">
|
|
||||||
{{ $elapsedPretty(duration) }}
|
|
||||||
<span class="px-4">{{ $bytesPretty(size) }}</span>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div v-if="progressPercent > 0" class="px-4 py-2 bg-primary text-sm font-semibold rounded-md text-gray-200 mt-4 relative" :class="resettingProgress ? 'opacity-25' : ''">
|
|
||||||
<p class="leading-6">Your Progress: {{ Math.round(progressPercent * 100) }}%</p>
|
|
||||||
<p v-if="progressPercent < 1" class="text-gray-400 text-xs">{{ $elapsedPretty(userTimeRemaining) }} remaining</p>
|
|
||||||
<div v-if="!resettingProgress" class="absolute -top-1.5 -right-1.5 p-1 w-5 h-5 rounded-full bg-bg hover:bg-error border border-primary flex items-center justify-center cursor-pointer" @click.stop="clearProgressClick">
|
|
||||||
<span class="material-icons text-sm">close</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="(isConnected && (showPlay || showRead)) || isDownloadPlayable" class="flex mt-4 -mr-2">
|
|
||||||
<ui-btn v-if="showPlay" color="success" :disabled="isPlaying" class="flex items-center justify-center flex-grow mr-2" :padding-x="4" @click="playClick">
|
|
||||||
<span v-show="!isPlaying" class="material-icons">play_arrow</span>
|
|
||||||
<span class="px-1 text-sm">{{ isPlaying ? (isStreaming ? 'Streaming' : 'Playing') : isDownloadPlayable ? 'Play local' : 'Play stream' }}</span>
|
|
||||||
</ui-btn>
|
|
||||||
<ui-btn v-if="showRead && isConnected" color="info" class="flex items-center justify-center mr-2" :class="showPlay ? '' : 'flex-grow'" :padding-x="2" @click="readBook">
|
|
||||||
<span class="material-icons">auto_stories</span>
|
|
||||||
<span v-if="!showPlay" class="px-2 text-base">Read {{ ebookFormat }}</span>
|
|
||||||
</ui-btn>
|
|
||||||
<ui-btn v-if="isConnected && showPlay && !isIos" color="primary" class="flex items-center justify-center" :padding-x="2" @click="downloadClick">
|
|
||||||
<span class="material-icons" :class="downloadObj ? 'animate-pulse' : ''">{{ downloadObj ? (isDownloading || isDownloadPreparing ? 'downloading' : 'download_done') : 'download' }}</span>
|
|
||||||
</ui-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="w-full py-4">
|
|
||||||
<p>{{ description }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import Path from 'path'
|
|
||||||
import { Dialog } from '@capacitor/dialog'
|
|
||||||
import AudioDownloader from '@/plugins/audio-downloader'
|
|
||||||
import StorageManager from '@/plugins/storage-manager'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
async asyncData({ store, params, redirect, app }) {
|
|
||||||
var audiobookId = params.id
|
|
||||||
var audiobook = null
|
|
||||||
|
|
||||||
if (app.$server.connected) {
|
|
||||||
audiobook = await app.$axios.$get(`/api/books/${audiobookId}`).catch((error) => {
|
|
||||||
console.error('Failed', error)
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
var download = store.getters['downloads/getDownload'](audiobookId)
|
|
||||||
if (download) {
|
|
||||||
audiobook = download.audiobook
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!audiobook) {
|
|
||||||
console.error('No audiobook...', params.id)
|
|
||||||
return redirect('/')
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
audiobook
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
resettingProgress: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
isIos() {
|
|
||||||
return this.$platform === 'ios'
|
|
||||||
},
|
|
||||||
isConnected() {
|
|
||||||
return this.$store.state.socketConnected
|
|
||||||
},
|
|
||||||
bookCoverAspectRatio() {
|
|
||||||
return this.$store.getters['getBookCoverAspectRatio']
|
|
||||||
},
|
|
||||||
audiobookId() {
|
|
||||||
return this.audiobook.id
|
|
||||||
},
|
|
||||||
book() {
|
|
||||||
return this.audiobook.book || {}
|
|
||||||
},
|
|
||||||
title() {
|
|
||||||
return this.book.title
|
|
||||||
},
|
|
||||||
author() {
|
|
||||||
return this.book.author || 'Unknown'
|
|
||||||
},
|
|
||||||
description() {
|
|
||||||
return this.book.description || ''
|
|
||||||
},
|
|
||||||
series() {
|
|
||||||
return this.book.series || null
|
|
||||||
},
|
|
||||||
volumeNumber() {
|
|
||||||
return this.book.volumeNumber || null
|
|
||||||
},
|
|
||||||
seriesText() {
|
|
||||||
if (!this.series) return ''
|
|
||||||
if (!this.volumeNumber) return this.series
|
|
||||||
return `${this.series} #${this.volumeNumber}`
|
|
||||||
},
|
|
||||||
duration() {
|
|
||||||
return this.audiobook.duration
|
|
||||||
},
|
|
||||||
size() {
|
|
||||||
return this.audiobook.size
|
|
||||||
},
|
|
||||||
userAudiobook() {
|
|
||||||
return this.$store.getters['user/getUserAudiobook'](this.audiobookId)
|
|
||||||
},
|
|
||||||
userToken() {
|
|
||||||
return this.$store.getters['user/getToken']
|
|
||||||
},
|
|
||||||
userCurrentTime() {
|
|
||||||
return this.userAudiobook ? this.userAudiobook.currentTime : 0
|
|
||||||
},
|
|
||||||
userTimeRemaining() {
|
|
||||||
return Math.max(0, this.duration - this.userCurrentTime)
|
|
||||||
},
|
|
||||||
progressPercent() {
|
|
||||||
return this.userAudiobook ? this.userAudiobook.progress : 0
|
|
||||||
},
|
|
||||||
isStreaming() {
|
|
||||||
return this.$store.getters['isAudiobookStreaming'](this.audiobookId)
|
|
||||||
},
|
|
||||||
isPlaying() {
|
|
||||||
return this.$store.getters['isAudiobookPlaying'](this.audiobookId)
|
|
||||||
},
|
|
||||||
numTracks() {
|
|
||||||
if (this.audiobook.tracks) return this.audiobook.tracks.length
|
|
||||||
return this.audiobook.numTracks || 0
|
|
||||||
},
|
|
||||||
isMissing() {
|
|
||||||
return this.audiobook.isMissing
|
|
||||||
},
|
|
||||||
isIncomplete() {
|
|
||||||
return this.audiobook.isIncomplete
|
|
||||||
},
|
|
||||||
isDownloading() {
|
|
||||||
return this.downloadObj ? this.downloadObj.isDownloading : false
|
|
||||||
},
|
|
||||||
showPlay() {
|
|
||||||
return !this.isMissing && !this.isIncomplete && this.numTracks
|
|
||||||
},
|
|
||||||
showRead() {
|
|
||||||
return this.hasEbook && this.ebookFormat !== '.pdf'
|
|
||||||
},
|
|
||||||
hasEbook() {
|
|
||||||
return this.audiobook.numEbooks
|
|
||||||
},
|
|
||||||
ebookFormat() {
|
|
||||||
if (!this.audiobook || !this.audiobook.ebooks || !this.audiobook.ebooks.length) return null
|
|
||||||
return this.audiobook.ebooks[0].ext.substr(1)
|
|
||||||
},
|
|
||||||
isDownloadPreparing() {
|
|
||||||
return this.downloadObj ? this.downloadObj.isPreparing : false
|
|
||||||
},
|
|
||||||
isDownloadPlayable() {
|
|
||||||
return this.downloadObj && !this.isDownloading && !this.isDownloadPreparing
|
|
||||||
},
|
|
||||||
downloadedCover() {
|
|
||||||
return this.downloadObj ? this.downloadObj.cover : null
|
|
||||||
},
|
|
||||||
downloadObj() {
|
|
||||||
return this.$store.getters['downloads/getDownload'](this.audiobookId)
|
|
||||||
},
|
|
||||||
hasStoragePermission() {
|
|
||||||
return this.$store.state.hasStoragePermission
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
readBook() {
|
|
||||||
this.$store.commit('openReader', this.audiobook)
|
|
||||||
},
|
|
||||||
playClick() {
|
|
||||||
this.$store.commit('setPlayOnLoad', true)
|
|
||||||
if (!this.isDownloadPlayable) {
|
|
||||||
// Stream
|
|
||||||
console.log('[PLAYCLICK] Set Playing STREAM ' + this.title)
|
|
||||||
this.$store.commit('setStreamAudiobook', this.audiobook)
|
|
||||||
this.$server.socket.emit('open_stream', this.audiobook.id)
|
|
||||||
} else {
|
|
||||||
// Local
|
|
||||||
console.log('[PLAYCLICK] Set Playing Local Download ' + this.title)
|
|
||||||
this.$store.commit('setPlayingDownload', this.downloadObj)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async clearProgressClick() {
|
|
||||||
const { value } = await Dialog.confirm({
|
|
||||||
title: 'Confirm',
|
|
||||||
message: 'Are you sure you want to reset your progress?'
|
|
||||||
})
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
this.resettingProgress = true
|
|
||||||
this.$store.dispatch('user/updateUserAudiobookData', {
|
|
||||||
audiobookId: this.audiobookId,
|
|
||||||
currentTime: 0,
|
|
||||||
totalDuration: this.duration,
|
|
||||||
progress: 0,
|
|
||||||
lastUpdate: Date.now(),
|
|
||||||
isRead: false
|
|
||||||
})
|
|
||||||
|
|
||||||
if (this.$server.connected) {
|
|
||||||
await this.$axios
|
|
||||||
.$patch(`/api/me/audiobook/${this.audiobookId}/reset-progress`)
|
|
||||||
.then(() => {
|
|
||||||
console.log('Progress reset complete')
|
|
||||||
this.$toast.success(`Your progress was reset`)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Progress reset failed', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.resettingProgress = false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
audiobookUpdated(audiobook) {
|
|
||||||
if (audiobook.id === this.audiobookId) {
|
|
||||||
console.log('Audiobook Updated - Fetch full audiobook')
|
|
||||||
this.$axios
|
|
||||||
.$get(`/api/books/${this.audiobookId}`)
|
|
||||||
.then((audiobook) => {
|
|
||||||
this.audiobook = audiobook
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Failed', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
downloadClick() {
|
|
||||||
console.log('downloadClick ' + this.$server.connected + ' | ' + !!this.downloadObj)
|
|
||||||
if (!this.$server.connected) return
|
|
||||||
|
|
||||||
if (this.downloadObj) {
|
|
||||||
console.log('Already downloaded', this.downloadObj)
|
|
||||||
} else {
|
|
||||||
this.prepareDownload()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async changeDownloadFolderClick() {
|
|
||||||
if (!this.hasStoragePermission) {
|
|
||||||
console.log('Requesting Storage Permission')
|
|
||||||
await StorageManager.requestStoragePermission()
|
|
||||||
} else {
|
|
||||||
var folderObj = await StorageManager.selectFolder()
|
|
||||||
if (folderObj.error) {
|
|
||||||
return this.$toast.error(`Error: ${folderObj.error || 'Unknown Error'}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
var permissionsGood = await StorageManager.checkFolderPermissions({ folderUrl: folderObj.uri })
|
|
||||||
console.log('Storage Permission check folder ' + permissionsGood)
|
|
||||||
|
|
||||||
if (!permissionsGood) {
|
|
||||||
this.$toast.error('Folder permissions failed')
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
this.$toast.success('Folder permission success')
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.$localStore.setDownloadFolder(folderObj)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async prepareDownload() {
|
|
||||||
var audiobook = this.audiobook
|
|
||||||
if (!audiobook) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download Path
|
|
||||||
var dlFolder = this.$localStore.downloadFolder
|
|
||||||
console.log('Prepare download: ' + this.hasStoragePermission + ' | ' + dlFolder)
|
|
||||||
|
|
||||||
if (!this.hasStoragePermission || !dlFolder) {
|
|
||||||
console.log('No download folder, request from user')
|
|
||||||
// User to select download folder from download modal to ensure permissions
|
|
||||||
// this.$store.commit('downloads/setShowModal', true)
|
|
||||||
this.changeDownloadFolderClick()
|
|
||||||
return
|
|
||||||
} else {
|
|
||||||
console.log('Has Download folder: ' + JSON.stringify(dlFolder))
|
|
||||||
}
|
|
||||||
|
|
||||||
var downloadObject = {
|
|
||||||
id: this.audiobookId,
|
|
||||||
downloadFolderUrl: dlFolder.uri,
|
|
||||||
audiobook: {
|
|
||||||
...audiobook
|
|
||||||
},
|
|
||||||
isPreparing: true,
|
|
||||||
isDownloading: false,
|
|
||||||
toastId: this.$toast(`Preparing download for "${this.title}"`, { timeout: false })
|
|
||||||
}
|
|
||||||
if (audiobook.tracks.length === 1) {
|
|
||||||
// Single track should not need preparation
|
|
||||||
console.log('Single track, start download no prep needed')
|
|
||||||
var track = audiobook.tracks[0]
|
|
||||||
var fileext = track.ext
|
|
||||||
|
|
||||||
console.log('Download Single Track Path: ' + track.path)
|
|
||||||
|
|
||||||
var relTrackPath = track.path.replace('\\', '/').replace(this.audiobook.path.replace('\\', '/'), '')
|
|
||||||
|
|
||||||
var url = `${this.$store.state.serverUrl}/s/book/${this.audiobookId}${relTrackPath}?token=${this.userToken}`
|
|
||||||
this.startDownload(url, fileext, downloadObject)
|
|
||||||
} else {
|
|
||||||
// Multi-track merge
|
|
||||||
this.$store.commit('downloads/addUpdateDownload', downloadObject)
|
|
||||||
|
|
||||||
var prepareDownloadPayload = {
|
|
||||||
audiobookId: this.audiobookId,
|
|
||||||
audioFileType: 'same',
|
|
||||||
type: 'singleAudio'
|
|
||||||
}
|
|
||||||
this.$server.socket.emit('download', prepareDownloadPayload)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
getCoverUrlForDownload() {
|
|
||||||
if (!this.book || !this.book.cover) return null
|
|
||||||
|
|
||||||
var cover = this.book.cover
|
|
||||||
if (cover.startsWith('http')) return cover
|
|
||||||
var coverSrc = this.$store.getters['audiobooks/getBookCoverSrc'](this.audiobook)
|
|
||||||
return coverSrc
|
|
||||||
// var _clean = cover.replace(/\\/g, '/')
|
|
||||||
// if (_clean.startsWith('/local')) {
|
|
||||||
// var _cover = process.env.NODE_ENV !== 'production' && process.env.PROD !== '1' ? _clean.replace('/local', '') : _clean
|
|
||||||
// return `${this.$store.state.serverUrl}${_cover}?token=${this.userToken}&ts=${Date.now()}`
|
|
||||||
// } else if (_clean.startsWith('/metadata')) {
|
|
||||||
// return `${this.$store.state.serverUrl}${_clean}?token=${this.userToken}&ts=${Date.now()}`
|
|
||||||
// }
|
|
||||||
// return _clean
|
|
||||||
},
|
|
||||||
async startDownload(url, fileext, download) {
|
|
||||||
this.$toast.update(download.toastId, { content: `Downloading "${download.audiobook.book.title}"...` })
|
|
||||||
|
|
||||||
var coverDownloadUrl = this.getCoverUrlForDownload()
|
|
||||||
var coverFilename = null
|
|
||||||
if (coverDownloadUrl) {
|
|
||||||
var coverNoQueryString = coverDownloadUrl.split('?')[0]
|
|
||||||
|
|
||||||
var coverExt = Path.extname(coverNoQueryString) || '.jpg'
|
|
||||||
coverFilename = `cover-${download.id}${coverExt}`
|
|
||||||
}
|
|
||||||
|
|
||||||
download.isDownloading = true
|
|
||||||
download.isPreparing = false
|
|
||||||
download.filename = `${download.audiobook.book.title}${fileext}`
|
|
||||||
this.$store.commit('downloads/addUpdateDownload', download)
|
|
||||||
|
|
||||||
console.log('Starting Download URL', url)
|
|
||||||
var downloadRequestPayload = {
|
|
||||||
audiobookId: download.id,
|
|
||||||
filename: download.filename,
|
|
||||||
coverFilename,
|
|
||||||
coverDownloadUrl,
|
|
||||||
downloadUrl: url,
|
|
||||||
title: download.audiobook.book.title,
|
|
||||||
downloadFolderUrl: download.downloadFolderUrl
|
|
||||||
}
|
|
||||||
var downloadRes = await AudioDownloader.download(downloadRequestPayload)
|
|
||||||
if (downloadRes.error) {
|
|
||||||
var errorMsg = downloadRes.error || 'Unknown error'
|
|
||||||
console.error('Download error', errorMsg)
|
|
||||||
this.$toast.update(download.toastId, { content: `Error: ${errorMsg}`, options: { timeout: 5000, type: 'error' } })
|
|
||||||
this.$store.commit('downloads/removeDownload', download)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
downloadReady(prepareDownload) {
|
|
||||||
var download = this.$store.getters['downloads/getDownload'](prepareDownload.audiobookId)
|
|
||||||
if (download) {
|
|
||||||
var fileext = prepareDownload.ext
|
|
||||||
var url = `${this.$store.state.serverUrl}/downloads/${prepareDownload.id}/${prepareDownload.filename}?token=${this.userToken}`
|
|
||||||
this.startDownload(url, fileext, download)
|
|
||||||
} else {
|
|
||||||
console.error('Prepare download killed but download not found', prepareDownload)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
downloadKilled(prepareDownload) {
|
|
||||||
var download = this.$store.getters['downloads/getDownload'](prepareDownload.audiobookId)
|
|
||||||
if (download) {
|
|
||||||
this.$toast.update(download.toastId, { content: `Prepare download killed for "${download.audiobook.book.title}"`, options: { timeout: 5000, type: 'error' } })
|
|
||||||
this.$store.commit('downloads/removeDownload', download)
|
|
||||||
} else {
|
|
||||||
console.error('Prepare download killed but download not found', prepareDownload)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
downloadFailed(prepareDownload) {
|
|
||||||
var download = this.$store.getters['downloads/getDownload'](prepareDownload.audiobookId)
|
|
||||||
if (download) {
|
|
||||||
this.$toast.update(download.toastId, { content: `Prepare download failed for "${download.audiobook.book.title}"`, options: { timeout: 5000, type: 'error' } })
|
|
||||||
this.$store.commit('downloads/removeDownload', download)
|
|
||||||
} else {
|
|
||||||
console.error('Prepare download failed but download not found', prepareDownload)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
if (!this.$server.socket) {
|
|
||||||
console.warn('Audiobook Page mounted: Server socket not set')
|
|
||||||
} else {
|
|
||||||
this.$server.socket.on('download_ready', this.downloadReady)
|
|
||||||
this.$server.socket.on('download_killed', this.downloadKilled)
|
|
||||||
this.$server.socket.on('download_failed', this.downloadFailed)
|
|
||||||
this.$server.socket.on('audiobook_updated', this.audiobookUpdated)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
if (!this.$server.socket) {
|
|
||||||
console.warn('Audiobook Page beforeDestroy: Server socket not set')
|
|
||||||
} else {
|
|
||||||
this.$server.socket.off('download_ready', this.downloadReady)
|
|
||||||
this.$server.socket.off('download_killed', this.downloadKilled)
|
|
||||||
this.$server.socket.off('download_failed', this.downloadFailed)
|
|
||||||
this.$server.socket.off('audiobook_updated', this.audiobookUpdated)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
|
@ -8,20 +8,20 @@
|
||||||
<div>
|
<div>
|
||||||
<p class="mb-4 text-center text-xl">
|
<p class="mb-4 text-center text-xl">
|
||||||
Bookshelf empty
|
Bookshelf empty
|
||||||
<span v-show="isSocketConnected">
|
<span v-show="user">
|
||||||
for library
|
for library
|
||||||
<strong>{{ currentLibraryName }}</strong>
|
<strong>{{ currentLibraryName }}</strong>
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="w-full" v-if="!isSocketConnected">
|
<div class="w-full" v-if="!user">
|
||||||
<div class="flex justify-center items-center mb-3">
|
<div class="flex justify-center items-center mb-3">
|
||||||
<span class="material-icons text-error text-lg">cloud_off</span>
|
<span class="material-icons text-error text-lg">cloud_off</span>
|
||||||
<p class="pl-2 text-error text-sm">Audiobookshelf server not connected.</p>
|
<p class="pl-2 text-error text-sm">Audiobookshelf server not connected.</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="px-4 text-center text-error absolute bottom-12 left-0 right-0 mx-auto"><strong>Important!</strong> This app requires that you are running <u>your own server</u> and does not provide any content.</p>
|
<!-- <p class="px-4 text-center text-error absolute bottom-12 left-0 right-0 mx-auto"><strong>Important!</strong> This app requires that you are running <u>your own server</u> and does not provide any content.</p> -->
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<ui-btn v-if="!isSocketConnected" small @click="$router.push('/connect')" class="w-32">Connect</ui-btn>
|
<ui-btn v-if="!user" small @click="$router.push('/connect')" class="w-32">Connect</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -46,6 +46,9 @@ export default {
|
||||||
return ab
|
return ab
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
user() {
|
||||||
|
return this.$store.state.user.user
|
||||||
|
},
|
||||||
isSocketConnected() {
|
isSocketConnected() {
|
||||||
return this.$store.state.socketConnected
|
return this.$store.state.socketConnected
|
||||||
},
|
},
|
||||||
|
@ -133,16 +136,16 @@ export default {
|
||||||
this.shelves = categories
|
this.shelves = categories
|
||||||
console.log('Shelves', this.shelves)
|
console.log('Shelves', this.shelves)
|
||||||
},
|
},
|
||||||
async socketInit(isConnected) {
|
// async socketInit(isConnected) {
|
||||||
if (isConnected && this.currentLibraryId) {
|
// if (isConnected && this.currentLibraryId) {
|
||||||
console.log('Connected - Load from server')
|
// console.log('Connected - Load from server')
|
||||||
await this.fetchCategories()
|
// await this.fetchCategories()
|
||||||
} else {
|
// } else {
|
||||||
console.log('Disconnected - Reset to local storage')
|
// console.log('Disconnected - Reset to local storage')
|
||||||
this.shelves = this.downloadOnlyShelves
|
// this.shelves = this.downloadOnlyShelves
|
||||||
}
|
// }
|
||||||
this.loading = false
|
// this.loading = false
|
||||||
},
|
// },
|
||||||
async libraryChanged(libid) {
|
async libraryChanged(libid) {
|
||||||
if (this.isSocketConnected && this.currentLibraryId) {
|
if (this.isSocketConnected && this.currentLibraryId) {
|
||||||
await this.fetchCategories()
|
await this.fetchCategories()
|
||||||
|
@ -211,43 +214,24 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
initListeners() {
|
initListeners() {
|
||||||
this.$server.on('initialized', this.socketInit)
|
// this.$server.on('initialized', this.socketInit)
|
||||||
this.$eventBus.$on('library-changed', this.libraryChanged)
|
this.$eventBus.$on('library-changed', this.libraryChanged)
|
||||||
this.$eventBus.$on('downloads-loaded', this.downloadsLoaded)
|
this.$eventBus.$on('downloads-loaded', this.downloadsLoaded)
|
||||||
|
|
||||||
if (this.$server.socket) {
|
|
||||||
this.$server.socket.on('audiobook_updated', this.audiobookUpdated)
|
|
||||||
this.$server.socket.on('audiobook_added', this.audiobookAdded)
|
|
||||||
this.$server.socket.on('audiobook_removed', this.audiobookRemoved)
|
|
||||||
this.$server.socket.on('audiobooks_updated', this.audiobooksUpdated)
|
|
||||||
this.$server.socket.on('audiobooks_added', this.audiobooksAdded)
|
|
||||||
} else {
|
|
||||||
console.error('Error socket not initialized')
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
removeListeners() {
|
removeListeners() {
|
||||||
this.$server.off('initialized', this.socketInit)
|
// this.$server.off('initialized', this.socketInit)
|
||||||
this.$eventBus.$off('library-changed', this.libraryChanged)
|
this.$eventBus.$off('library-changed', this.libraryChanged)
|
||||||
this.$eventBus.$off('downloads-loaded', this.downloadsLoaded)
|
this.$eventBus.$off('downloads-loaded', this.downloadsLoaded)
|
||||||
|
|
||||||
if (this.$server.socket) {
|
|
||||||
this.$server.socket.off('audiobook_updated', this.audiobookUpdated)
|
|
||||||
this.$server.socket.off('audiobook_added', this.audiobookAdded)
|
|
||||||
this.$server.socket.off('audiobook_removed', this.audiobookRemoved)
|
|
||||||
this.$server.socket.off('audiobooks_updated', this.audiobooksUpdated)
|
|
||||||
this.$server.socket.off('audiobooks_added', this.audiobooksAdded)
|
|
||||||
} else {
|
|
||||||
console.error('Error socket not initialized')
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.initListeners()
|
this.initListeners()
|
||||||
if (this.$server.initialized && this.currentLibraryId) {
|
|
||||||
this.fetchCategories()
|
this.fetchCategories()
|
||||||
} else {
|
// if (this.$server.initialized && this.currentLibraryId) {
|
||||||
this.shelves = this.downloadOnlyShelves
|
// this.fetchCategories()
|
||||||
}
|
// } else {
|
||||||
|
// this.shelves = this.downloadOnlyShelves
|
||||||
|
// }
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.removeListeners()
|
this.removeListeners()
|
||||||
|
|
|
@ -10,53 +10,11 @@
|
||||||
</div>
|
</div>
|
||||||
<p class="hidden absolute short:block top-1.5 left-12 p-2 font-book text-xl">audiobookshelf</p>
|
<p class="hidden absolute short:block top-1.5 left-12 p-2 font-book text-xl">audiobookshelf</p>
|
||||||
|
|
||||||
<p class="absolute bottom-16 left-0 right-0 px-2 text-center text-error"><strong>Important!</strong> This app requires that you are running <u>your own server</u> and does not provide any content.</p>
|
<!-- <p class="absolute bottom-16 left-0 right-0 px-2 text-center text-error"><strong>Important!</strong> This app requires that you are running <u>your own server</u> and does not provide any content.</p> -->
|
||||||
|
|
||||||
<div class="w-full max-w-md mx-auto px-4 sm:px-6 lg:px-8 z-10">
|
<connection-server-connect-form :server-connection-configs="serverConnectionConfigs" :last-server-connection-config="lastServerConnectionConfig" />
|
||||||
<div v-show="loggedIn" class="mt-8 bg-primary overflow-hidden shadow rounded-lg p-6 text-center">
|
|
||||||
<p class="text-success text-xl mb-2">Login Success!</p>
|
|
||||||
<p>Connecting socket..</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-show="!loggedIn" class="mt-8 bg-primary overflow-hidden shadow rounded-lg p-6 w-full">
|
|
||||||
<h2 class="text-lg leading-7 mb-4">Server address</h2>
|
|
||||||
<form v-show="!showAuth" @submit.prevent="submit" novalidate class="w-full">
|
|
||||||
<ui-text-input v-model="serverUrl" :disabled="processing || !networkConnected" placeholder="http://55.55.55.55:13378" type="url" class="w-full sm:w-72 h-10" />
|
|
||||||
<div class="flex justify-end">
|
|
||||||
<ui-btn :disabled="processing || !networkConnected" type="submit" :padding-x="3" class="h-10 mt-4">{{ networkConnected ? 'Submit' : 'No Internet' }}</ui-btn>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<template v-if="showAuth">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<p class="">{{ serverUrl }}</p>
|
|
||||||
<div class="flex-grow" />
|
|
||||||
<span class="material-icons" style="font-size: 1.1rem" @click="editServerUrl">edit</span>
|
|
||||||
</div>
|
|
||||||
<div class="w-full h-px bg-gray-200 my-2" />
|
|
||||||
<form @submit.prevent="submitAuth" class="pt-3">
|
|
||||||
<ui-text-input v-model="username" :disabled="processing" placeholder="username" class="w-full my-1 text-lg" />
|
|
||||||
<ui-text-input v-model="password" type="password" :disabled="processing" placeholder="password" class="w-full my-1 text-lg" />
|
|
||||||
|
|
||||||
<ui-btn :disabled="processing || !networkConnected" type="submit" class="mt-1 h-10">{{ networkConnected ? 'Submit' : 'No Internet' }}</ui-btn>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div v-show="error" class="w-full rounded-lg bg-red-600 bg-opacity-10 border border-error border-opacity-50 py-3 px-2 flex items-center mt-4">
|
|
||||||
<span class="material-icons mr-2 text-error" style="font-size: 1.1rem">warning</span>
|
|
||||||
<p class="text-error">{{ error }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div :class="processing ? 'opacity-100' : 'opacity-0 pointer-events-none'" class="fixed w-full h-full top-0 left-0 bg-black bg-opacity-75 flex items-center justify-center z-30 transition-opacity duration-500">
|
|
||||||
<div>
|
|
||||||
<div class="absolute top-0 left-0 w-full p-6 flex items-center flex-col justify-center z-0 short:hidden">
|
|
||||||
<img src="/Logo.png" class="h-20 w-20 mb-2" />
|
|
||||||
</div>
|
|
||||||
<svg class="animate-spin w-16 h-16" viewBox="0 0 24 24">
|
|
||||||
<path fill="currentColor" d="M12,4V2A10,10 0 0,0 2,12H4A8,8 0 0,1 12,4Z" />
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex items-center justify-center pt-4 fixed bottom-4 left-0 right-0">
|
<div class="flex items-center justify-center pt-4 fixed bottom-4 left-0 right-0">
|
||||||
<a href="https://github.com/advplyr/audiobookshelf-app" target="_blank" class="text-sm pr-2">Follow the project on Github</a>
|
<a href="https://github.com/advplyr/audiobookshelf-app" target="_blank" class="text-sm pr-2">Follow the project on Github</a>
|
||||||
<a href="https://github.com/advplyr/audiobookshelf-app" target="_blank"
|
<a href="https://github.com/advplyr/audiobookshelf-app" target="_blank"
|
||||||
|
@ -74,15 +32,7 @@
|
||||||
export default {
|
export default {
|
||||||
layout: 'blank',
|
layout: 'blank',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {}
|
||||||
serverUrl: null,
|
|
||||||
processing: false,
|
|
||||||
showAuth: false,
|
|
||||||
username: null,
|
|
||||||
password: null,
|
|
||||||
error: null,
|
|
||||||
loggedIn: false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
networkConnected() {
|
networkConnected() {
|
||||||
|
@ -90,110 +40,12 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async submit() {
|
|
||||||
if (!this.networkConnected) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!this.serverUrl.startsWith('http')) {
|
|
||||||
this.serverUrl = 'http://' + this.serverUrl
|
|
||||||
}
|
|
||||||
this.processing = true
|
|
||||||
this.error = null
|
|
||||||
var response = await this.$server.check(this.serverUrl)
|
|
||||||
this.processing = false
|
|
||||||
if (!response || response.error) {
|
|
||||||
console.error('Server invalid')
|
|
||||||
this.error = response ? response.error : 'Invalid Server'
|
|
||||||
} else {
|
|
||||||
this.showAuth = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async submitAuth() {
|
|
||||||
if (!this.networkConnected) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (!this.username) {
|
|
||||||
this.error = 'Invalid username'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.error = null
|
|
||||||
|
|
||||||
this.processing = true
|
|
||||||
var response = await this.$server.login(this.serverUrl, this.username, this.password)
|
|
||||||
this.processing = false
|
|
||||||
if (response.error) {
|
|
||||||
console.error('Login failed')
|
|
||||||
this.error = response.error
|
|
||||||
} else {
|
|
||||||
console.log('Login Success!')
|
|
||||||
this.loggedIn = true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
editServerUrl() {
|
|
||||||
this.error = null
|
|
||||||
this.showAuth = false
|
|
||||||
},
|
|
||||||
redirect() {
|
|
||||||
if (this.$route.query && this.$route.query.redirect) {
|
|
||||||
this.$router.replace(this.$route.query.redirect)
|
|
||||||
} else {
|
|
||||||
this.$router.replace('/bookshelf')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
socketConnected() {
|
|
||||||
console.log('Socket connected')
|
|
||||||
this.redirect()
|
|
||||||
},
|
|
||||||
async init() {
|
async init() {
|
||||||
await this.$store.dispatch('setupNetworkListener')
|
await this.$store.dispatch('setupNetworkListener')
|
||||||
|
|
||||||
if (!this.$server) {
|
|
||||||
console.error('Invalid server not initialized')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.$server.connected) {
|
|
||||||
console.warn('Server already connected')
|
|
||||||
return this.redirect()
|
|
||||||
}
|
|
||||||
this.$server.on('connected', this.socketConnected)
|
|
||||||
|
|
||||||
var localServerUrl = await this.$localStore.getServerUrl()
|
|
||||||
var localUserToken = await this.$localStore.getToken()
|
|
||||||
|
|
||||||
if (!this.networkConnected) return
|
|
||||||
|
|
||||||
if (localServerUrl) {
|
|
||||||
this.serverUrl = localServerUrl
|
|
||||||
if (localUserToken) {
|
|
||||||
this.processing = true
|
|
||||||
var response = await this.$server.connect(localServerUrl, localUserToken)
|
|
||||||
if (!response || response.error) {
|
|
||||||
var errorMsg = response ? response.error : 'Unknown Error'
|
|
||||||
this.processing = false
|
|
||||||
this.error = errorMsg
|
|
||||||
if (!this.$server.url) {
|
|
||||||
this.serverUrl = null
|
|
||||||
this.showAuth = false
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
console.log('Server connect success')
|
|
||||||
this.showAuth = true
|
|
||||||
} else {
|
|
||||||
this.submit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.init()
|
this.init()
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
if (!this.$server) {
|
|
||||||
console.error('Connected beforeDestroy: No Server')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.$server.off('connected', this.socketConnected)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -277,7 +277,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async init() {
|
async init() {
|
||||||
this.localFolders = (await this.$db.loadFolders()) || []
|
this.localFolders = (await this.$db.getLocalFolders()) || []
|
||||||
AudioDownloader.addListener('onDownloadProgress', this.onDownloadProgress)
|
AudioDownloader.addListener('onDownloadProgress', this.onDownloadProgress)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -61,7 +61,7 @@ export default {
|
||||||
var libraryItemId = params.id
|
var libraryItemId = params.id
|
||||||
var libraryItem = null
|
var libraryItem = null
|
||||||
|
|
||||||
if (app.$server.connected) {
|
if (store.state.user.serverConnectionConfig) {
|
||||||
libraryItem = await app.$axios.$get(`/api/items/${libraryItemId}?expanded=1`).catch((error) => {
|
libraryItem = await app.$axios.$get(`/api/items/${libraryItemId}?expanded=1`).catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
return false
|
return false
|
||||||
|
@ -218,10 +218,10 @@ export default {
|
||||||
// }
|
// }
|
||||||
},
|
},
|
||||||
async clearProgressClick() {
|
async clearProgressClick() {
|
||||||
if (!this.$server.connected) {
|
// if (!this.$server.connected) {
|
||||||
this.$toast.info('Clear downloaded book progress not yet implemented')
|
// this.$toast.info('Clear downloaded book progress not yet implemented')
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
const { value } = await Dialog.confirm({
|
const { value } = await Dialog.confirm({
|
||||||
title: 'Confirm',
|
title: 'Confirm',
|
||||||
|
@ -264,9 +264,6 @@ export default {
|
||||||
this.download()
|
this.download()
|
||||||
},
|
},
|
||||||
async download(selectedLocalFolder = null) {
|
async download(selectedLocalFolder = null) {
|
||||||
console.log('downloadClick ' + this.$server.connected + ' | ' + !!this.downloadObj)
|
|
||||||
if (!this.$server.connected) return
|
|
||||||
|
|
||||||
if (!this.numTracks || this.downloadObj) {
|
if (!this.numTracks || this.downloadObj) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -274,7 +271,7 @@ export default {
|
||||||
// Get the local folder to download to
|
// Get the local folder to download to
|
||||||
var localFolder = selectedLocalFolder
|
var localFolder = selectedLocalFolder
|
||||||
if (!localFolder) {
|
if (!localFolder) {
|
||||||
var localFolders = (await this.$db.loadFolders()) || []
|
var localFolders = (await this.$db.getLocalFolders()) || []
|
||||||
console.log('Local folders loaded', localFolders.length)
|
console.log('Local folders loaded', localFolders.length)
|
||||||
var foldersWithMediaType = localFolders.filter((lf) => {
|
var foldersWithMediaType = localFolders.filter((lf) => {
|
||||||
console.log('Checking local folder', lf.mediaType)
|
console.log('Checking local folder', lf.mediaType)
|
||||||
|
@ -468,24 +465,24 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
if (!this.$server.socket) {
|
// if (!this.$server.socket) {
|
||||||
console.warn('Library Item Page mounted: Server socket not set')
|
// console.warn('Library Item Page mounted: Server socket not set')
|
||||||
} else {
|
// } else {
|
||||||
this.$server.socket.on('download_ready', this.downloadReady)
|
// this.$server.socket.on('download_ready', this.downloadReady)
|
||||||
this.$server.socket.on('download_killed', this.downloadKilled)
|
// this.$server.socket.on('download_killed', this.downloadKilled)
|
||||||
this.$server.socket.on('download_failed', this.downloadFailed)
|
// this.$server.socket.on('download_failed', this.downloadFailed)
|
||||||
this.$server.socket.on('item_updated', this.itemUpdated)
|
// this.$server.socket.on('item_updated', this.itemUpdated)
|
||||||
}
|
// }
|
||||||
},
|
},
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
if (!this.$server.socket) {
|
// if (!this.$server.socket) {
|
||||||
console.warn('Library Item Page beforeDestroy: Server socket not set')
|
// console.warn('Library Item Page beforeDestroy: Server socket not set')
|
||||||
} else {
|
// } else {
|
||||||
this.$server.socket.off('download_ready', this.downloadReady)
|
// this.$server.socket.off('download_ready', this.downloadReady)
|
||||||
this.$server.socket.off('download_killed', this.downloadKilled)
|
// this.$server.socket.off('download_killed', this.downloadKilled)
|
||||||
this.$server.socket.off('download_failed', this.downloadFailed)
|
// this.$server.socket.off('download_failed', this.downloadFailed)
|
||||||
this.$server.socket.off('item_updated', this.itemUpdated)
|
// this.$server.socket.off('item_updated', this.itemUpdated)
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -82,7 +82,7 @@ export default {
|
||||||
this.$router.push(`/localMedia/folders/${folderObj.id}?scan=1`)
|
this.$router.push(`/localMedia/folders/${folderObj.id}?scan=1`)
|
||||||
},
|
},
|
||||||
async init() {
|
async init() {
|
||||||
this.localFolders = (await this.$db.loadFolders()) || []
|
this.localFolders = (await this.$db.getLocalFolders()) || []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<p v-if="bookResults.length" class="font-semibold text-sm mb-1">Books</p>
|
<p v-if="bookResults.length" class="font-semibold text-sm mb-1">Books</p>
|
||||||
<template v-for="bookResult in bookResults">
|
<template v-for="bookResult in bookResults">
|
||||||
<div :key="bookResult.audiobook.id" class="w-full h-16 py-1">
|
<div :key="bookResult.audiobook.id" class="w-full h-16 py-1">
|
||||||
<nuxt-link :to="`/audiobook/${bookResult.audiobook.id}`">
|
<nuxt-link :to="`/item/${bookResult.audiobook.id}`">
|
||||||
<cards-book-search-card :audiobook="bookResult.audiobook" :search="lastSearch" :match-key="bookResult.matchKey" :match-text="bookResult.matchText" />
|
<cards-book-search-card :audiobook="bookResult.audiobook" :search="lastSearch" :match-key="bookResult.matchKey" :match-text="bookResult.matchText" />
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,7 +11,7 @@ export default function ({ $axios, store }) {
|
||||||
console.warn('[Axios] No Bearer Token for request')
|
console.warn('[Axios] No Bearer Token for request')
|
||||||
}
|
}
|
||||||
|
|
||||||
var serverUrl = store.state.serverUrl
|
var serverUrl = store.getters['user/getServerAddress']
|
||||||
if (serverUrl) {
|
if (serverUrl) {
|
||||||
config.url = `${serverUrl}${config.url}`
|
config.url = `${serverUrl}${config.url}`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import { registerPlugin } from '@capacitor/core';
|
import { registerPlugin, Capacitor } from '@capacitor/core';
|
||||||
|
|
||||||
const DbManager = registerPlugin('DbManager');
|
const isWeb = Capacitor.getPlatform() == 'web'
|
||||||
|
const DbManager = registerPlugin('DbManager')
|
||||||
|
|
||||||
class DbService {
|
class DbService {
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
save(db, key, value) {
|
save(db, key, value) {
|
||||||
|
if (isWeb) return
|
||||||
return DbManager.saveFromWebview({ db, key, value }).then(() => {
|
return DbManager.saveFromWebview({ db, key, value }).then(() => {
|
||||||
console.log('Saved data', db, key, JSON.stringify(value))
|
console.log('Saved data', db, key, JSON.stringify(value))
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
@ -14,6 +16,7 @@ class DbService {
|
||||||
}
|
}
|
||||||
|
|
||||||
load(db, key) {
|
load(db, key) {
|
||||||
|
if (isWeb) return null
|
||||||
return DbManager.loadFromWebview({ db, key }).then((data) => {
|
return DbManager.loadFromWebview({ db, key }).then((data) => {
|
||||||
console.log('Loaded data', db, key, JSON.stringify(data))
|
console.log('Loaded data', db, key, JSON.stringify(data))
|
||||||
return data
|
return data
|
||||||
|
@ -23,7 +26,37 @@ class DbService {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
loadFolders() {
|
getDeviceData() {
|
||||||
|
if (isWeb) return {}
|
||||||
|
return DbManager.getDeviceData_WV().then((data) => {
|
||||||
|
console.log('Loaded device data', JSON.stringify(data))
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
setServerConnectionConfig(serverConnectionConfig) {
|
||||||
|
if (isWeb) return null
|
||||||
|
return DbManager.setCurrentServerConnectionConfig_WV(serverConnectionConfig).then((data) => {
|
||||||
|
console.log('Set server connection config', JSON.stringify(data))
|
||||||
|
return data
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
removeServerConnectionConfig(serverConnectionConfigId) {
|
||||||
|
if (isWeb) return null
|
||||||
|
return DbManager.removeServerConnectionConfig_WV({ serverConnectionConfigId }).then((data) => {
|
||||||
|
console.log('Removed server connection config', serverConnectionConfigId)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
if (isWeb) return null
|
||||||
|
return DbManager.logout_WV()
|
||||||
|
}
|
||||||
|
|
||||||
|
getLocalFolders() {
|
||||||
|
if (isWeb) return []
|
||||||
return DbManager.getLocalFolders_WV().then((data) => {
|
return DbManager.getLocalFolders_WV().then((data) => {
|
||||||
console.log('Loaded local folders', JSON.stringify(data))
|
console.log('Loaded local folders', JSON.stringify(data))
|
||||||
if (data.folders && typeof data.folders == 'string') {
|
if (data.folders && typeof data.folders == 'string') {
|
||||||
|
@ -37,6 +70,7 @@ class DbService {
|
||||||
}
|
}
|
||||||
|
|
||||||
getLocalFolder(folderId) {
|
getLocalFolder(folderId) {
|
||||||
|
if (isWeb) return null
|
||||||
return DbManager.getLocalFolder_WV({ folderId }).then((data) => {
|
return DbManager.getLocalFolder_WV({ folderId }).then((data) => {
|
||||||
console.log('Got local folder', JSON.stringify(data))
|
console.log('Got local folder', JSON.stringify(data))
|
||||||
return data
|
return data
|
||||||
|
@ -44,6 +78,7 @@ class DbService {
|
||||||
}
|
}
|
||||||
|
|
||||||
getLocalMediaItemsInFolder(folderId) {
|
getLocalMediaItemsInFolder(folderId) {
|
||||||
|
if (isWeb) return []
|
||||||
return DbManager.getLocalMediaItemsInFolder_WV({ folderId }).then((data) => {
|
return DbManager.getLocalMediaItemsInFolder_WV({ folderId }).then((data) => {
|
||||||
console.log('Loaded local media items in folder', JSON.stringify(data))
|
console.log('Loaded local media items in folder', JSON.stringify(data))
|
||||||
if (data.localMediaItems && typeof data.localMediaItems == 'string') {
|
if (data.localMediaItems && typeof data.localMediaItems == 'string') {
|
||||||
|
|
|
@ -4,6 +4,8 @@ import { Dialog } from '@capacitor/dialog'
|
||||||
import { StatusBar, Style } from '@capacitor/status-bar';
|
import { StatusBar, Style } from '@capacitor/status-bar';
|
||||||
import { formatDistance, format } from 'date-fns'
|
import { formatDistance, format } from 'date-fns'
|
||||||
|
|
||||||
|
Vue.prototype.$eventBus = new Vue()
|
||||||
|
|
||||||
const setStatusBarStyleDark = async () => {
|
const setStatusBarStyleDark = async () => {
|
||||||
await StatusBar.setStyle({ style: Style.Dark })
|
await StatusBar.setStyle({ style: Style.Dark })
|
||||||
}
|
}
|
||||||
|
@ -22,9 +24,7 @@ App.addListener('backButton', async ({ canGoBack }) => {
|
||||||
} else {
|
} else {
|
||||||
window.history.back()
|
window.history.back()
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
|
|
||||||
Vue.prototype.$eventBus = new Vue()
|
|
||||||
|
|
||||||
Vue.prototype.$isDev = process.env.NODE_ENV !== 'production'
|
Vue.prototype.$isDev = process.env.NODE_ENV !== 'production'
|
||||||
|
|
||||||
|
|
|
@ -8,50 +8,6 @@ class LocalStorage {
|
||||||
this.downloadFolder = null
|
this.downloadFolder = null
|
||||||
}
|
}
|
||||||
|
|
||||||
async setToken(token) {
|
|
||||||
try {
|
|
||||||
if (token) {
|
|
||||||
await Storage.set({ key: 'token', value: token })
|
|
||||||
} else {
|
|
||||||
await Storage.remove({ key: 'token' })
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[LocalStorage] Failed to set token', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getToken() {
|
|
||||||
try {
|
|
||||||
return (await Storage.get({ key: 'token' }) || {}).value || null
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[LocalStorage] Failed to get token', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setCurrentLibrary(library) {
|
|
||||||
try {
|
|
||||||
if (library) {
|
|
||||||
await Storage.set({ key: 'library', value: JSON.stringify(library) })
|
|
||||||
} else {
|
|
||||||
await Storage.remove({ key: 'library' })
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[LocalStorage] Failed to set library', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async getCurrentLibrary() {
|
|
||||||
try {
|
|
||||||
var _value = (await Storage.get({ key: 'library' }) || {}).value || null
|
|
||||||
if (!_value) return null
|
|
||||||
return JSON.parse(_value)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[LocalStorage] Failed to get current library', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setDownloadFolder(folderObj) {
|
async setDownloadFolder(folderObj) {
|
||||||
try {
|
try {
|
||||||
if (folderObj) {
|
if (folderObj) {
|
||||||
|
@ -82,15 +38,6 @@ class LocalStorage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getServerUrl() {
|
|
||||||
try {
|
|
||||||
return (await Storage.get({ key: 'serverUrl' }) || {}).value || null
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[LocalStorage] Failed to get serverUrl', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async setUserSettings(settings) {
|
async setUserSettings(settings) {
|
||||||
try {
|
try {
|
||||||
await Storage.set({ key: 'userSettings', value: JSON.stringify(settings) })
|
await Storage.set({ key: 'userSettings', value: JSON.stringify(settings) })
|
||||||
|
|
|
@ -1,5 +1,73 @@
|
||||||
import Server from '../Server'
|
import Vue from 'vue'
|
||||||
|
import { io } from 'socket.io-client'
|
||||||
|
import EventEmitter from 'events'
|
||||||
|
|
||||||
export default function ({ store, $axios }, inject) {
|
class ServerSocket extends EventEmitter {
|
||||||
inject('server', new Server(store, $axios))
|
constructor(store) {
|
||||||
|
super()
|
||||||
|
|
||||||
|
this.$store = store
|
||||||
|
this.socket = null
|
||||||
|
this.connected = false
|
||||||
|
this.serverAddress = null
|
||||||
|
this.token = null
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(serverAddress, token) {
|
||||||
|
this.serverAddress = serverAddress
|
||||||
|
this.token = token
|
||||||
|
|
||||||
|
console.log('[SOCKET] Connect Socket', this.serverAddress)
|
||||||
|
|
||||||
|
const socketOptions = {
|
||||||
|
transports: ['websocket'],
|
||||||
|
upgrade: false,
|
||||||
|
// reconnectionAttempts: 3
|
||||||
|
}
|
||||||
|
this.socket = io(this.serverAddress, socketOptions)
|
||||||
|
this.setSocketListeners()
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
if (this.socket) this.socket.disconnect()
|
||||||
|
}
|
||||||
|
|
||||||
|
setSocketListeners() {
|
||||||
|
if (!this.socket) return
|
||||||
|
this.socket.on('connect', this.onConnect.bind(this))
|
||||||
|
this.socket.on('disconnect', this.onDisconnect.bind(this))
|
||||||
|
this.socket.on('init', this.onInit.bind(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
onConnect() {
|
||||||
|
console.log('[SOCKET] Socket Connected ' + this.socket.id)
|
||||||
|
this.connected = true
|
||||||
|
this.$store.commit('setSocketConnected', true)
|
||||||
|
this.emit('connection-update', true)
|
||||||
|
}
|
||||||
|
|
||||||
|
onDisconnect(reason) {
|
||||||
|
console.log('[SOCKET] Socket Disconnected: ' + reason)
|
||||||
|
this.connected = false
|
||||||
|
this.$store.commit('setSocketConnected', false)
|
||||||
|
this.emit('connection-update', false)
|
||||||
|
|
||||||
|
this.socket.removeAllListeners()
|
||||||
|
if (this.socket.io && this.socket.io.removeAllListeners) {
|
||||||
|
this.socket.io.removeAllListeners()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onInit(data) {
|
||||||
|
console.log('[SOCKET] Initial socket data received', data)
|
||||||
|
if (data.serverSettings) {
|
||||||
|
this.$store.commit('setServerSettings', data.serverSettings)
|
||||||
|
}
|
||||||
|
this.emit('initialized', true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ({ app, store }, inject) => {
|
||||||
|
console.log('Check event bus', this, Vue.prototype.$eventBus)
|
||||||
|
inject('socket', new ServerSocket(store))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,35 +0,0 @@
|
||||||
export const state = () => ({
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getters = {
|
|
||||||
getBookCoverSrc: (state, getters, rootState, rootGetters) => (bookItem, placeholder = '/book_placeholder.jpg') => {
|
|
||||||
if (!bookItem) return placeholder
|
|
||||||
var book = bookItem.book
|
|
||||||
if (!book || !book.cover || book.cover === placeholder) return placeholder
|
|
||||||
|
|
||||||
// Absolute URL covers (should no longer be used)
|
|
||||||
if (book.cover.startsWith('http:') || book.cover.startsWith('https:')) return book.cover
|
|
||||||
|
|
||||||
var userToken = rootGetters['user/getToken']
|
|
||||||
var bookLastUpdate = book.lastUpdate || Date.now()
|
|
||||||
|
|
||||||
if (!bookItem.id) {
|
|
||||||
console.error('No book item id', bookItem)
|
|
||||||
}
|
|
||||||
if (process.env.NODE_ENV !== 'production') { // Testing
|
|
||||||
// return `http://localhost:3333/api/books/${bookItem.id}/cover?token=${userToken}&ts=${bookLastUpdate}`
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = new URL(`/api/books/${bookItem.id}/cover`, rootState.serverUrl)
|
|
||||||
return `${url}?token=${userToken}&ts=${bookLastUpdate}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const actions = {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export const mutations = {
|
|
||||||
|
|
||||||
}
|
|
|
@ -18,7 +18,7 @@ export const getters = {
|
||||||
// return `http://localhost:3333/api/items/${libraryItem.id}/cover?token=${userToken}&ts=${lastUpdate}`
|
// return `http://localhost:3333/api/items/${libraryItem.id}/cover?token=${userToken}&ts=${lastUpdate}`
|
||||||
}
|
}
|
||||||
|
|
||||||
var url = new URL(`/api/items/${libraryItem.id}/cover`, rootState.serverUrl)
|
var url = new URL(`/api/items/${libraryItem.id}/cover`, rootGetters['user/getServerAddress'])
|
||||||
return `${url}?token=${userToken}&ts=${lastUpdate}`
|
return `${url}?token=${userToken}&ts=${lastUpdate}`
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,11 +47,13 @@ export const getters = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
|
// Listen for network connection
|
||||||
async setupNetworkListener({ state, commit }) {
|
async setupNetworkListener({ state, commit }) {
|
||||||
if (state.isNetworkListenerInit) return
|
if (state.isNetworkListenerInit) return
|
||||||
commit('setNetworkListenerInit', true)
|
commit('setNetworkListenerInit', true)
|
||||||
|
|
||||||
var status = await Network.getStatus()
|
var status = await Network.getStatus()
|
||||||
|
console.log('Network status', status)
|
||||||
commit('setNetworkStatus', status)
|
commit('setNetworkStatus', status)
|
||||||
|
|
||||||
Network.addListener('networkStatusChange', (status) => {
|
Network.addListener('networkStatusChange', (status) => {
|
||||||
|
|
|
@ -27,12 +27,6 @@ export const actions = {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// var library = state.libraries.find(lib => lib.id === libraryId)
|
|
||||||
// if (library) {
|
|
||||||
// commit('setCurrentLibrary', libraryId)
|
|
||||||
// return library
|
|
||||||
// }
|
|
||||||
|
|
||||||
return this.$axios
|
return this.$axios
|
||||||
.$get(`/api/libraries/${libraryId}?include=filterdata`)
|
.$get(`/api/libraries/${libraryId}?include=filterdata`)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export const state = () => ({
|
export const state = () => ({
|
||||||
user: null,
|
user: null,
|
||||||
|
serverConnectionConfig: null,
|
||||||
userAudiobookData: [],
|
userAudiobookData: [],
|
||||||
settings: {
|
settings: {
|
||||||
mobileOrderBy: 'addedAt',
|
mobileOrderBy: 'addedAt',
|
||||||
|
@ -20,6 +21,9 @@ export const getters = {
|
||||||
getToken: (state) => {
|
getToken: (state) => {
|
||||||
return state.user ? state.user.token : null
|
return state.user ? state.user.token : null
|
||||||
},
|
},
|
||||||
|
getServerAddress: (state) => {
|
||||||
|
return state.serverConnectionConfig ? state.serverConnectionConfig.address : null
|
||||||
|
},
|
||||||
getUserLibraryItemProgress: (state) => (libraryItemId) => {
|
getUserLibraryItemProgress: (state) => (libraryItemId) => {
|
||||||
if (!state.user.libraryItemProgress) return null
|
if (!state.user.libraryItemProgress) return null
|
||||||
return state.user.libraryItemProgress.find(li => li.id == libraryItemId)
|
return state.user.libraryItemProgress.find(li => li.id == libraryItemId)
|
||||||
|
@ -102,6 +106,16 @@ export const actions = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
|
logout(state) {
|
||||||
|
state.user = null
|
||||||
|
state.serverConnectionConfig = null
|
||||||
|
},
|
||||||
|
setUser(state, user) {
|
||||||
|
state.user = user
|
||||||
|
},
|
||||||
|
setServerConnectionConfig(state, serverConnectionConfig) {
|
||||||
|
state.serverConnectionConfig = serverConnectionConfig
|
||||||
|
},
|
||||||
setUserAudiobookData(state, abdata) {
|
setUserAudiobookData(state, abdata) {
|
||||||
var index = state.userAudiobookData.findIndex(uab => uab.audiobookId === abdata.audiobookId)
|
var index = state.userAudiobookData.findIndex(uab => uab.audiobookId === abdata.audiobookId)
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
|
@ -116,16 +130,6 @@ export const mutations = {
|
||||||
setAllUserAudiobookData(state, allAbData) {
|
setAllUserAudiobookData(state, allAbData) {
|
||||||
state.userAudiobookData = allAbData
|
state.userAudiobookData = allAbData
|
||||||
},
|
},
|
||||||
setUser(state, user) {
|
|
||||||
state.user = user
|
|
||||||
if (user) {
|
|
||||||
if (user.token) this.$localStore.setToken(user.token)
|
|
||||||
console.log('setUser', user.username)
|
|
||||||
} else {
|
|
||||||
this.$localStore.setToken(null)
|
|
||||||
console.warn('setUser cleared')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setSettings(state, settings) {
|
setSettings(state, settings) {
|
||||||
if (!settings) return
|
if (!settings) return
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue