feat: grid for list cards
This commit is contained in:
parent
4b47478440
commit
42e9f306e8
@ -45,7 +45,7 @@ describe('List History', () => {
|
|||||||
|
|
||||||
cy.get('body')
|
cy.get('body')
|
||||||
.should('contain', 'Last viewed')
|
.should('contain', 'Last viewed')
|
||||||
cy.get('.list-cards-wrapper-2-rows')
|
cy.get('[data-cy="listCardGrid"]')
|
||||||
.should('not.contain', lists[0].title)
|
.should('not.contain', lists[0].title)
|
||||||
.should('contain', lists[1].title)
|
.should('contain', lists[1].title)
|
||||||
.should('contain', lists[2].title)
|
.should('contain', lists[2].title)
|
||||||
|
176
src/components/list/partials/ListCard.vue
Normal file
176
src/components/list/partials/ListCard.vue
Normal file
@ -0,0 +1,176 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="list-card"
|
||||||
|
:class="{
|
||||||
|
'has-light-text': background !== null,
|
||||||
|
'has-background': blurHashUrl !== '' || background !== null
|
||||||
|
}"
|
||||||
|
:style="{
|
||||||
|
'border-left': list.hexColor ? `0.25rem solid ${list.hexColor}` : undefined,
|
||||||
|
'background-image': blurHashUrl !== '' ? `url(${blurHashUrl})` : undefined,
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="list-background background-fade-in"
|
||||||
|
:class="{'is-visible': background}"
|
||||||
|
:style="{'background-image': background !== null ? `url(${background})` : undefined}"
|
||||||
|
/>
|
||||||
|
<span v-if="list.isArchived" class="is-archived" >{{ $t('namespace.archived') }}</span>
|
||||||
|
|
||||||
|
<div class="list-title" aria-hidden="true">{{ list.title }}</div>
|
||||||
|
<BaseButton
|
||||||
|
class="list-button"
|
||||||
|
:aria-label="list.title"
|
||||||
|
:title="list.description"
|
||||||
|
:to="{
|
||||||
|
name: 'list.index',
|
||||||
|
params: { listId: list.id}
|
||||||
|
}"
|
||||||
|
/>
|
||||||
|
<BaseButton
|
||||||
|
v-if="!list.isArchived"
|
||||||
|
class="favorite"
|
||||||
|
:class="{'is-favorite': list.isFavorite}"
|
||||||
|
@click.prevent.stop="listStore.toggleListFavorite(list)"
|
||||||
|
>
|
||||||
|
<icon :icon="list.isFavorite ? 'star' : ['far', 'star']" />
|
||||||
|
</BaseButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {toRef, type PropType} from 'vue'
|
||||||
|
|
||||||
|
import type {IList} from '@/modelTypes/IList'
|
||||||
|
|
||||||
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
|
||||||
|
import {useListBackground} from './useListBackground'
|
||||||
|
import {useListStore} from '@/stores/lists'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
list: {
|
||||||
|
type: Object as PropType<IList>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const {background, blurHashUrl} = useListBackground(toRef(props, 'list'))
|
||||||
|
|
||||||
|
const listStore = useListStore()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.list-card {
|
||||||
|
--list-card-padding: 1rem;
|
||||||
|
background: var(--white);
|
||||||
|
padding: var(--list-card-padding);
|
||||||
|
border-radius: $radius;
|
||||||
|
box-shadow: var(--shadow-sm);
|
||||||
|
transition: box-shadow $transition;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden; // hide background
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: var(--shadow-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:focus {
|
||||||
|
box-shadow: var(--shadow-xs) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
// so the elements are on top of the background
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-background,
|
||||||
|
.list-background {
|
||||||
|
background-size: cover;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-background,
|
||||||
|
.list-button {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.is-archived {
|
||||||
|
font-size: .75rem;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-title {
|
||||||
|
align-self: flex-end;
|
||||||
|
font-family: $vikunja-font;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: var(--title-line-height);
|
||||||
|
color: var(--text);
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 0;
|
||||||
|
max-height: calc(100% - (var(--list-card-padding) + 1rem)); // padding & height of the "is archived" badge
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-word;
|
||||||
|
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-light-text .list-title {
|
||||||
|
color: var(--grey-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.has-background .list-title {
|
||||||
|
text-shadow:
|
||||||
|
0 0 10px var(--black),
|
||||||
|
1px 1px 5px var(--grey-700),
|
||||||
|
-1px -1px 5px var(--grey-700);
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.favorite {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--list-card-padding);
|
||||||
|
right: var(--list-card-padding);
|
||||||
|
transition: opacity $transition, color $transition;
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-favorite {
|
||||||
|
display: inline-block;
|
||||||
|
opacity: 1;
|
||||||
|
color: var(--warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-card:hover .favorite {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-fade-in {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity $transition;
|
||||||
|
transition-delay: $transition-duration * 2; // To fake an appearing background
|
||||||
|
|
||||||
|
&.is-visible {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
77
src/components/list/partials/ListCardGrid.vue
Normal file
77
src/components/list/partials/ListCardGrid.vue
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
<template>
|
||||||
|
<ul class="list-grid">
|
||||||
|
<li
|
||||||
|
v-for="(item, index) in filteredLists"
|
||||||
|
:key="`list_${item.id}_${index}`"
|
||||||
|
class="list-grid-item"
|
||||||
|
>
|
||||||
|
<ListCard :list="item" />
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {computed, type PropType} from 'vue'
|
||||||
|
import type {IList} from '@/modelTypes/IList'
|
||||||
|
|
||||||
|
import ListCard from './ListCard.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
lists: {
|
||||||
|
type: Array as PropType<IList[]>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
showArchived: {
|
||||||
|
default: false,
|
||||||
|
type: Boolean,
|
||||||
|
},
|
||||||
|
itemLimit: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const filteredLists = computed(() => {
|
||||||
|
return props.showArchived
|
||||||
|
? props.lists
|
||||||
|
: props.lists.filter(l => !l.isArchived)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$list-height: 150px;
|
||||||
|
$list-spacing: 1rem;
|
||||||
|
|
||||||
|
.list-grid {
|
||||||
|
margin: 0; // reset li
|
||||||
|
list-style-type: none;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(var(--list-columns), 1fr);
|
||||||
|
grid-auto-rows: $list-height;
|
||||||
|
gap: $list-spacing;
|
||||||
|
|
||||||
|
@media screen and (min-width: $mobile) {
|
||||||
|
--list-rows: 4;
|
||||||
|
--list-columns: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $mobile) and (max-width: $tablet) {
|
||||||
|
--list-columns: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $tablet) and (max-width: $widescreen) {
|
||||||
|
--list-columns: 3;
|
||||||
|
--list-rows: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: $widescreen) {
|
||||||
|
--list-columns: 5;
|
||||||
|
--list-rows: 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-grid-item {
|
||||||
|
display: grid;
|
||||||
|
margin-top: 0; // remove padding coming form .content li + li
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,226 +0,0 @@
|
|||||||
<template>
|
|
||||||
<router-link
|
|
||||||
:class="{
|
|
||||||
'has-light-text': !colorIsDark(list.hexColor) || background !== null,
|
|
||||||
'has-background': blurHashUrl !== '' || background !== null,
|
|
||||||
}"
|
|
||||||
:style="{
|
|
||||||
'border-color': `${list.hexColor}`,
|
|
||||||
'background-image': blurHashUrl !== null ? `url(${blurHashUrl})` : false,
|
|
||||||
}"
|
|
||||||
:to="{ name: 'list.index', params: { listId: list.id} }"
|
|
||||||
class="list-card"
|
|
||||||
v-if="list !== null && (showArchived ? true : !list.isArchived)"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="list-background background-fade-in"
|
|
||||||
:class="{'is-visible': background}"
|
|
||||||
:style="{'background-image': background !== null ? `url(${background})` : undefined}"
|
|
||||||
/>
|
|
||||||
<div class="list-content">
|
|
||||||
<span class="is-archived" v-if="list.isArchived">
|
|
||||||
{{ $t('namespace.archived') }}
|
|
||||||
</span>
|
|
||||||
<BaseButton
|
|
||||||
v-else
|
|
||||||
:class="{'is-favorite': list.isFavorite}"
|
|
||||||
@click.stop="listStore.toggleListFavorite(list)"
|
|
||||||
class="favorite"
|
|
||||||
>
|
|
||||||
<icon :icon="list.isFavorite ? 'star' : ['far', 'star']"/>
|
|
||||||
</BaseButton>
|
|
||||||
|
|
||||||
<div class="title">{{ list.title }}</div>
|
|
||||||
</div>
|
|
||||||
</router-link>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import {type PropType, ref, watch} from 'vue'
|
|
||||||
|
|
||||||
import ListService from '@/services/list'
|
|
||||||
import {getBlobFromBlurHash} from '@/helpers/getBlobFromBlurHash'
|
|
||||||
|
|
||||||
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
|
||||||
|
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
|
||||||
import type {IList} from '@/modelTypes/IList'
|
|
||||||
import {useListStore} from '@/stores/lists'
|
|
||||||
|
|
||||||
const background = ref<string | null>(null)
|
|
||||||
const backgroundLoading = ref(false)
|
|
||||||
const blurHashUrl = ref('')
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
list: {
|
|
||||||
type: Object as PropType<IList>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
showArchived: {
|
|
||||||
default: false,
|
|
||||||
type: Boolean,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(props.list, loadBackground, {immediate: true})
|
|
||||||
|
|
||||||
async function loadBackground() {
|
|
||||||
if (props.list === null || !props.list.backgroundInformation || backgroundLoading.value) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const blurHash = await getBlobFromBlurHash(props.list.backgroundBlurHash)
|
|
||||||
if (blurHash) {
|
|
||||||
blurHashUrl.value = window.URL.createObjectURL(blurHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
backgroundLoading.value = true
|
|
||||||
|
|
||||||
const listService = new ListService()
|
|
||||||
try {
|
|
||||||
background.value = await listService.background(props.list)
|
|
||||||
} finally {
|
|
||||||
backgroundLoading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const listStore = useListStore()
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
|
||||||
.list-card {
|
|
||||||
cursor: pointer;
|
|
||||||
width: calc((100% - #{($lists-per-row - 1) * 1rem}) / #{$lists-per-row});
|
|
||||||
height: $list-height;
|
|
||||||
border-left-width: 0.8rem;
|
|
||||||
border-left-style: solid;
|
|
||||||
background: var(--white);
|
|
||||||
margin: 0 $list-spacing $list-spacing 0;
|
|
||||||
border-radius: $radius;
|
|
||||||
box-shadow: var(--shadow-sm);
|
|
||||||
transition: box-shadow $transition;
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
&.has-light-text .title {
|
|
||||||
color: var(--grey-100) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-background,
|
|
||||||
.list-background {
|
|
||||||
background-size: cover;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.has-background .title {
|
|
||||||
text-shadow: 0 0 10px var(--black), 1px 1px 5px var(--grey-700), -1px -1px 5px var(--grey-700);
|
|
||||||
color: var(--white);
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-background {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
&:active,
|
|
||||||
&:focus,
|
|
||||||
&:focus:not(:active) {
|
|
||||||
box-shadow: var(--shadow-xs) !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (min-width: $widescreen) {
|
|
||||||
&:nth-child(#{$lists-per-row}n) {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: $widescreen) and (min-width: $tablet) {
|
|
||||||
$lists-per-row: 3;
|
|
||||||
& {
|
|
||||||
width: calc((100% - #{($lists-per-row - 1) * 1rem}) / #{$lists-per-row});
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(#{$lists-per-row}n) {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: $tablet) {
|
|
||||||
$lists-per-row: 2;
|
|
||||||
& {
|
|
||||||
width: calc((100% - #{($lists-per-row - 1) * 1rem}) / #{$lists-per-row});
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(#{$lists-per-row}n) {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media screen and (max-width: $mobile) {
|
|
||||||
$lists-per-row: 1;
|
|
||||||
& {
|
|
||||||
width: 100%;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.list-content {
|
|
||||||
display: flex;
|
|
||||||
align-content: flex-start;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
row-gap: 0.8rem;
|
|
||||||
padding: 1rem;
|
|
||||||
position: absolute;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
|
|
||||||
.is-archived {
|
|
||||||
font-size: .75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite {
|
|
||||||
margin-left: auto;
|
|
||||||
transition: opacity $transition, color $transition;
|
|
||||||
opacity: 0;
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
&:hover,
|
|
||||||
&.is-favorite {
|
|
||||||
color: var(--warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.favorite.is-favorite,
|
|
||||||
&:hover .favorite {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
align-self: flex-start;
|
|
||||||
font-family: $vikunja-font;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: var(--text);
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 0;
|
|
||||||
max-height: calc(100% - 2rem); // 1rem padding, 1rem height of the "is archived" badge
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 3;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
55
src/components/list/partials/useListBackground.ts
Normal file
55
src/components/list/partials/useListBackground.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import {ref, watch, type Ref} from 'vue'
|
||||||
|
import ListService from '@/services/list'
|
||||||
|
import type {IList} from '@/modelTypes/IList'
|
||||||
|
import {getBlobFromBlurHash} from '@/helpers/getBlobFromBlurHash'
|
||||||
|
|
||||||
|
export function useListBackground(list: Ref<IList>) {
|
||||||
|
const background = ref<string | null>(null)
|
||||||
|
const backgroundLoading = ref(false)
|
||||||
|
const blurHashUrl = ref('')
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => [list.value.id, list.value.backgroundBlurHash] as [IList['id'], IList['backgroundBlurHash']],
|
||||||
|
async ([listId, blurHash], oldValue) => {
|
||||||
|
if (
|
||||||
|
list.value === null ||
|
||||||
|
!list.value.backgroundInformation ||
|
||||||
|
backgroundLoading.value
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const [oldListId, oldBlurHash] = oldValue || []
|
||||||
|
if (
|
||||||
|
oldValue !== undefined &&
|
||||||
|
listId === oldListId && blurHash === oldBlurHash
|
||||||
|
) {
|
||||||
|
// list hasn't changed
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
backgroundLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const blurHashPromise = getBlobFromBlurHash(blurHash).then((blurHash) => {
|
||||||
|
blurHashUrl.value = blurHash ? window.URL.createObjectURL(blurHash) : ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const listService = new ListService()
|
||||||
|
const backgroundPromise = listService.background(list.value).then((result) => {
|
||||||
|
background.value = result
|
||||||
|
})
|
||||||
|
await Promise.all([blurHashPromise, backgroundPromise])
|
||||||
|
} finally {
|
||||||
|
backgroundLoading.value = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true },
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
background,
|
||||||
|
blurHashUrl,
|
||||||
|
backgroundLoading,
|
||||||
|
}
|
||||||
|
}
|
@ -39,8 +39,8 @@ export default class ListService extends AbstractService<IList> {
|
|||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
async background(list) {
|
async background(list: Pick<IList, 'id' | 'backgroundInformation'>) {
|
||||||
if (list.background === null) {
|
if (list.backgroundInformation === null) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +52,7 @@ export default class ListService extends AbstractService<IList> {
|
|||||||
return window.URL.createObjectURL(new Blob([response.data]))
|
return window.URL.createObjectURL(new Blob([response.data]))
|
||||||
}
|
}
|
||||||
|
|
||||||
async removeBackground(list) {
|
async removeBackground(list: Pick<IList, 'id'>) {
|
||||||
const cancel = this.setLoading()
|
const cancel = this.setLoading()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -32,8 +32,4 @@ $switch-view-height: 2.69rem;
|
|||||||
|
|
||||||
$navbar-height: 4rem;
|
$navbar-height: 4rem;
|
||||||
$navbar-width: 300px;
|
$navbar-width: 300px;
|
||||||
$navbar-icon-width: 40px;
|
$navbar-icon-width: 40px;
|
||||||
|
|
||||||
$lists-per-row: 5;
|
|
||||||
$list-height: 150px;
|
|
||||||
$list-spacing: 1rem;
|
|
@ -40,17 +40,11 @@
|
|||||||
</template>
|
</template>
|
||||||
<div v-if="listHistory.length > 0" class="is-max-width-desktop has-text-left mt-4">
|
<div v-if="listHistory.length > 0" class="is-max-width-desktop has-text-left mt-4">
|
||||||
<h3>{{ $t('home.lastViewed') }}</h3>
|
<h3>{{ $t('home.lastViewed') }}</h3>
|
||||||
<div class="is-flex list-cards-wrapper-2-rows">
|
<ListCardGrid :lists="listHistory" v-cy="'listCardGrid'" />
|
||||||
<list-card
|
|
||||||
v-for="(l, k) in listHistory"
|
|
||||||
:key="`l${k}`"
|
|
||||||
:list="l"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<ShowTasks
|
<ShowTasks
|
||||||
v-if="hasLists"
|
v-if="hasLists"
|
||||||
class="mt-4"
|
class="show-tasks"
|
||||||
:key="showTasksKey"
|
:key="showTasksKey"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -61,7 +55,7 @@ import {ref, computed} from 'vue'
|
|||||||
|
|
||||||
import Message from '@/components/misc/message.vue'
|
import Message from '@/components/misc/message.vue'
|
||||||
import ShowTasks from '@/views/tasks/ShowTasks.vue'
|
import ShowTasks from '@/views/tasks/ShowTasks.vue'
|
||||||
import ListCard from '@/components/list/partials/list-card.vue'
|
import ListCardGrid from '@/components/list/partials/ListCardGrid.vue'
|
||||||
import AddTask from '@/components/tasks/add-task.vue'
|
import AddTask from '@/components/tasks/add-task.vue'
|
||||||
|
|
||||||
import {getHistory} from '@/modules/listHistory'
|
import {getHistory} from '@/modules/listHistory'
|
||||||
@ -113,14 +107,8 @@ function updateTaskList() {
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style scoped lang="scss">
|
||||||
.list-cards-wrapper-2-rows {
|
.show-tasks {
|
||||||
flex-wrap: wrap;
|
margin-top: 2rem;
|
||||||
max-height: calc(#{$list-height * 2} + #{$list-spacing * 2} - 4px);
|
|
||||||
overflow: hidden;
|
|
||||||
|
|
||||||
@media screen and (max-width: $mobile) {
|
|
||||||
max-height: calc(#{$list-height * 4} + #{$list-spacing * 4} - 4px);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
@ -15,28 +15,28 @@
|
|||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<p class="has-text-centered has-text-grey mt-4 is-italic" v-if="namespaces.length === 0">
|
<p v-if="namespaces.length === 0" class="has-text-centered has-text-grey mt-4 is-italic">
|
||||||
{{ $t('namespace.noneAvailable') }}
|
{{ $t('namespace.noneAvailable') }}
|
||||||
<router-link :to="{name: 'namespace.create'}">
|
<BaseButton :to="{name: 'namespace.create'}">
|
||||||
{{ $t('namespace.create.title') }}.
|
{{ $t('namespace.create.title') }}.
|
||||||
</router-link>
|
</BaseButton>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<section :key="`n${n.id}`" class="namespace" v-for="n in namespaces">
|
<section :key="`n${n.id}`" class="namespace" v-for="n in namespaces">
|
||||||
<x-button
|
<x-button
|
||||||
|
v-if="n.id > 0 && n.lists.length > 0"
|
||||||
:to="{name: 'list.create', params: {namespaceId: n.id}}"
|
:to="{name: 'list.create', params: {namespaceId: n.id}}"
|
||||||
class="is-pulled-right"
|
class="is-pulled-right"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
v-if="n.id > 0 && n.lists.length > 0"
|
|
||||||
icon="plus"
|
icon="plus"
|
||||||
>
|
>
|
||||||
{{ $t('list.create.header') }}
|
{{ $t('list.create.header') }}
|
||||||
</x-button>
|
</x-button>
|
||||||
<x-button
|
<x-button
|
||||||
|
v-if="n.isArchived"
|
||||||
:to="{name: 'namespace.settings.archive', params: {id: n.id}}"
|
:to="{name: 'namespace.settings.archive', params: {id: n.id}}"
|
||||||
class="is-pulled-right mr-4"
|
class="is-pulled-right mr-4"
|
||||||
variant="secondary"
|
variant="secondary"
|
||||||
v-if="n.isArchived"
|
|
||||||
icon="archive"
|
icon="archive"
|
||||||
>
|
>
|
||||||
{{ $t('namespace.unarchive') }}
|
{{ $t('namespace.unarchive') }}
|
||||||
@ -44,26 +44,22 @@
|
|||||||
|
|
||||||
<h2 class="namespace-title">
|
<h2 class="namespace-title">
|
||||||
<span v-cy="'namespace-title'">{{ getNamespaceTitle(n) }}</span>
|
<span v-cy="'namespace-title'">{{ getNamespaceTitle(n) }}</span>
|
||||||
<span class="is-archived" v-if="n.isArchived">
|
<span v-if="n.isArchived" class="is-archived">
|
||||||
{{ $t('namespace.archived') }}
|
{{ $t('namespace.archived') }}
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p class="has-text-centered has-text-grey mt-4 is-italic" v-if="n.lists.length === 0">
|
<p v-if="n.lists.length === 0" class="has-text-centered has-text-grey mt-4 is-italic">
|
||||||
{{ $t('namespace.noLists') }}
|
{{ $t('namespace.noLists') }}
|
||||||
<router-link :to="{name: 'list.create', params: {namespaceId: n.id}}">
|
<BaseButton :to="{name: 'list.create', params: {namespaceId: n.id}}">
|
||||||
{{ $t('namespace.createList') }}
|
{{ $t('namespace.createList') }}
|
||||||
</router-link>
|
</BaseButton>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="lists">
|
<ListCardGrid v-else
|
||||||
<list-card
|
:lists="n.lists"
|
||||||
v-for="l in n.lists"
|
:show-archived="showArchived"
|
||||||
:key="`l${l.id}`"
|
/>
|
||||||
:list="l"
|
|
||||||
:show-archived="showArchived"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -72,8 +68,9 @@
|
|||||||
import {computed} from 'vue'
|
import {computed} from 'vue'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
import Fancycheckbox from '@/components/input/fancycheckbox.vue'
|
||||||
import ListCard from '@/components/list/partials/list-card.vue'
|
import ListCardGrid from '@/components/list/partials/ListCardGrid.vue'
|
||||||
|
|
||||||
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
|
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
|
||||||
import {useTitle} from '@/composables/useTitle'
|
import {useTitle} from '@/composables/useTitle'
|
||||||
@ -89,11 +86,10 @@ const showArchived = useStorage('showArchived', false)
|
|||||||
|
|
||||||
const loading = computed(() => namespaceStore.isLoading)
|
const loading = computed(() => namespaceStore.isLoading)
|
||||||
const namespaces = computed(() => {
|
const namespaces = computed(() => {
|
||||||
return namespaceStore.namespaces.filter(n => showArchived.value ? true : !n.isArchived)
|
return namespaceStore.namespaces.filter(namespace => showArchived.value
|
||||||
// return namespaceStore.namespaces.filter(n => showArchived.value ? true : !n.isArchived).map(n => {
|
? true
|
||||||
// n.lists = n.lists.filter(l => !l.isArchived)
|
: !namespace.isArchived,
|
||||||
// return n
|
)
|
||||||
// })
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -121,10 +117,8 @@ const namespaces = computed(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.namespace {
|
.namespace:not(:first-child) {
|
||||||
& + & {
|
margin-top: 1rem;
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.namespace-title {
|
.namespace-title {
|
||||||
@ -142,9 +136,4 @@ const namespaces = computed(() => {
|
|||||||
background: var(--white-translucent);
|
background: var(--white-translucent);
|
||||||
margin-left: .5rem;
|
margin-left: .5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.lists {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
}
|
|
||||||
</style>
|
</style>
|
Loading…
x
Reference in New Issue
Block a user