feat: settings background script setup #2104
|
@ -59,7 +59,7 @@ describe('Lists', () => {
|
||||||
.click()
|
.click()
|
||||||
cy.get('#title')
|
cy.get('#title')
|
||||||
.type(`{selectall}${newListName}`)
|
.type(`{selectall}${newListName}`)
|
||||||
cy.get('footer.modal-card-foot .button')
|
cy.get('footer.card-footer .button')
|
||||||
.contains('Save')
|
.contains('Save')
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ describe('Namepaces', () => {
|
||||||
.should('equal', newNamespaces[0].title) // wait until the namespace data is loaded
|
.should('equal', newNamespaces[0].title) // wait until the namespace data is loaded
|
||||||
cy.get('#namespacetext')
|
cy.get('#namespacetext')
|
||||||
.type(`{selectall}${newNamespaceName}`)
|
.type(`{selectall}${newNamespaceName}`)
|
||||||
cy.get('footer.modal-card-foot .button')
|
cy.get('footer.card-footer .button')
|
||||||
.contains('Save')
|
.contains('Save')
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
|
|
|
@ -69,9 +69,11 @@ const showIconOnly = computed(() => props.icon !== '' && typeof slots.default ==
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
height: auto;
|
||||||
min-height: $button-height;
|
min-height: $button-height;
|
||||||
box-shadow: var(--shadow-sm);
|
box-shadow: var(--shadow-sm);
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
white-space: break-spaces;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
box-shadow: var(--shadow-md);
|
box-shadow: var(--shadow-md);
|
||||||
|
|
|
@ -16,11 +16,21 @@
|
||||||
</span>
|
</span>
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
</header>
|
</header>
|
||||||
<div class="card-content loader-container" :class="{'p-0': !padding, 'is-loading': loading}">
|
<div
|
||||||
|
class="card-content loader-container"
|
||||||
|
:class="{
|
||||||
|
'p-0': !padding,
|
||||||
|
'is-loading': loading
|
||||||
|
}"
|
||||||
|
>
|
||||||
<div :class="{'content': hasContent}">
|
<div :class="{'content': hasContent}">
|
||||||
<slot></slot>
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<footer v-if="$slots.footer" class="card-footer">
|
||||||
|
<slot name="footer" />
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -76,9 +86,11 @@ defineEmits(['close'])
|
||||||
border-radius: $radius $radius 0 0;
|
border-radius: $radius $radius 0 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: should maybe be merged somehow with modal
|
.card-footer {
|
||||||
:deep(.modal-card-foot) {
|
|
||||||
background-color: var(--grey-50);
|
background-color: var(--grey-50);
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
|
padding: var(--modal-card-head-padding);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -4,38 +4,41 @@
|
||||||
:title="title"
|
:title="title"
|
||||||
:shadow="false"
|
:shadow="false"
|
||||||
:padding="false"
|
:padding="false"
|
||||||
class="has-text-left has-overflow"
|
class="has-text-left"
|
||||||
:has-close="true"
|
:has-close="true"
|
||||||
@close="$router.back()"
|
@close="$router.back()"
|
||||||
:loading="loading"
|
:loading="loading"
|
||||||
>
|
>
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<slot></slot>
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
<footer class="modal-card-foot is-flex is-justify-content-flex-end">
|
|
||||||
<x-button
|
<template #footer>
|
||||||
v-if="tertiary !== ''"
|
<slot name="footer">
|
||||||
:shadow="false"
|
<x-button
|
||||||
variant="tertiary"
|
v-if="tertiary !== ''"
|
||||||
@click.prevent.stop="$emit('tertiary')"
|
:shadow="false"
|
||||||
>
|
variant="tertiary"
|
||||||
{{ tertiary }}
|
@click.prevent.stop="$emit('tertiary')"
|
||||||
</x-button>
|
>
|
||||||
<x-button
|
{{ tertiary }}
|
||||||
variant="secondary"
|
</x-button>
|
||||||
@click.prevent.stop="$router.back()"
|
<x-button
|
||||||
>
|
variant="secondary"
|
||||||
{{ $t('misc.cancel') }}
|
@click.prevent.stop="$router.back()"
|
||||||
</x-button>
|
>
|
||||||
<x-button
|
{{ $t('misc.cancel') }}
|
||||||
variant="primary"
|
</x-button>
|
||||||
@click.prevent.stop="primary()"
|
<x-button
|
||||||
:icon="primaryIcon"
|
variant="primary"
|
||||||
:disabled="primaryDisabled"
|
@click.prevent.stop="primary()"
|
||||||
>
|
:icon="primaryIcon"
|
||||||
{{ primaryLabel || $t('misc.create') }}
|
:disabled="primaryDisabled || loading"
|
||||||
</x-button>
|
>
|
||||||
</footer>
|
{{ primaryLabel || $t('misc.create') }}
|
||||||
|
</x-button>
|
||||||
|
</slot>
|
||||||
|
</template>
|
||||||
</card>
|
</card>
|
||||||
</modal>
|
</modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -19,17 +19,16 @@
|
||||||
{{ $t('about.apiVersion', {version: apiVersion}) }}
|
{{ $t('about.apiVersion', {version: apiVersion}) }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<footer class="modal-card-foot is-flex is-justify-content-flex-end">
|
<template #footer>
|
||||||
<x-button
|
<x-button
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
@click.prevent.stop="$router.back()"
|
@click.prevent.stop="$router.back()"
|
||||||
>
|
>
|
||||||
{{ $t('misc.close') }}
|
{{ $t('misc.close') }}
|
||||||
</x-button>
|
</x-button>
|
||||||
</footer>
|
</template>
|
||||||
</card>
|
</card>
|
||||||
</modal>
|
</modal>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
|
@ -47,14 +47,17 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<x-button
|
|
||||||
:loading="savedFilterService.loading"
|
<template #footer>
|
||||||
:disabled="savedFilterService.loading"
|
<x-button
|
||||||
@click="create()"
|
:loading="savedFilterService.loading"
|
||||||
class="is-fullwidth"
|
:disabled="savedFilterService.loading"
|
||||||
>
|
@click="create()"
|
||||||
{{ $t('filters.create.action') }}
|
class="is-fullwidth"
|
||||||
</x-button>
|
>
|
||||||
|
{{ $t('filters.create.action') }}
|
||||||
|
</x-button>
|
||||||
|
</template>
|
||||||
</card>
|
</card>
|
||||||
</modal>
|
</modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -1,13 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<create-edit
|
<create-edit
|
||||||
|
v-if="uploadBackgroundEnabled || unsplashBackgroundEnabled"
|
||||||
:title="$t('list.background.title')"
|
:title="$t('list.background.title')"
|
||||||
primary-label=""
|
|
||||||
:loading="backgroundService.loading"
|
:loading="backgroundService.loading"
|
||||||
class="list-background-setting"
|
class="list-background-setting"
|
||||||
:wide="true"
|
:wide="true"
|
||||||
v-if="uploadBackgroundEnabled || unsplashBackgroundEnabled"
|
|
||||||
:tertiary="hasBackground ? $t('list.background.remove') : ''"
|
|
||||||
@tertiary="removeBackground()"
|
|
||||||
>
|
>
|
||||||
<div class="mb-4" v-if="uploadBackgroundEnabled">
|
<div class="mb-4" v-if="uploadBackgroundEnabled">
|
||||||
<input
|
<input
|
||||||
|
@ -19,7 +16,7 @@
|
||||||
/>
|
/>
|
||||||
<x-button
|
<x-button
|
||||||
:loading="backgroundUploadService.loading"
|
:loading="backgroundUploadService.loading"
|
||||||
@click="$refs.backgroundUploadInput.click()"
|
@click="backgroundUploadInput?.click()"
|
||||||
variant="primary"
|
variant="primary"
|
||||||
>
|
>
|
||||||
{{ $t('list.background.upload') }}
|
{{ $t('list.background.upload') }}
|
||||||
dpschen marked this conversation as resolved
Outdated
|
|||||||
|
@ -28,245 +25,260 @@
|
||||||
<template v-if="unsplashBackgroundEnabled">
|
<template v-if="unsplashBackgroundEnabled">
|
||||||
<input
|
<input
|
||||||
:class="{'is-loading': backgroundService.loading}"
|
:class="{'is-loading': backgroundService.loading}"
|
||||||
@keyup="() => debounceNewBackgroundSearch()"
|
@keyup="debounceNewBackgroundSearch()"
|
||||||
class="input is-expanded"
|
class="input is-expanded"
|
||||||
:placeholder="$t('list.background.searchPlaceholder')"
|
:placeholder="$t('list.background.searchPlaceholder')"
|
||||||
type="text"
|
type="text"
|
||||||
v-model="backgroundSearchTerm"
|
v-model="backgroundSearchTerm"
|
||||||
/>
|
/>
|
||||||
<p class="unsplash-link">
|
|
||||||
<BaseButton href="https://unsplash.com">{{ $t('list.background.poweredByUnsplash') }}</BaseButton>
|
<p class="unsplash-credit">
|
||||||
|
<BaseButton class="unsplash-credit__link" href="https://unsplash.com">{{ $t('list.background.poweredByUnsplash') }}</BaseButton>
|
||||||
</p>
|
</p>
|
||||||
<div class="image-search-result">
|
|
||||||
<a
|
<ul class="image-search__result-list">
|
||||||
|
<li
|
||||||
|
v-for="im in backgroundSearchResult"
|
||||||
|
class="image-search__result-item"
|
||||||
:key="im.id"
|
:key="im.id"
|
||||||
:style="{'background-image': `url(${backgroundBlurHashes[im.id]})`}"
|
:style="{'background-image': `url(${backgroundBlurHashes[im.id]})`}"
|
||||||
@click="() => setBackground(im.id)"
|
>
|
||||||
class="image"
|
|
||||||
v-for="im in backgroundSearchResult">
|
|
||||||
<transition name="fade">
|
<transition name="fade">
|
||||||
<img :src="backgroundThumbs[im.id]" alt="" v-if="backgroundThumbs[im.id]"/>
|
<BaseButton
|
||||||
|
v-if="backgroundThumbs[im.id]"
|
||||||
|
class="image-search__image-button"
|
||||||
|
@click="setBackground(im.id)"
|
||||||
|
>
|
||||||
|
<img class="image-search__image" :src="backgroundThumbs[im.id]" alt="" />
|
||||||
|
</BaseButton>
|
||||||
</transition>
|
</transition>
|
||||||
<a
|
|
||||||
|
<BaseButton
|
||||||
:href="`https://unsplash.com/@${im.info.author}`"
|
:href="`https://unsplash.com/@${im.info.author}`"
|
||||||
rel="noreferrer noopener nofollow"
|
class="image-search__info"
|
||||||
target="_blank"
|
>
|
||||||
class="info">
|
|
||||||
{{ im.info.authorName }}
|
{{ im.info.authorName }}
|
||||||
</a>
|
</BaseButton>
|
||||||
</a>
|
</li>
|
||||||
</div>
|
</ul>
|
||||||
<x-button
|
<x-button
|
||||||
|
v-if="backgroundSearchResult.length > 0"
|
||||||
:disabled="backgroundService.loading"
|
:disabled="backgroundService.loading"
|
||||||
@click="() => searchBackgrounds(currentPage + 1)"
|
@click="searchBackgrounds(currentPage + 1)"
|
||||||
class="is-load-more-button mt-4"
|
class="is-load-more-button mt-4"
|
||||||
:shadow="false"
|
:shadow="false"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
v-if="backgroundSearchResult.length > 0"
|
|
||||||
>
|
>
|
||||||
{{ backgroundService.loading ? $t('misc.loading') : $t('list.background.loadMore') }}
|
{{ backgroundService.loading ? $t('misc.loading') : $t('list.background.loadMore') }}
|
||||||
</x-button>
|
</x-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<x-button
|
||||||
|
v-if="hasBackground"
|
||||||
|
:shadow="false"
|
||||||
|
variant="tertiary"
|
||||||
|
class="is-danger"
|
||||||
|
@click.prevent.stop="removeBackground"
|
||||||
|
>
|
||||||
|
{{ $t('list.background.remove') }}
|
||||||
|
</x-button>
|
||||||
|
<x-button
|
||||||
|
variant="secondary"
|
||||||
|
@click.prevent.stop="$router.back()"
|
||||||
|
>
|
||||||
|
{{ $t('misc.close') }}
|
||||||
|
</x-button>
|
||||||
|
</template>
|
||||||
</create-edit>
|
</create-edit>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {defineComponent} from 'vue'
|
import {defineComponent} from 'vue'
|
||||||
import {mapState} from 'vuex'
|
export default defineComponent({ name: 'list-setting-background' })
|
||||||
import {getBlobFromBlurHash} from '../../../helpers/getBlobFromBlurHash'
|
</script>
|
||||||
|
|
||||||
import BackgroundUnsplashService from '../../../services/backgroundUnsplash'
|
<script setup lang="ts">
|
||||||
import BackgroundUploadService from '../../../services/backgroundUpload'
|
import {ref, computed, shallowReactive} from 'vue'
|
||||||
import ListService from '@/services/list'
|
import {useI18n} from 'vue-i18n'
|
||||||
import {CURRENT_LIST} from '@/store/mutation-types'
|
import {useStore} from 'vuex'
|
||||||
import CreateEdit from '@/components/misc/create-edit.vue'
|
import {useRoute, useRouter} from 'vue-router'
|
||||||
import debounce from 'lodash.debounce'
|
import debounce from 'lodash.debounce'
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
|
||||||
|
import BackgroundUnsplashService from '@/services/backgroundUnsplash'
|
||||||
|
import BackgroundUploadService from '@/services/backgroundUpload'
|
||||||
|
import ListService from '@/services/list'
|
||||||
|
import BackgroundImageModel from '@/models/backgroundImage'
|
||||||
|
|
||||||
|
import {getBlobFromBlurHash} from '@/helpers/getBlobFromBlurHash'
|
||||||
|
import {useTitle} from '@/composables/useTitle'
|
||||||
|
import {CURRENT_LIST} from '@/store/mutation-types'
|
||||||
|
|
||||||
|
import CreateEdit from '@/components/misc/create-edit.vue'
|
||||||
|
import { success } from '@/message'
|
||||||
|
|
||||||
const SEARCH_DEBOUNCE = 300
|
const SEARCH_DEBOUNCE = 300
|
||||||
|
|
||||||
export default defineComponent({
|
const {t} = useI18n()
|
||||||
name: 'list-setting-background',
|
const store = useStore()
|
||||||
components: {CreateEdit, BaseButton},
|
const route = useRoute()
|
||||||
data() {
|
const router = useRouter()
|
||||||
return {
|
|
||||||
backgroundService: new BackgroundUnsplashService(),
|
|
||||||
backgroundSearchTerm: '',
|
|
||||||
backgroundSearchResult: [],
|
|
||||||
backgroundThumbs: {},
|
|
||||||
backgroundBlurHashes: {},
|
|
||||||
currentPage: 1,
|
|
||||||
|
|
||||||
// We're using debounce to not search on every keypress but with a delay.
|
useTitle(() => t('list.background.title'))
|
||||||
debounceNewBackgroundSearch: debounce(this.newBackgroundSearch, SEARCH_DEBOUNCE, {
|
|
||||||
trailing: true,
|
|
||||||
}),
|
|
||||||
|
|
||||||
backgroundUploadService: new BackgroundUploadService(),
|
const backgroundService = shallowReactive(new BackgroundUnsplashService())
|
||||||
listService: new ListService(),
|
const backgroundSearchTerm = ref('')
|
||||||
}
|
const backgroundSearchResult = ref([])
|
||||||
},
|
const backgroundThumbs = ref<Record<string, string>>({})
|
||||||
computed: mapState({
|
const backgroundBlurHashes = ref<Record<string, string>>({})
|
||||||
unsplashBackgroundEnabled: state => state.config.enabledBackgroundProviders.includes('unsplash'),
|
const currentPage = ref(1)
|
||||||
uploadBackgroundEnabled: state => state.config.enabledBackgroundProviders.includes('upload'),
|
|
||||||
currentList: state => state.currentList,
|
|
||||||
hasBackground: state => state.background !== null,
|
|
||||||
}),
|
|
||||||
created() {
|
|
||||||
this.setTitle(this.$t('list.background.title'))
|
|
||||||
// Show the default collection of backgrounds
|
|
||||||
this.newBackgroundSearch()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
newBackgroundSearch() {
|
|
||||||
if (!this.unsplashBackgroundEnabled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// This is an extra method to reset a few things when searching to not break loading more photos.
|
|
||||||
this.backgroundSearchResult = []
|
|
||||||
this.backgroundThumbs = {}
|
|
||||||
this.searchBackgrounds()
|
|
||||||
},
|
|
||||||
|
|
||||||
async searchBackgrounds(page = 1) {
|
// We're using debounce to not search on every keypress but with a delay.
|
||||||
this.currentPage = page
|
const debounceNewBackgroundSearch = debounce(newBackgroundSearch, SEARCH_DEBOUNCE, {
|
||||||
const result = await this.backgroundService.getAll({}, {s: this.backgroundSearchTerm, p: page})
|
trailing: true,
|
||||||
this.backgroundSearchResult = this.backgroundSearchResult.concat(result)
|
|
||||||
result.forEach(background => {
|
|
||||||
getBlobFromBlurHash(background.blurHash)
|
|
||||||
.then(b => {
|
|
||||||
this.backgroundBlurHashes[background.id] = window.URL.createObjectURL(b)
|
|
||||||
})
|
|
||||||
|
|
||||||
this.backgroundService.thumb(background)
|
|
||||||
.then(b => {
|
|
||||||
this.backgroundThumbs[background.id] = b
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
async setBackground(backgroundId) {
|
|
||||||
// Don't set a background if we're in the process of setting one
|
|
||||||
if (this.backgroundService.loading) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const list = await this.backgroundService.update({id: backgroundId, listId: this.$route.params.listId})
|
|
||||||
await this.$store.dispatch(CURRENT_LIST, {list, forceUpdate: true})
|
|
||||||
this.$store.commit('namespaces/setListInNamespaceById', list)
|
|
||||||
this.$store.commit('lists/setList', list)
|
|
||||||
this.$message.success({message: this.$t('list.background.success')})
|
|
||||||
},
|
|
||||||
|
|
||||||
async uploadBackground() {
|
|
||||||
if (this.$refs.backgroundUploadInput.files.length === 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const list = await this.backgroundUploadService.create(this.$route.params.listId, this.$refs.backgroundUploadInput.files[0])
|
|
||||||
await this.$store.dispatch(CURRENT_LIST, {list, forceUpdate: true})
|
|
||||||
this.$store.commit('namespaces/setListInNamespaceById', list)
|
|
||||||
this.$store.commit('lists/setList', list)
|
|
||||||
this.$message.success({message: this.$t('list.background.success')})
|
|
||||||
},
|
|
||||||
|
|
||||||
async removeBackground() {
|
|
||||||
const list = await this.listService.removeBackground(this.currentList)
|
|
||||||
await this.$store.dispatch(CURRENT_LIST, {list, forceUpdate: true})
|
|
||||||
this.$store.commit('namespaces/setListInNamespaceById', list)
|
|
||||||
this.$store.commit('lists/setList', list)
|
|
||||||
this.$message.success({message: this.$t('list.background.removeSuccess')})
|
|
||||||
this.$router.back()
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const backgroundUploadService = ref(new BackgroundUploadService())
|
||||||
|
const listService = ref(new ListService())
|
||||||
|
|
||||||
|
const unsplashBackgroundEnabled = computed(() => store.state.config.enabledBackgroundProviders.includes('unsplash'))
|
||||||
|
const uploadBackgroundEnabled = computed(() => store.state.config.enabledBackgroundProviders.includes('upload'))
|
||||||
|
const currentList = computed(() => store.state.currentList)
|
||||||
|
const hasBackground = computed(() => store.state.background !== null)
|
||||||
|
|
||||||
|
// Show the default collection of backgrounds
|
||||||
|
newBackgroundSearch()
|
||||||
|
|
||||||
|
function newBackgroundSearch() {
|
||||||
|
if (!unsplashBackgroundEnabled.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// This is an extra method to reset a few things when searching to not break loading more photos.
|
||||||
|
backgroundSearchResult.value = []
|
||||||
|
backgroundThumbs.value = {}
|
||||||
|
searchBackgrounds()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function searchBackgrounds(page = 1) {
|
||||||
|
currentPage.value = page
|
||||||
|
const result = await backgroundService.getAll({}, {s: backgroundSearchTerm.value, p: page})
|
||||||
|
backgroundSearchResult.value = backgroundSearchResult.value.concat(result)
|
||||||
|
result.forEach((background: BackgroundImageModel) => {
|
||||||
|
getBlobFromBlurHash(background.blurHash)
|
||||||
|
.then((b) => {
|
||||||
|
backgroundBlurHashes.value[background.id] = window.URL.createObjectURL(b)
|
||||||
|
})
|
||||||
|
|
||||||
|
backgroundService.thumb(background).then(b => {
|
||||||
|
backgroundThumbs.value[background.id] = b
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setBackground(backgroundId: string) {
|
||||||
|
// Don't set a background if we're in the process of setting one
|
||||||
|
if (backgroundService.loading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = await backgroundService.update({id: backgroundId, listId: route.params.listId})
|
||||||
|
await store.dispatch(CURRENT_LIST, {list, forceUpdate: true})
|
||||||
|
store.commit('namespaces/setListInNamespaceById', list)
|
||||||
|
store.commit('lists/setList', list)
|
||||||
|
success({message: t('list.background.success')})
|
||||||
|
}
|
||||||
|
|
||||||
|
const backgroundUploadInput = ref<HTMLInputElement | null>(null)
|
||||||
|
async function uploadBackground() {
|
||||||
|
if (backgroundUploadInput.value?.files?.length === 0) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const list = await backgroundUploadService.value.create(route.params.listId, backgroundUploadInput.value?.files[0])
|
||||||
|
await store.dispatch(CURRENT_LIST, {list, forceUpdate: true})
|
||||||
|
store.commit('namespaces/setListInNamespaceById', list)
|
||||||
|
store.commit('lists/setList', list)
|
||||||
|
success({message: t('list.background.success')})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removeBackground() {
|
||||||
|
const list = await listService.value.removeBackground(currentList.value)
|
||||||
|
await store.dispatch(CURRENT_LIST, {list, forceUpdate: true})
|
||||||
|
store.commit('namespaces/setListInNamespaceById', list)
|
||||||
|
store.commit('lists/setList', list)
|
||||||
|
success({message: t('list.background.removeSuccess')})
|
||||||
|
router.back()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.list-background-setting {
|
.unsplash-credit {
|
||||||
|
text-align: right;
|
||||||
|
font-size: .8rem;
|
||||||
|
}
|
||||||
|
|
||||||
.unsplash-link {
|
.unsplash-credit__link {
|
||||||
text-align: right;
|
color: var(--grey-800);
|
||||||
font-size: .8rem;
|
}
|
||||||
|
|
||||||
a {
|
.image-search__result-list {
|
||||||
color: var(--grey-800);
|
--items-per-row: 1;
|
||||||
}
|
margin: 1rem 0 0;
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
grid-template-columns: repeat(var(--items-per-row), 1fr);
|
||||||
|
|
||||||
|
@media screen and (min-width: $mobile) {
|
||||||
|
--items-per-row: 2;
|
||||||
}
|
}
|
||||||
|
@media screen and (min-width: $tablet) {
|
||||||
.image-search-result {
|
--items-per-row: 4;
|
||||||
margin-top: 1rem;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
|
|
||||||
.image {
|
|
||||||
width: calc(100% / 5 - 1rem);
|
|
||||||
height: 120px;
|
|
||||||
margin: .5rem;
|
|
||||||
background-size: cover;
|
|
||||||
background-position: center;
|
|
||||||
display: flex;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
@media screen and (min-width: $desktop) {
|
|
||||||
&:nth-child(5n) {
|
|
||||||
break-after: always;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: $desktop) {
|
|
||||||
width: calc(100% / 4 - 1rem);
|
|
||||||
|
|
||||||
&:nth-child(4n) {
|
|
||||||
break-after: always;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: $tablet) {
|
|
||||||
width: calc(100% / 2 - 1rem);
|
|
||||||
|
|
||||||
&:nth-child(2n) {
|
|
||||||
break-after: always;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: ($mobile)) {
|
|
||||||
width: calc(100% - 1rem);
|
|
||||||
|
|
||||||
&:nth-child(1n) {
|
|
||||||
break-after: always;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.info {
|
|
||||||
align-self: flex-end;
|
|
||||||
display: block;
|
|
||||||
opacity: 0;
|
|
||||||
width: 100%;
|
|
||||||
padding: .25rem 0;
|
|
||||||
text-align: center;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
font-size: .75rem;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--white);
|
|
||||||
transition: opacity $transition;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover .info {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@media screen and (min-width: $tablet) {
|
||||||
.is-load-more-button {
|
--items-per-row: 5;
|
||||||
margin: 1rem auto 0 !important;
|
|
||||||
display: block;
|
|
||||||
width: 200px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.image-search__result-item {
|
||||||
|
margin-top: 0; // FIXME: removes padding from .content
|
||||||
|
aspect-ratio: 16 / 10;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
display: flex;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-search__image-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-search__image {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-search__info {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: .25rem 0;
|
||||||
|
opacity: 0;
|
||||||
|
text-align: center;
|
||||||
|
background: rgba(0, 0, 0, 0.5);
|
||||||
|
font-size: .75rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--white);
|
||||||
|
transition: opacity $transition;
|
||||||
|
}
|
||||||
|
.image-search__result-item:hover .image-search__info {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-load-more-button {
|
||||||
|
margin: 1rem auto 0 !important;
|
||||||
|
display: block;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
Reference in New Issue
Does this generate an error message when
backgroundUploadInput
is undefined or null? Or is vue smart enough to figure this out and prevent an error?I think this compiles to
@click="null"