Compare commits

...

4 Commits

Author SHA1 Message Date
advplyr
abb5bd3a2d Update string order 2025-01-23 17:58:53 -06:00
advplyr
79acc41d16 Add populate from buttons to batch edit 2025-01-23 17:49:58 -06:00
advplyr
598a93d224 Update copy to clipboard buttons to be standardized 2025-01-22 17:56:46 -06:00
advplyr
c3c846f82d Update rss feed copy to clipboard to show checkmark instead of toast 2025-01-21 17:58:10 -06:00
10 changed files with 128 additions and 44 deletions

View File

@@ -90,8 +90,8 @@
<div class="relative">
<ui-textarea-with-label :value="prettyFfprobeData" readonly :rows="30" class="text-xs" />
<button class="absolute top-4 right-4" :class="copiedToClipboard ? 'text-success' : 'text-white/50 hover:text-white/80'" @click.stop="copyFfprobeData">
<span class="material-symbols">{{ copiedToClipboard ? 'check' : 'content_copy' }}</span>
<button class="absolute top-4 right-4" :class="hasCopied ? 'text-success' : 'text-gray-400 hover:text-white'" @click.stop="copyToClipboard">
<span class="material-symbols">{{ hasCopied ? 'done' : 'content_copy' }}</span>
</button>
</div>
</div>
@@ -113,14 +113,13 @@ export default {
return {
probingFile: false,
ffprobeData: null,
copiedToClipboard: false
hasCopied: null
}
},
watch: {
show(newVal) {
if (newVal) {
this.ffprobeData = null
this.copiedToClipboard = false
this.probingFile = false
}
}
@@ -165,8 +164,13 @@ export default {
this.probingFile = false
})
},
async copyFfprobeData() {
this.copiedToClipboard = await this.$copyToClipboard(this.prettyFfprobeData)
copyToClipboard() {
clearTimeout(this.hasCopied)
this.$copyToClipboard(this.prettyFfprobeData).then((success) => {
this.hasCopied = setTimeout(() => {
this.hasCopied = null
}, 2000)
})
}
},
mounted() {}

View File

@@ -16,7 +16,7 @@
<template v-if="currentShare">
<div class="w-full py-2">
<label class="px-1 text-sm font-semibold block">{{ $strings.LabelShareURL }}</label>
<ui-text-input v-model="currentShareUrl" show-copy readonly class="text-base h-10" />
<ui-text-input v-model="currentShareUrl" show-copy readonly />
</div>
<div class="w-full py-2 px-1">
<p v-if="currentShare.isDownloadable" class="text-sm mb-2">{{ $strings.LabelDownloadable }}</p>

View File

@@ -10,9 +10,7 @@
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedIsOpen }}</p>
<div class="w-full relative">
<ui-text-input :value="feedUrl" readonly />
<span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(feedUrl)">content_copy</span>
<ui-text-input :value="feedUrl" readonly show-copy />
</div>
<div v-if="currentFeed.meta" class="mt-5">
@@ -160,9 +158,6 @@ export default {
this.processing = false
})
},
copyToClipboard(str) {
this.$copyToClipboard(str, this)
},
closeFeed() {
this.processing = true
this.$axios

View File

@@ -5,8 +5,7 @@
<p class="text-lg font-semibold mb-4">{{ $strings.HeaderRSSFeedGeneral }}</p>
<div class="w-full relative">
<ui-text-input :value="feedUrl" readonly />
<span class="material-symbols absolute right-2 bottom-2 p-0.5 text-base transition-transform duration-100 text-gray-300 hover:text-white transform hover:scale-125 cursor-pointer" @click="copyToClipboard(feedUrl)">content_copy</span>
<ui-text-input :value="feedUrl" readonly show-copy />
</div>
<div v-if="feed.meta" class="mt-5">
@@ -74,13 +73,7 @@ export default {
feedUrl() {
return this.feed ? `${window.origin}${this.$config.routerBasePath}${this.feed.feedUrl}` : ''
}
},
methods: {
copyToClipboard(str) {
this.$copyToClipboard(str, this)
}
},
mounted() {}
}
}
</script>

View File

@@ -7,8 +7,8 @@
<div v-if="type === 'password' && isHovering" class="absolute top-0 right-0 h-full px-4 flex items-center justify-center">
<span class="material-symbols text-gray-400 cursor-pointer text-lg" @click.stop.prevent="showPassword = !showPassword">{{ !showPassword ? 'visibility' : 'visibility_off' }}</span>
</div>
<div v-else-if="showCopy" class="absolute top-0 right-0 h-full px-4 flex items-center justify-center">
<span class="material-symbols text-gray-400 cursor-pointer text-lg" @click.stop.prevent="copyToClipboard">{{ !hasCopied ? 'content_copy' : 'done' }}</span>
<div v-else-if="showCopy" class="absolute top-0 right-0 h-full px-2 flex items-center justify-center">
<span class="material-symbols cursor-pointer text-lg" :class="hasCopied ? 'text-success' : 'text-gray-400 hover:text-white'" @click.stop.prevent="copyToClipboard">{{ !hasCopied ? 'content_copy' : 'done' }}</span>
</div>
</div>
</template>
@@ -47,7 +47,7 @@ export default {
showPassword: false,
isHovering: false,
isFocused: false,
hasCopied: false,
hasCopied: null,
isInvalidDate: false
}
},
@@ -62,7 +62,12 @@ export default {
},
classList() {
var _list = []
_list.push(`px-${this.paddingX}`)
if (this.showCopy) {
_list.push('pl-3', 'pr-8')
} else {
_list.push(`px-${this.paddingX}`)
}
_list.push(`py-${this.paddingY}`)
if (this.noSpinner) _list.push('no-spinner')
if (this.textCenter) _list.push('text-center')
@@ -80,11 +85,10 @@ export default {
},
methods: {
copyToClipboard() {
if (this.hasCopied) return
clearTimeout(this.hasCopied)
this.$copyToClipboard(this.inputValue).then((success) => {
this.hasCopied = success
setTimeout(() => {
this.hasCopied = false
this.hasCopied = setTimeout(() => {
this.hasCopied = null
}, 2000)
})
},

View File

@@ -6,7 +6,7 @@
<em v-if="note" class="font-normal text-xs pl-2">{{ note }}</em>
</label>
</slot>
<ui-text-input :placeholder="placeholder || label" :inputId="identifier" ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" class="w-full" :class="inputClass" @blur="inputBlurred" />
<ui-text-input :placeholder="placeholder || label" :inputId="identifier" ref="input" v-model="inputValue" :disabled="disabled" :readonly="readonly" :type="type" :show-copy="showCopy" class="w-full" :class="inputClass" @blur="inputBlurred" />
</div>
</template>
@@ -23,7 +23,8 @@ export default {
},
readonly: Boolean,
disabled: Boolean,
inputClass: String
inputClass: String,
showCopy: Boolean
},
data() {
return {}

View File

@@ -86,7 +86,12 @@
</div>
</div>
<div class="w-full flex items-center justify-end p-4">
<div class="w-full flex items-center p-4 space-x-2">
<ui-btn small @click.stop="resetMapDetails">{{ $strings.ButtonReset }}</ui-btn>
<ui-tooltip direction="bottom" :text="$strings.MessageBatchEditPopulateMapDetailsAllHelp">
<ui-btn small :disabled="!hasSelectedBatchUsage" @click.stop="populateFromExisting()">{{ $strings.ButtonBatchEditPopulateFromExisting }}</ui-btn>
</ui-tooltip>
<div class="flex-grow" />
<ui-btn color="success" :disabled="!hasSelectedBatchUsage" :padding-x="8" small class="text-base" :loading="isProcessing" @click="mapBatchDetails">{{ $strings.ButtonApply }}</ui-btn>
</div>
</div>
@@ -97,6 +102,11 @@
<div class="flex justify-center flex-wrap">
<template v-for="libraryItem in libraryItemCopies">
<div :key="libraryItem.id" class="w-full max-w-3xl border border-black-300 p-6 -ml-px -mt-px">
<div class="flex items-center justify-end">
<ui-tooltip direction="bottom" :text="$strings.MessageBatchEditPopulateMapDetailsItemHelp">
<ui-btn small :disabled="!hasSelectedBatchUsage" @click="populateFromExisting(libraryItem.id)">{{ $strings.ButtonBatchEditPopulateMapDetails }}</ui-btn>
</ui-tooltip>
</div>
<widgets-book-details-edit v-if="libraryItem.mediaType === 'book'" :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" @change="handleItemChange" />
<widgets-podcast-details-edit v-else :ref="`itemForm-${libraryItem.id}`" :library-item="libraryItem" @change="handleItemChange" />
</div>
@@ -228,6 +238,88 @@ export default {
}
},
methods: {
resetMapDetails() {
this.blurBatchForm()
this.batchDetails = {
subtitle: null,
authors: null,
publishedYear: null,
series: [],
genres: [],
tags: [],
narrators: [],
publisher: null,
language: null,
explicit: false,
abridged: false
}
this.selectedBatchUsage = {
subtitle: false,
authors: false,
publishedYear: false,
series: false,
genres: false,
tags: false,
narrators: false,
publisher: false,
language: false,
explicit: false,
abridged: false
}
},
populateFromExisting(libraryItemId) {
this.blurBatchForm()
let libraryItemsToMap = this.libraryItemCopies
if (libraryItemId) {
libraryItemsToMap = this.libraryItemCopies.filter((li) => li.id === libraryItemId)
}
for (const key in this.selectedBatchUsage) {
if (!this.selectedBatchUsage[key]) continue
if (this.isMapAppend && !this.appendableKeys.includes(key)) continue
let existingValues = undefined
libraryItemsToMap.forEach((li) => {
if (key === 'tags') {
if (!existingValues) existingValues = []
li.media.tags.forEach((tag) => {
if (!existingValues.includes(tag)) {
existingValues.push(tag)
}
})
} else if (key === 'authors') {
if (!existingValues) existingValues = []
li.media.metadata[key].forEach((entity) => {
if (!existingValues.some((au) => au.id === entity.id)) {
existingValues.push({
id: entity.id,
name: entity.name
})
}
})
} else if (key === 'series') {
if (!existingValues) existingValues = []
li.media.metadata[key].forEach((entity) => {
if (!existingValues.includes(entity.name)) {
existingValues.push(entity.name)
}
})
} else if (key === 'genres' || key === 'narrators') {
if (!existingValues) existingValues = []
li.media.metadata[key].forEach((item) => {
if (!existingValues.includes(item)) {
existingValues.push(item)
}
})
} else if (existingValues === undefined) {
existingValues = li.media.metadata[key]
}
})
this.batchDetails[key] = existingValues
}
},
handleItemChange(itemChange) {
if (!itemChange.hasChanges) {
this.itemsWithChanges = this.itemsWithChanges.filter((id) => id !== itemChange.libraryItemId)

View File

@@ -14,11 +14,7 @@
<h1 class="text-xl pl-2">{{ username }}</h1>
</div>
<div v-if="userToken" class="flex text-xs mt-4">
<ui-text-input-with-label :label="$strings.LabelApiToken" :value="userToken" readonly />
<div class="px-1 mt-8 cursor-pointer" @click="copyToClipboard(userToken)">
<span class="material-symbols pl-2 text-base">content_copy</span>
</div>
<ui-text-input-with-label :label="$strings.LabelApiToken" :value="userToken" readonly show-copy />
</div>
<div class="w-full h-px bg-white bg-opacity-10 my-2" />
<div class="py-2">
@@ -140,9 +136,6 @@ export default {
}
},
methods: {
copyToClipboard(str) {
this.$copyToClipboard(str, this)
},
async init() {
this.listeningSessions = await this.$axios
.$get(`/api/users/${this.user.id}/listening-sessions?page=0&itemsPerPage=10`)

View File

@@ -128,12 +128,11 @@ Vue.prototype.$sanitizeSlug = (str) => {
return str
}
Vue.prototype.$copyToClipboard = (str, ctx) => {
Vue.prototype.$copyToClipboard = (str) => {
return new Promise((resolve) => {
if (navigator.clipboard) {
navigator.clipboard.writeText(str).then(
() => {
if (ctx) ctx.$toast.success('Copied to clipboard')
resolve(true)
},
(err) => {
@@ -152,7 +151,6 @@ Vue.prototype.$copyToClipboard = (str, ctx) => {
document.execCommand('copy')
document.body.removeChild(el)
if (ctx) ctx.$toast.success('Copied to clipboard')
resolve(true)
}
})

View File

@@ -10,6 +10,8 @@
"ButtonApplyChapters": "Apply Chapters",
"ButtonAuthors": "Authors",
"ButtonBack": "Back",
"ButtonBatchEditPopulateFromExisting": "Populate from existing",
"ButtonBatchEditPopulateMapDetails": "Populate map details",
"ButtonBrowseForFolder": "Browse for Folder",
"ButtonCancel": "Cancel",
"ButtonCancelEncode": "Cancel Encode",
@@ -704,6 +706,8 @@
"MessageBackupsLocationEditNote": "Note: Updating the backup location will not move or modify existing backups",
"MessageBackupsLocationNoEditNote": "Note: The backup location is set through an environment variable and cannot be changed here.",
"MessageBackupsLocationPathEmpty": "Backup location path cannot be empty",
"MessageBatchEditPopulateMapDetailsAllHelp": "Populate enabled fields with data from all items. Fields with multiple values will be merged",
"MessageBatchEditPopulateMapDetailsItemHelp": "Populate enabled map details fields with data from this item",
"MessageBatchQuickMatchDescription": "Quick Match will attempt to add missing covers and metadata for the selected items. Enable the options below to allow Quick Match to overwrite existing covers and/or metadata.",
"MessageBookshelfNoCollections": "You haven't made any collections yet",
"MessageBookshelfNoRSSFeeds": "No RSS feeds are open",