Mobile UI adjustments

This commit is contained in:
advplyr 2022-02-09 11:19:02 -06:00
parent 18a095c6e4
commit 70c5db4534
14 changed files with 50 additions and 213 deletions

View File

@ -11,7 +11,7 @@
<p>Series</p>
</nuxt-link>
</div>
<div id="toolbar" class="absolute top-10 md:top-0 left-0 w-full h-10 md:h-full z-30 flex items-center px-2 md:px-8">
<div id="toolbar" class="absolute top-10 md:top-0 left-0 w-full h-10 md:h-full z-30 flex items-center justify-end md:justify-start px-2 md:px-8">
<template v-if="page !== 'search' && !isHome">
<p v-if="!selectedSeries" class="font-book hidden md:block">{{ numShowing }} {{ entityName }}</p>
<div v-else class="items-center hidden md:flex">
@ -25,11 +25,11 @@
<span class="font-mono">{{ numShowing }}</span>
</div>
</div>
<div class="flex-grow hidden md:inline-block" />
<div class="flex-grow hidden sm:inline-block" />
<ui-checkbox v-show="showSortFilters" v-model="settings.collapseSeries" label="Collapse Series" checkbox-bg="bg" check-color="white" small class="mr-2" @input="updateCollapseSeries" />
<controls-filter-select v-show="showSortFilters" v-model="settings.filterBy" class="w-48 h-7.5 ml-4" @change="updateFilter" />
<controls-order-select v-show="showSortFilters" v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-48 h-7.5 ml-4" @change="updateOrder" />
<controls-filter-select v-show="showSortFilters" v-model="settings.filterBy" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateFilter" />
<controls-order-select v-show="showSortFilters" v-model="settings.orderBy" :descending.sync="settings.orderDesc" class="w-36 sm:w-44 md:w-48 h-7.5 ml-1 sm:ml-4" @change="updateOrder" />
<!-- <div v-show="showSortFilters" class="h-7 ml-4 flex border border-white border-opacity-25 rounded-md">
<div class="h-full px-2 text-white flex items-center rounded-l-md hover:bg-primary hover:bg-opacity-75 cursor-pointer" :class="isGridMode ? 'bg-primary' : 'text-opacity-70'" @click="$emit('update:viewMode', 'grid')">
<span class="material-icons" style="font-size: 1.4rem">view_module</span>

View File

@ -9,7 +9,7 @@
<div v-show="routeName === route.iod" class="h-full w-0.5 bg-yellow-400 absolute top-0 left-0" />
</nuxt-link>
<div class="w-full h-10 px-4 border-t border-black border-opacity-20 absolute left-0 flex flex-col justify-center" :style="{ bottom: streamAudiobook && windowHeight > 700 && !isMobile ? '300px' : '65px' }">
<div class="w-full h-10 px-4 border-t border-black border-opacity-20 absolute left-0 flex flex-col justify-center" :style="{ bottom: streamAudiobook && isMobileLandscape ? '300px' : '65px' }">
<p class="font-mono text-sm">v{{ $config.version }}</p>
<a v-if="hasUpdate" :href="githubTagUrl" target="_blank" class="text-warning text-sm">Update available: {{ latestVersion }}</a>
</div>
@ -22,10 +22,7 @@ export default {
isOpen: Boolean
},
data() {
return {
windowWidth: 0,
windowHeight: 0
}
return {}
},
computed: {
userIsRoot() {
@ -88,7 +85,10 @@ export default {
return classes.join(' ')
},
isMobile() {
return this.windowWidth < 758
return this.$store.state.globals.isMobile
},
isMobileLandscape() {
return this.$store.state.globals.isMobileLandscape
},
drawerOpen() {
if (this.isMobile) return this.isOpen
@ -120,19 +120,7 @@ export default {
},
closeDrawer() {
this.$emit('update:isOpen', false)
},
resize() {
this.windowWidth = window.innerWidth
this.windowHeight = window.innerHeight
}
},
mounted() {
window.addEventListener('resize', this.resize)
this.windowWidth = window.innerWidth
this.windowHeight = window.innerHeight
},
beforeDestroy() {
window.removeEventListener('resize', this.resize)
}
}
</script>

View File

@ -3,7 +3,7 @@
<div class="rounded-sm h-full relative" :style="{ padding: `0px ${paddingX}px` }" @mouseover="mouseoverCard" @mouseleave="mouseleaveCard" @click="clickCard">
<nuxt-link :to="groupTo" class="cursor-pointer">
<div class="w-full h-full relative" :class="isHovering ? 'bg-black-400' : 'bg-primary'" :style="{ height: coverHeight + 'px', width: coverWidth + 'px' }">
<covers-group-cover ref="groupcover" :id="seriesId" :name="groupName" :is-categorized="isCategorized" :group-to="groupTo" :type="groupType" :book-items="bookItems" :width="coverWidth" :height="coverHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" />
<covers-group-cover ref="groupcover" :id="seriesId" :name="groupName" :group-to="groupTo" :type="groupType" :book-items="bookItems" :width="coverWidth" :height="coverHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" />
<div v-if="hasValidCovers" class="bg-black bg-opacity-60 absolute top-0 left-0 w-full h-full flex items-center justify-center text-center transition-opacity z-30" :class="isHovering ? '' : 'opacity-0'" :style="{ padding: `${sizeMultiplier}rem` }">
<p class="font-book" :style="{ fontSize: sizeMultiplier + 'rem' }">{{ groupName }}</p>

View File

@ -36,7 +36,7 @@
<div v-if="!booksInSeries" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
<!-- Overlay is not shown if collapsing series in library -->
<div v-show="!booksInSeries && audiobook && (isHovering || isSelectionMode || isMoreMenuOpen)" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded" :class="overlayWrapperClasslist">
<div v-show="!booksInSeries && audiobook && (isHovering || isSelectionMode || isMoreMenuOpen)" class="w-full h-full absolute top-0 left-0 z-10 bg-black rounded hidden md:block" :class="overlayWrapperClasslist">
<div v-show="showPlayButton" class="h-full flex items-center justify-center pointer-events-none">
<div class="hover:text-gray-200 hover:scale-110 transform duration-200 pointer-events-auto" @click.stop.prevent="play">
<span class="material-icons" :style="{ fontSize: playIconFontSize + 'rem' }">play_circle_filled</span>

View File

@ -19,7 +19,7 @@
<template v-for="item in items">
<li :key="item.value" class="text-gray-50 select-none relative py-2 pr-9 cursor-pointer hover:bg-black-400" :class="item.value === selected ? 'bg-primary bg-opacity-50' : ''" role="option" @click="clickedOption(item)">
<div class="flex items-center justify-between">
<span class="font-normal ml-3 block truncate">{{ item.text }}</span>
<span class="font-normal ml-3 block truncate text-sm md:text-base">{{ item.text }}</span>
</div>
<div v-if="item.sublist" class="absolute right-1 top-0 bottom-0 h-full flex items-center">
<span class="material-icons">arrow_right</span>

View File

@ -1,5 +1,5 @@
<template>
<div ref="wrapper" :style="{ height: height + 'px', width: width + 'px' }" class="relative" @mouseover="mouseoverCover" @mouseleave="mouseleaveCover">
<div ref="wrapper" :style="{ height: height + 'px', width: width + 'px' }" class="relative">
<div v-if="noValidCovers" class="absolute top-0 left-0 w-full h-full flex items-center justify-center box-shadow-book" :style="{ padding: `${sizeMultiplier}rem` }">
<p :style="{ fontSize: sizeMultiplier + 'rem' }">{{ name }}</p>
</div>
@ -18,7 +18,6 @@ export default {
width: Number,
height: Number,
groupTo: String,
isCategorized: Boolean,
bookCoverAspectRatio: Number
},
data() {
@ -33,7 +32,6 @@ export default {
isFannedOut: false,
isDetached: false,
isAttaching: false,
windowWidth: 0,
isInit: false
}
},
@ -56,10 +54,6 @@ export default {
showExperimentalFeatures() {
return this.store.state.showExperimentalFeatures
},
showCoverFan() {
// return this.showExperimentalFeatures && this.windowWidth > 1024 && !this.isCategorized
return false
},
store() {
return this.$store || this.$nuxt.$store
},
@ -68,140 +62,6 @@ export default {
}
},
methods: {
mouseoverCover() {
if (this.showCoverFan) this.setHover(true)
},
mouseleaveCover() {
if (this.showCoverFan) this.setHover(false)
},
detchCoverWrapper() {
if (!this.coverWrapperEl || !this.$refs.wrapper || this.isDetached) return
this.coverWrapperEl.remove()
this.isDetached = true
document.body.appendChild(this.coverWrapperEl)
this.coverWrapperEl.addEventListener('mouseleave', this.mouseleaveCover)
this.coverWrapperEl.style.position = 'absolute'
this.coverWrapperEl.style.zIndex = 40
this.updatePosition()
},
attachCoverWrapper() {
if (!this.coverWrapperEl || !this.$refs.wrapper || !this.isDetached) return
this.coverWrapperEl.remove()
this.coverWrapperEl.style.position = 'relative'
this.coverWrapperEl.style.left = 'unset'
this.coverWrapperEl.style.top = 'unset'
this.coverWrapperEl.style.width = this.$refs.wrapper.clientWidth + 'px'
this.$refs.wrapper.appendChild(this.coverWrapperEl)
this.isDetached = false
},
updatePosition() {
var rect = this.$refs.wrapper.getBoundingClientRect()
this.coverWrapperEl.style.top = rect.top + window.scrollY + 'px'
this.coverWrapperEl.style.left = rect.left + window.scrollX + 4 + 'px'
this.coverWrapperEl.style.height = rect.height + 'px'
this.coverWrapperEl.style.width = rect.width + 'px'
},
setHover(val) {
if (this.isAttaching) return
if (val && !this.isHovering) {
this.detchCoverWrapper()
this.fanOutCovers()
} else if (!val && this.isHovering) {
this.isAttaching = true
// this.reverseFan()
// setTimeout(() => {
// this.attachCoverWrapper()
// this.isAttaching = false
// }, 100)
}
this.isHovering = val
},
fanOutCovers() {
if (this.coverImageEls.length < 2 || this.isFannedOut) return
this.isFannedOut = true
var fanCoverWidth = this.coverWidth * 0.75
var maximumWidth = window.innerWidth - 80
var totalFanWidth = (this.coverImageEls.length + 1) * fanCoverWidth
// If Fan width is too large, set new fan cover width
if (totalFanWidth > maximumWidth) {
fanCoverWidth = maximumWidth / (this.coverImageEls.length + 1)
}
var fanWidth = (this.coverImageEls.length - 1) * fanCoverWidth
var offsetLeft = (-1 * fanWidth) / 2
var rect = this.$refs.wrapper.getBoundingClientRect()
// If fan is going off page left or right, make adjustment
var leftEdge = rect.left + offsetLeft
var rightEdge = rect.left + rect.width - offsetLeft
if (leftEdge < 0) {
offsetLeft += leftEdge * -1
}
if (rightEdge + 80 > window.innerWidth) {
var difference = rightEdge + 80 - window.innerWidth
offsetLeft -= difference / 2
}
for (let i = 0; i < this.coverImageEls.length; i++) {
var coverEl = this.coverImageEls[i]
// Series name card pop out further
if (i === this.coverImageEls.length - 1) {
offsetLeft += fanCoverWidth * 0.25
}
coverEl.style.transform = `translateX(${offsetLeft}px)`
offsetLeft += fanCoverWidth
var coverOverlay = document.createElement('div')
coverOverlay.className = 'absolute top-0 left-0 w-full h-full hover:bg-black hover:bg-opacity-40 text-white text-opacity-0 hover:text-opacity-100 flex items-center justify-center cursor-pointer'
if (coverEl.dataset.volumeNumber) {
var pEl = document.createElement('p')
pEl.className = 'text-2xl'
pEl.textContent = `#${coverEl.dataset.volumeNumber}`
coverOverlay.appendChild(pEl)
}
if (coverEl.dataset.audiobookId) {
let audiobookId = coverEl.dataset.audiobookId
coverOverlay.addEventListener('click', (e) => {
this.router.push(`/audiobook/${audiobookId}`)
e.stopPropagation()
e.preventDefault()
})
} else {
// Is Series
coverOverlay.addEventListener('click', (e) => {
this.router.push(this.groupTo)
e.stopPropagation()
e.preventDefault()
})
}
coverEl.appendChild(coverOverlay)
}
},
reverseFan() {
if (this.coverImageEls.length < 2 || !this.isFannedOut) return
this.isFannedOut = false
for (let i = 0; i < this.coverImageEls.length; i++) {
var coverEl = this.coverImageEls[i]
coverEl.style.transform = 'translateX(0px)'
if (coverEl.lastChild) coverEl.lastChild.remove() // Remove cover overlay
}
},
getCoverUrl(book) {
return this.store.getters['audiobooks/getBookCoverSrc'](book, '')
},
@ -302,7 +162,7 @@ export default {
}
this.noValidCovers = false
validCovers = validCovers.slice(0, 10);
validCovers = validCovers.slice(0, 10)
var coverWidth = this.width
var widthPer = this.width
@ -328,12 +188,6 @@ export default {
coverImageEls.push(img)
}
if (this.showCoverFan) {
var seriesNameCover = this.createSeriesNameCover(offsetLeft)
outerdiv.appendChild(seriesNameCover)
coverImageEls.push(seriesNameCover)
}
this.coverImageEls = coverImageEls
if (this.$refs.wrapper) {
@ -342,9 +196,7 @@ export default {
}
}
},
mounted() {
this.windowWidth = window.innerWidth
},
mounted() {},
beforeDestroy() {
if (this.coverWrapperEl) this.coverWrapperEl.remove()
if (this.coverImageEls && this.coverImageEls.length) {

View File

@ -9,22 +9,21 @@
<table id="backups">
<tr>
<th>File</th>
<th class="w-56">Datetime</th>
<th class="w-28">Size</th>
<th class="hidden sm:block w-32 md:w-56">Datetime</th>
<th class="hidden sm:block w-20 md:w-28">Size</th>
<th class="w-36"></th>
</tr>
<tr v-for="backup in backups" :key="backup.id">
<td>
<p class="truncate">/{{ backup.path.replace(/\\/g, '/') }}</p>
<p class="truncate text-xs sm:text-sm md:text-base">/{{ backup.path.replace(/\\/g, '/') }}</p>
</td>
<td class="font-sans">{{ backup.datePretty }}</td>
<td class="font-mono">{{ $bytesPretty(backup.fileSize) }}</td>
<td class="hidden sm:block font-sans text-base">{{ backup.datePretty }}</td>
<td class="hidden sm:block font-mono md:text-base text-xs">{{ $bytesPretty(backup.fileSize) }}</td>
<td>
<div class="w-full flex items-center justify-center">
<div class="w-full flex flex-row items-center justify-center">
<ui-btn small color="primary" @click="applyBackup(backup)">Apply</ui-btn>
<a :href="`/metadata/${backup.path.replace(/%/g, '%25').replace(/#/g, '%23')}?token=${userToken}`" class="mx-1 pt-0.5 hover:text-opacity-100 text-opacity-70 text-white" download><span class="material-icons text-xl">download</span></a>
<!-- <span class="material-icons text-xl hover:text-opacity-100 text-opacity-70 text-white cursor-pointer mx-1" @click="downloadBackup">download</span> -->
<span class="material-icons text-xl hover:text-error hover:text-opacity-100 text-opacity-70 text-white cursor-pointer mx-1" @click="deleteBackupClick(backup)">delete</span>
</div>

View File

@ -43,7 +43,7 @@ export default {
return classes.join(' ')
},
labelClass() {
if (this.small) return 'text-sm'
if (this.small) return 'text-xs md:text-sm'
return ''
},
svgClass() {

View File

@ -499,12 +499,17 @@ export default {
this.$eventBus.$emit('player-hotkey', name)
e.preventDefault()
}
},
resize() {
this.$store.commit('globals/updateWindowSize', { width: window.innerWidth, height: window.innerHeight })
}
},
beforeMount() {
this.initializeSocket()
},
mounted() {
this.resize()
window.addEventListener('resize', this.resize)
window.addEventListener('keydown', this.keyDown)
this.$store.dispatch('libraries/load')
@ -527,6 +532,7 @@ export default {
}
},
beforeDestroy() {
window.removeEventListener('resize', this.resize)
window.removeEventListener('keydown', this.keyDown)
}
}

View File

@ -1,15 +1,15 @@
<template>
<div id="page-wrapper" class="bg-bg page overflow-hidden" :class="streamAudiobook ? 'streaming' : ''">
<div class="w-full h-full overflow-y-auto px-2 py-6 md:p-8">
<div class="flex flex-col sm:flex-row max-w-6xl mx-auto">
<div class="w-full flex justify-center md:block sm:w-32 md:w-52" style="min-width: 208px">
<div class="flex flex-col md:flex-row max-w-6xl mx-auto">
<div class="w-full flex justify-center md:block md:w-52" style="min-width: 208px">
<div class="relative" style="height: fit-content">
<covers-book-cover :audiobook="audiobook" :width="bookCoverWidth" :book-cover-aspect-ratio="bookCoverAspectRatio" />
<div class="absolute bottom-0 left-0 h-1.5 bg-yellow-400 shadow-sm z-10" :class="userIsRead ? 'bg-success' : 'bg-yellow-400'" :style="{ width: 208 * progressPercent + 'px' }"></div>
</div>
</div>
<div class="flex-grow px-2 py-6 md:py-0 md:px-10">
<div class="flex">
<div class="flex justify-center">
<div class="mb-4">
<div class="flex sm:items-end flex-col sm:flex-row">
<h1 class="text-2xl md:text-3xl font-sans">
@ -73,7 +73,7 @@
</div>
</div>
</div>
<div class="flex-grow" />
<div class="hidden md:block flex-grow" />
</div>
<!-- Alerts -->
@ -82,7 +82,7 @@
<p class="ml-4">Book has no audio tracks but has valid ebook files. The e-reader is experimental and can be turned on in config.</p>
</div>
<div v-if="progressPercent > 0 && progressPercent < 1" class="px-4 py-2 mt-4 bg-primary text-sm font-semibold rounded-md text-gray-200 relative max-w-max" :class="resettingProgress ? 'opacity-25' : ''">
<div v-if="progressPercent > 0 && progressPercent < 1" class="px-4 py-2 mt-4 bg-primary text-sm font-semibold rounded-md text-gray-200 relative max-w-max mx-auto md:mx-0" :class="resettingProgress ? 'opacity-25' : ''">
<p class="leading-6">Your Progress: {{ Math.round(progressPercent * 100) }}%</p>
<p 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">
@ -90,7 +90,7 @@
</div>
</div>
<div class="flex items-center pt-4">
<div class="flex items-center justify-center md:justify-start pt-4">
<ui-btn v-if="showPlayButton" :disabled="streaming" color="success" :padding-x="4" small class="flex items-center h-9 mr-2" @click="startStream">
<span v-show="!streaming" class="material-icons -ml-2 pr-1 text-white">play_arrow</span>
{{ streaming ? 'Streaming' : 'Play' }}
@ -177,8 +177,7 @@ export default {
return {
isRead: false,
resettingProgress: false,
isProcessingReadUpdate: false,
windowWidth: 0
isProcessingReadUpdate: false
}
},
watch: {
@ -197,7 +196,7 @@ export default {
return this.coverAspectRatio === this.$constants.BookCoverAspectRatio.SQUARE ? 1 : 1.6
},
bookCoverWidth() {
return this.windowWidth < 800 ? 176 : 208
return 208
},
isDeveloperMode() {
return this.$store.state.developerMode
@ -453,14 +452,9 @@ export default {
collectionsClick() {
this.$store.commit('setSelectedAudiobook', this.audiobook)
this.$store.commit('globals/setShowUserCollectionsModal', true)
},
resize() {
this.windowWidth = window.innerWidth
}
},
mounted() {
this.windowWidth = window.innerWidth
window.addEventListener('resize', this.resize)
this.$store.commit('audiobooks/addListener', { id: 'audiobook', audiobookId: this.audiobookId, meth: this.audiobookUpdated })
// use this audiobooks library id as the current
@ -469,7 +463,6 @@ export default {
}
},
beforeDestroy() {
window.removeEventListener('resize', this.resize)
this.$store.commit('audiobooks/removeListener', 'audiobook')
}
}

View File

@ -1,14 +1,14 @@
<template>
<div id="page-wrapper" class="page p-6 overflow-y-auto" :class="streamAudiobook ? 'streaming' : ''">
<div id="page-wrapper" class="page overflow-y-auto" :class="streamAudiobook ? 'streaming' : ''">
<div class="w-full max-w-4xl mx-auto">
<div class="mb-4 flex items-end">
<p class="text-2xl mr-4">Logger</p>
<div class="mb-4 flex flex-col sm:flex-row items-start sm:items-end">
<p class="text-2xl mr-4 mb-2 sm:mb-0">Logger</p>
<ui-text-input ref="input" v-model="search" placeholder="Search filter.." @input="inputUpdate" clearable class="w-40 h-8 text-sm" />
<ui-text-input ref="input" v-model="search" placeholder="Search filter.." @input="inputUpdate" clearable class="w-full sm:w-40 h-8 text-sm mb-2 sm:mb-0" />
<div class="flex-grow" />
<div class="w-44">
<div class="w-full sm:w-44">
<ui-dropdown v-model="newServerSettings.logLevel" label="Server Log Level" :items="logLevelItems" @input="logLevelUpdated" />
</div>
</div>

View File

@ -30,7 +30,7 @@
</div>
</div>
</div>
<div class="flex">
<div class="flex flex-col md:flex-row">
<stats-daily-listening-chart :listening-stats="listeningStats" />
<div class="w-80 my-6 mx-auto">
<h1 class="text-2xl mb-4 font-book">Recent Listening Sessions</h1>

View File

@ -143,6 +143,5 @@ export {
export default ({ app }, inject) => {
app.$decode = decode
app.$encode = encode
// app.$isDev = process.env.NODE_ENV !== 'production'
inject('isDev', process.env.NODE_ENV !== 'production')
}

View File

@ -1,5 +1,7 @@
export const state = () => ({
isMobile: false,
isMobileLandscape: false,
showBatchUserCollectionModal: false,
showUserCollectionsModal: false,
showEditCollectionModal: false,
@ -7,15 +9,13 @@ export const state = () => ({
showBookshelfTextureModal: false
})
export const getters = {
}
export const actions = {
}
export const getters = {}
export const mutations = {
updateWindowSize(state, { width, height }) {
state.isMobile = width < 768 || height < 768
state.isMobileLandscape = state.isMobile && height > width
},
setShowUserCollectionsModal(state, val) {
state.showBatchUserCollectionModal = false
state.showUserCollectionsModal = val