mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2025-02-15 17:59:20 +01:00
Update:Media item share modal UI/UX and localization #1768
This commit is contained in:
parent
31146082f0
commit
c309856f74
@ -2,44 +2,45 @@
|
|||||||
<modals-modal ref="modal" v-model="show" name="share" :width="600" :height="'unset'" :processing="processing">
|
<modals-modal ref="modal" v-model="show" name="share" :width="600" :height="'unset'" :processing="processing">
|
||||||
<template #outer>
|
<template #outer>
|
||||||
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
<div class="absolute top-0 left-0 p-5 w-2/3 overflow-hidden">
|
||||||
<p class="text-3xl text-white truncate">Share media item</p>
|
<p class="text-3xl text-white truncate">{{ $strings.LabelShare }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="px-6 py-8 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
<div class="px-6 py-8 w-full text-sm rounded-lg bg-bg shadow-lg border border-black-300 overflow-y-auto overflow-x-hidden" style="max-height: 80vh">
|
||||||
<template v-if="currentShare">
|
<template v-if="currentShare">
|
||||||
<div class="w-full py-2">
|
<div class="w-full py-2">
|
||||||
<label class="px-1 text-sm font-semibold block">Share URL</label>
|
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelShareURL }}</label>
|
||||||
<ui-text-input v-model="currentShareUrl" readonly class="text-base h-10" />
|
<ui-text-input v-model="currentShareUrl" show-copy readonly class="text-base h-10" />
|
||||||
</div>
|
</div>
|
||||||
<div class="w-full py-2 px-1">
|
<div class="w-full py-2 px-1">
|
||||||
<p v-if="currentShare.expiresAt" class="text-base">Expires in {{ currentShareTimeRemaining }}</p>
|
<p v-if="currentShare.expiresAt" class="text-base">{{ $getString('MessageShareExpiresIn', [currentShareTimeRemaining]) }}</p>
|
||||||
<p v-else>Permanent</p>
|
<p v-else>{{ $strings.LabelPermanent }}</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<div class="flex items-center justify-between space-x-4">
|
<div class="flex flex-col sm:flex-row items-center justify-between space-y-4 sm:space-y-0 sm:space-x-4 mb-4">
|
||||||
<div class="w-40">
|
<div class="w-full sm:w-48">
|
||||||
<label class="px-1 text-sm font-semibold block">Slug</label>
|
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelSlug }}</label>
|
||||||
<ui-text-input v-model="newShareSlug" class="text-base h-10" />
|
<ui-text-input v-model="newShareSlug" class="text-base h-10" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
<div class="w-80">
|
<div class="w-full sm:w-80">
|
||||||
<label class="px-1 text-sm font-semibold block">Share Duration</label>
|
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelDuration }}</label>
|
||||||
<div class="inline-flex items-center space-x-2">
|
<div class="inline-flex items-center space-x-2">
|
||||||
<div>
|
<div>
|
||||||
<ui-icon-btn icon="remove" :size="10" @click="clickMinus" />
|
<ui-icon-btn icon="remove" :size="10" @click="clickMinus" />
|
||||||
</div>
|
</div>
|
||||||
<ui-text-input v-model="newShareDuration" type="number" text-center no-spinner class="text-center w-28 h-10 text-base" />
|
<ui-text-input v-model="newShareDuration" type="number" text-center no-spinner class="text-center max-w-12 min-w-12 h-10 text-base" />
|
||||||
<div>
|
<div>
|
||||||
<ui-icon-btn icon="add" :size="10" @click="clickPlus" />
|
<ui-icon-btn icon="add" :size="10" @click="clickPlus" />
|
||||||
</div>
|
</div>
|
||||||
|
<div class="w-28">
|
||||||
<ui-dropdown v-model="shareDurationUnit" :items="durationUnits" />
|
<ui-dropdown v-model="shareDurationUnit" :items="durationUnits" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-sm text-gray-300 py-4 px-1">
|
</div>
|
||||||
Share URL will be: <span class="">{{ demoShareUrl }}</span>
|
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareURLWillBe', [demoShareUrl])" />
|
||||||
</p>
|
<p class="text-sm text-gray-300 py-1 px-1" v-html="$getString('MessageShareExpirationWillBe', [expirationDateString])" />
|
||||||
</template>
|
</template>
|
||||||
<div class="flex items-center pt-6">
|
<div class="flex items-center pt-6">
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
@ -72,15 +73,15 @@ export default {
|
|||||||
shareDurationUnit: 'minutes',
|
shareDurationUnit: 'minutes',
|
||||||
durationUnits: [
|
durationUnits: [
|
||||||
{
|
{
|
||||||
text: 'Minutes',
|
text: this.$strings.LabelMinutes,
|
||||||
value: 'minutes'
|
value: 'minutes'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Hours',
|
text: this.$strings.LabelHours,
|
||||||
value: 'hours'
|
value: 'hours'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
text: 'Days',
|
text: this.$strings.LabelDays,
|
||||||
value: 'days'
|
value: 'days'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -116,15 +117,20 @@ export default {
|
|||||||
},
|
},
|
||||||
currentShareTimeRemaining() {
|
currentShareTimeRemaining() {
|
||||||
if (!this.currentShare) return 'Error'
|
if (!this.currentShare) return 'Error'
|
||||||
if (!this.currentShare.expiresAt) return 'Permanent'
|
if (!this.currentShare.expiresAt) return this.$strings.LabelPermanent
|
||||||
const msRemaining = new Date(this.currentShare.expiresAt).valueOf() - Date.now()
|
const msRemaining = new Date(this.currentShare.expiresAt).valueOf() - Date.now()
|
||||||
if (msRemaining <= 0) return 'Expired'
|
if (msRemaining <= 0) return 'Expired'
|
||||||
return this.$elapsedPretty(msRemaining / 1000, true)
|
return this.$elapsedPrettyExtended(msRemaining / 1000, true, false)
|
||||||
},
|
},
|
||||||
expireDurationSeconds() {
|
expireDurationSeconds() {
|
||||||
let shareDuration = Number(this.newShareDuration)
|
let shareDuration = Number(this.newShareDuration)
|
||||||
if (!shareDuration || isNaN(shareDuration)) return 0
|
if (!shareDuration || isNaN(shareDuration)) return 0
|
||||||
return this.newShareDuration * (this.shareDurationUnit === 'minutes' ? 60 : this.shareDurationUnit === 'hours' ? 3600 : 86400)
|
return this.newShareDuration * (this.shareDurationUnit === 'minutes' ? 60 : this.shareDurationUnit === 'hours' ? 3600 : 86400)
|
||||||
|
},
|
||||||
|
expirationDateString() {
|
||||||
|
if (!this.expireDurationSeconds) return this.$strings.LabelPermanent
|
||||||
|
const dateMs = Date.now() + this.expireDurationSeconds * 1000
|
||||||
|
return this.$formatDatetime(dateMs, this.$store.state.serverSettings.dateFormat, this.$store.state.serverSettings.timeFormat)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -1,12 +1,33 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="wrapper" class="relative">
|
<div ref="wrapper" class="relative">
|
||||||
<input :id="inputId" :name="inputName" ref="input" v-model="inputValue" :type="actualType" :step="step" :min="min" :readonly="readonly" :disabled="disabled" :placeholder="placeholder" dir="auto" class="rounded bg-primary text-gray-200 focus:border-gray-300 focus:bg-bg focus:outline-none border border-gray-600 h-full w-full" :class="classList" @keyup="keyup" @change="change" @focus="focused" @blur="blurred" />
|
<input
|
||||||
|
:id="inputId"
|
||||||
|
:name="inputName"
|
||||||
|
ref="input"
|
||||||
|
v-model="inputValue"
|
||||||
|
:type="actualType"
|
||||||
|
:step="step"
|
||||||
|
:min="min"
|
||||||
|
:readonly="readonly"
|
||||||
|
:disabled="disabled"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
dir="auto"
|
||||||
|
class="rounded bg-primary text-gray-200 focus:border-gray-300 focus:bg-bg focus:outline-none border border-gray-600 h-full w-full"
|
||||||
|
:class="classList"
|
||||||
|
@keyup="keyup"
|
||||||
|
@change="change"
|
||||||
|
@focus="focused"
|
||||||
|
@blur="blurred"
|
||||||
|
/>
|
||||||
<div v-if="clearable && inputValue" class="absolute top-0 right-0 h-full px-2 flex items-center justify-center">
|
<div v-if="clearable && inputValue" class="absolute top-0 right-0 h-full px-2 flex items-center justify-center">
|
||||||
<span class="material-icons text-gray-300 cursor-pointer" style="font-size: 1.1rem" @click.stop.prevent="clear">close</span>
|
<span class="material-icons text-gray-300 cursor-pointer" style="font-size: 1.1rem" @click.stop.prevent="clear">close</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="type === 'password' && isHovering" class="absolute top-0 right-0 h-full px-4 flex items-center justify-center">
|
<div v-if="type === 'password' && isHovering" class="absolute top-0 right-0 h-full px-4 flex items-center justify-center">
|
||||||
<span class="material-icons-outlined text-gray-400 cursor-pointer text-lg" @click.stop.prevent="showPassword = !showPassword">{{ !showPassword ? 'visibility' : 'visibility_off' }}</span>
|
<span class="material-icons-outlined text-gray-400 cursor-pointer text-lg" @click.stop.prevent="showPassword = !showPassword">{{ !showPassword ? 'visibility' : 'visibility_off' }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else-if="showCopy" class="absolute top-0 right-0 h-full px-4 flex items-center justify-center">
|
||||||
|
<span class="material-icons-outlined text-gray-400 cursor-pointer text-lg" @click.stop.prevent="copyToClipboard">{{ !hasCopied ? 'content_copy' : 'done' }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -34,6 +55,7 @@ export default {
|
|||||||
clearable: Boolean,
|
clearable: Boolean,
|
||||||
inputId: String,
|
inputId: String,
|
||||||
inputName: String,
|
inputName: String,
|
||||||
|
showCopy: Boolean,
|
||||||
step: [String, Number],
|
step: [String, Number],
|
||||||
min: [String, Number]
|
min: [String, Number]
|
||||||
},
|
},
|
||||||
@ -41,7 +63,8 @@ export default {
|
|||||||
return {
|
return {
|
||||||
showPassword: false,
|
showPassword: false,
|
||||||
isHovering: false,
|
isHovering: false,
|
||||||
isFocused: false
|
isFocused: false,
|
||||||
|
hasCopied: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@ -67,6 +90,15 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
copyToClipboard() {
|
||||||
|
if (this.hasCopied) return
|
||||||
|
this.$copyToClipboard(this.inputValue).then((success) => {
|
||||||
|
this.hasCopied = success
|
||||||
|
setTimeout(() => {
|
||||||
|
this.hasCopied = false
|
||||||
|
}, 2000)
|
||||||
|
})
|
||||||
|
},
|
||||||
clear() {
|
clear() {
|
||||||
this.inputValue = ''
|
this.inputValue = ''
|
||||||
this.$emit('clear')
|
this.$emit('clear')
|
||||||
|
@ -442,7 +442,7 @@ export default {
|
|||||||
|
|
||||||
if (this.userIsAdminOrUp && !this.isPodcast) {
|
if (this.userIsAdminOrUp && !this.isPodcast) {
|
||||||
items.push({
|
items.push({
|
||||||
text: 'Share',
|
text: this.$strings.LabelShare,
|
||||||
action: 'share'
|
action: 'share'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,11 @@
|
|||||||
<div id="page-wrapper" class="w-full h-screen max-h-screen overflow-hidden">
|
<div id="page-wrapper" class="w-full h-screen max-h-screen overflow-hidden">
|
||||||
<div class="w-full h-full flex items-center justify-center">
|
<div class="w-full h-full flex items-center justify-center">
|
||||||
<div class="w-full p-2 sm:p-4 md:p-8">
|
<div class="w-full p-2 sm:p-4 md:p-8">
|
||||||
<div :style="{ width: coverWidth + 'px', height: coverHeight + 'px' }" class="mx-auto overflow-hidden rounded-xl my-2">
|
<div v-if="!isMobileLandscape" :style="{ width: coverWidth + 'px', height: coverHeight + 'px' }" class="mx-auto overflow-hidden rounded-xl my-2">
|
||||||
<img :src="coverUrl" class="object-contain w-full h-full" />
|
<img :src="coverUrl" class="object-contain w-full h-full" />
|
||||||
</div>
|
</div>
|
||||||
<p class="text-2xl md:text-3xl font-semibold text-center mb-1">{{ mediaItemShare.playbackSession.displayTitle || 'No title' }}</p>
|
<p class="text-2xl lg:text-3xl font-semibold text-center mb-1 line-clamp-2">{{ mediaItemShare.playbackSession.displayTitle || 'No title' }}</p>
|
||||||
<p v-if="mediaItemShare.playbackSession.displayAuthor" class="text-xl text-slate-400 font-semibold text-center mb-1">{{ mediaItemShare.playbackSession.displayAuthor }}</p>
|
<p v-if="mediaItemShare.playbackSession.displayAuthor" class="text-lg lg:text-xl text-slate-400 font-semibold text-center mb-1 truncate">{{ mediaItemShare.playbackSession.displayAuthor }}</p>
|
||||||
|
|
||||||
<div class="w-full pt-16">
|
<div class="w-full pt-16">
|
||||||
<player-ui ref="audioPlayer" :chapters="chapters" :paused="isPaused" :loading="!hasLoaded" :is-podcast="false" hide-bookmarks hide-sleep-timer @playPause="playPause" @jumpForward="jumpForward" @jumpBackward="jumpBackward" @setVolume="setVolume" @setPlaybackRate="setPlaybackRate" @seek="seek" />
|
<player-ui ref="audioPlayer" :chapters="chapters" :paused="isPaused" :loading="!hasLoaded" :is-podcast="false" hide-bookmarks hide-sleep-timer @playPause="playPause" @jumpForward="jumpForward" @jumpBackward="jumpBackward" @setVolume="setVolume" @setPlaybackRate="setPlaybackRate" @seek="seek" />
|
||||||
@ -79,6 +79,9 @@ export default {
|
|||||||
const coverAspectRatio = this.playbackSession.coverAspectRatio
|
const coverAspectRatio = this.playbackSession.coverAspectRatio
|
||||||
return coverAspectRatio === this.$constants.BookCoverAspectRatio.STANDARD ? 1.6 : 1
|
return coverAspectRatio === this.$constants.BookCoverAspectRatio.STANDARD ? 1.6 : 1
|
||||||
},
|
},
|
||||||
|
isMobileLandscape() {
|
||||||
|
return this.windowWidth > this.windowHeight && this.windowHeight < 450
|
||||||
|
},
|
||||||
coverWidth() {
|
coverWidth() {
|
||||||
const availableCoverWidth = Math.min(450, this.windowWidth - 32)
|
const availableCoverWidth = Math.min(450, this.windowWidth - 32)
|
||||||
const availableCoverHeight = Math.min(450, this.windowHeight - 250)
|
const availableCoverHeight = Math.min(450, this.windowHeight - 250)
|
||||||
|
@ -258,6 +258,7 @@
|
|||||||
"LabelCurrently": "Currently:",
|
"LabelCurrently": "Currently:",
|
||||||
"LabelCustomCronExpression": "Custom Cron Expression:",
|
"LabelCustomCronExpression": "Custom Cron Expression:",
|
||||||
"LabelDatetime": "Datetime",
|
"LabelDatetime": "Datetime",
|
||||||
|
"LabelDays": "Days",
|
||||||
"LabelDeleteFromFileSystemCheckbox": "Delete from file system (uncheck to only remove from database)",
|
"LabelDeleteFromFileSystemCheckbox": "Delete from file system (uncheck to only remove from database)",
|
||||||
"LabelDescription": "Description",
|
"LabelDescription": "Description",
|
||||||
"LabelDeselectAll": "Deselect All",
|
"LabelDeselectAll": "Deselect All",
|
||||||
@ -321,6 +322,7 @@
|
|||||||
"LabelHighestPriority": "Highest priority",
|
"LabelHighestPriority": "Highest priority",
|
||||||
"LabelHost": "Host",
|
"LabelHost": "Host",
|
||||||
"LabelHour": "Hour",
|
"LabelHour": "Hour",
|
||||||
|
"LabelHours": "Hours",
|
||||||
"LabelIcon": "Icon",
|
"LabelIcon": "Icon",
|
||||||
"LabelImageURLFromTheWeb": "Image URL from the web",
|
"LabelImageURLFromTheWeb": "Image URL from the web",
|
||||||
"LabelInProgress": "In Progress",
|
"LabelInProgress": "In Progress",
|
||||||
@ -371,6 +373,7 @@
|
|||||||
"LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
|
"LabelMetadataOrderOfPrecedenceDescription": "Higher priority metadata sources will override lower priority metadata sources",
|
||||||
"LabelMetadataProvider": "Metadata Provider",
|
"LabelMetadataProvider": "Metadata Provider",
|
||||||
"LabelMinute": "Minute",
|
"LabelMinute": "Minute",
|
||||||
|
"LabelMinutes": "Minutes",
|
||||||
"LabelMissing": "Missing",
|
"LabelMissing": "Missing",
|
||||||
"LabelMissingEbook": "Has no ebook",
|
"LabelMissingEbook": "Has no ebook",
|
||||||
"LabelMissingSupplementaryEbook": "Has no supplementary ebook",
|
"LabelMissingSupplementaryEbook": "Has no supplementary ebook",
|
||||||
@ -410,6 +413,7 @@
|
|||||||
"LabelOverwrite": "Overwrite",
|
"LabelOverwrite": "Overwrite",
|
||||||
"LabelPassword": "Password",
|
"LabelPassword": "Password",
|
||||||
"LabelPath": "Path",
|
"LabelPath": "Path",
|
||||||
|
"LabelPermanent": "Permanent",
|
||||||
"LabelPermissionsAccessAllLibraries": "Can Access All Libraries",
|
"LabelPermissionsAccessAllLibraries": "Can Access All Libraries",
|
||||||
"LabelPermissionsAccessAllTags": "Can Access All Tags",
|
"LabelPermissionsAccessAllTags": "Can Access All Tags",
|
||||||
"LabelPermissionsAccessExplicitContent": "Can Access Explicit Content",
|
"LabelPermissionsAccessExplicitContent": "Can Access Explicit Content",
|
||||||
@ -507,6 +511,8 @@
|
|||||||
"LabelSettingsStoreMetadataWithItem": "Store metadata with item",
|
"LabelSettingsStoreMetadataWithItem": "Store metadata with item",
|
||||||
"LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders",
|
"LabelSettingsStoreMetadataWithItemHelp": "By default metadata files are stored in /metadata/items, enabling this setting will store metadata files in your library item folders",
|
||||||
"LabelSettingsTimeFormat": "Time Format",
|
"LabelSettingsTimeFormat": "Time Format",
|
||||||
|
"LabelShare": "Share",
|
||||||
|
"LabelShareURL": "Share URL",
|
||||||
"LabelShowAll": "Show All",
|
"LabelShowAll": "Show All",
|
||||||
"LabelShowSeconds": "Show seconds",
|
"LabelShowSeconds": "Show seconds",
|
||||||
"LabelSize": "Size",
|
"LabelSize": "Size",
|
||||||
@ -716,6 +722,9 @@
|
|||||||
"MessageSelected": "{0} selected",
|
"MessageSelected": "{0} selected",
|
||||||
"MessageServerCouldNotBeReached": "Server could not be reached",
|
"MessageServerCouldNotBeReached": "Server could not be reached",
|
||||||
"MessageSetChaptersFromTracksDescription": "Set chapters using each audio file as a chapter and chapter title as the audio file name",
|
"MessageSetChaptersFromTracksDescription": "Set chapters using each audio file as a chapter and chapter title as the audio file name",
|
||||||
|
"MessageShareExpirationWillBe": "Expiration will be <strong>{0}</strong>",
|
||||||
|
"MessageShareExpiresIn": "Expires in {0}",
|
||||||
|
"MessageShareURLWillBe": "Share URL will be <strong>{0}</strong>",
|
||||||
"MessageStartPlaybackAtTime": "Start playback for \"{0}\" at {1}?",
|
"MessageStartPlaybackAtTime": "Start playback for \"{0}\" at {1}?",
|
||||||
"MessageThinking": "Thinking...",
|
"MessageThinking": "Thinking...",
|
||||||
"MessageUploaderItemFailed": "Failed to upload",
|
"MessageUploaderItemFailed": "Failed to upload",
|
||||||
|
Loading…
Reference in New Issue
Block a user