Add animations and pin create card input
Signed-off-by: Julius Härtl <jus@bitgrid.net>
This commit is contained in:
@@ -23,12 +23,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="board-wrapper">
|
<div class="board-wrapper">
|
||||||
<Controls :board="board" />
|
<Controls :board="board" />
|
||||||
<div v-if="loading" class="emptycontent">
|
<transition name="fade" mode="out-in">
|
||||||
|
<div v-if="loading" class="emptycontent" key="loading">
|
||||||
<div class="icon icon-loading" />
|
<div class="icon icon-loading" />
|
||||||
<h2>{{ t('deck', 'Loading board') }}</h2>
|
<h2>{{ t('deck', 'Loading board') }}</h2>
|
||||||
<p />
|
<p />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="board" class="board">
|
<div v-else-if="board && !loading" class="board" key="board">
|
||||||
<Container lock-axix="y"
|
<Container lock-axix="y"
|
||||||
orientation="horizontal"
|
orientation="horizontal"
|
||||||
:drag-handle-selector="dragHandleSelector"
|
:drag-handle-selector="dragHandleSelector"
|
||||||
@@ -38,11 +39,13 @@
|
|||||||
</Draggable>
|
</Draggable>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="emptycontent">
|
<div v-else class="emptycontent" key="notfound">
|
||||||
<div class="icon icon-deck" />
|
<div class="icon icon-deck" />
|
||||||
<h2>{{ t('deck', 'Board not found') }}</h2>
|
<h2>{{ t('deck', 'Board not found') }}</h2>
|
||||||
<p />
|
<p />
|
||||||
</div>
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -102,8 +105,12 @@ export default {
|
|||||||
methods: {
|
methods: {
|
||||||
async fetchData() {
|
async fetchData() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
try {
|
||||||
await this.$store.dispatch('loadBoardById', this.id)
|
await this.$store.dispatch('loadBoardById', this.id)
|
||||||
await this.$store.dispatch('loadStacks', this.id)
|
await this.$store.dispatch('loadStacks', this.id)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
|
}
|
||||||
this.loading = false
|
this.loading = false
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -125,6 +132,8 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
@import "../../css/animations.scss";
|
||||||
|
|
||||||
$board-spacing: 15px;
|
$board-spacing: 15px;
|
||||||
$stack-spacing: 10px;
|
$stack-spacing: 10px;
|
||||||
$stack-width: 300px;
|
$stack-width: 300px;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="stack">
|
<div class="stack">
|
||||||
<div class="stack--header">
|
<div v-click-outside="stopCardCreation" class="stack--header">
|
||||||
<transition name="fade" mode="out-in">
|
<transition name="fade" mode="out-in">
|
||||||
<h3 v-if="!canManage">
|
<h3 v-if="!canManage">
|
||||||
{{ stack.title }}
|
{{ stack.title }}
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
{{ stack.title }}
|
{{ stack.title }}
|
||||||
</h3>
|
</h3>
|
||||||
<form v-else @submit.prevent="finishedEdit(stack)">
|
<form v-else @submit.prevent="finishedEdit(stack)">
|
||||||
<input v-model="copiedStack.title" type="text" autofocus>
|
<input v-model="copiedStack.title" v-focus type="text">
|
||||||
<input v-tooltip="t('deck', 'Add a new stack')"
|
<input v-tooltip="t('deck', 'Add a new stack')"
|
||||||
class="icon-confirm"
|
class="icon-confirm"
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -45,31 +45,35 @@
|
|||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Actions>
|
</Actions>
|
||||||
<Actions v-if="canEdit && !showArchived">
|
<Actions v-if="canEdit && !showArchived">
|
||||||
<ActionButton icon="icon-add" @click="showAddCard=true">
|
<ActionButton icon="icon-add" @click.stop="showAddCard=true">
|
||||||
{{ t('deck', 'Add card') }}
|
{{ t('deck', 'Add card') }}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</Actions>
|
</Actions>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form v-if="showAddCard"
|
<transition name="slide-top" appear>
|
||||||
class="stack--card-add"
|
<div v-if="showAddCard" class="stack--card-add">
|
||||||
:class="{ 'icon-loading-small': stateCardCreating }"
|
<form :class="{ 'icon-loading-small': stateCardCreating }"
|
||||||
@submit.prevent="clickAddCard()">
|
@submit.prevent.stop="clickAddCard()">
|
||||||
<label for="new-stack-input-main" class="hidden-visually">{{ t('deck', 'Add a new card') }}</label>
|
<label for="new-stack-input-main" class="hidden-visually">{{ t('deck', 'Add a new card') }}</label>
|
||||||
<input id="new-stack-input-main"
|
<input id="new-stack-input-main"
|
||||||
|
ref="newCardInput"
|
||||||
v-model="newCardTitle"
|
v-model="newCardTitle"
|
||||||
v-focus
|
v-focus
|
||||||
type="text"
|
type="text"
|
||||||
class="no-close"
|
class="no-close"
|
||||||
:disabled="stateCardCreating"
|
:disabled="stateCardCreating"
|
||||||
placeholder="Add a new card"
|
placeholder="Add a new card"
|
||||||
required>
|
required
|
||||||
|
@keydown.esc="stopCardCreation">
|
||||||
|
|
||||||
<input v-show="!stateCardCreating"
|
<input v-show="!stateCardCreating"
|
||||||
class="icon-confirm"
|
class="icon-confirm"
|
||||||
type="submit"
|
type="submit"
|
||||||
value="">
|
value="">
|
||||||
</form>
|
</form>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
|
||||||
<Container :get-child-payload="payloadForCard(stack.id)"
|
<Container :get-child-payload="payloadForCard(stack.id)"
|
||||||
group-name="stack"
|
group-name="stack"
|
||||||
@@ -78,7 +82,11 @@
|
|||||||
@should-accept-drop="canEdit"
|
@should-accept-drop="canEdit"
|
||||||
@drop="($event) => onDropCard(stack.id, $event)">
|
@drop="($event) => onDropCard(stack.id, $event)">
|
||||||
<Draggable v-for="card in cardsByStack" :key="card.id">
|
<Draggable v-for="card in cardsByStack" :key="card.id">
|
||||||
<CardItem v-if="card" :id="card.id" />
|
<transition :appear="animate && !card.animated && (card.animated=true)"
|
||||||
|
:appear-class="'zoom-appear-class'"
|
||||||
|
:appear-active-class="'zoom-appear-active-class'">
|
||||||
|
<CardItem :id="card.id" />
|
||||||
|
</transition>
|
||||||
</Draggable>
|
</Draggable>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,6 +122,7 @@ export default {
|
|||||||
newCardTitle: '',
|
newCardTitle: '',
|
||||||
showAddCard: false,
|
showAddCard: false,
|
||||||
stateCardCreating: false,
|
stateCardCreating: false,
|
||||||
|
animate: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -133,6 +142,16 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
stopCardCreation(e) {
|
||||||
|
// For some reason the submit event triggers a MouseEvent that is bubbling to the outside
|
||||||
|
// so we have to ignore it
|
||||||
|
e.stopPropagation()
|
||||||
|
if (this.$refs.newCardInput && this.$refs.newCardInput.parentElement === e.target.parentElement) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
this.showAddCard = false
|
||||||
|
return false
|
||||||
|
},
|
||||||
async onDropCard(stackId, event) {
|
async onDropCard(stackId, event) {
|
||||||
const { addedIndex, removedIndex, payload } = event
|
const { addedIndex, removedIndex, payload } = event
|
||||||
const card = Object.assign({}, payload)
|
const card = Object.assign({}, payload)
|
||||||
@@ -172,13 +191,19 @@ export default {
|
|||||||
async clickAddCard() {
|
async clickAddCard() {
|
||||||
this.stateCardCreating = true
|
this.stateCardCreating = true
|
||||||
try {
|
try {
|
||||||
await this.$store.dispatch('addCard', {
|
this.animate = true
|
||||||
|
const newCard = await this.$store.dispatch('addCard', {
|
||||||
title: this.newCardTitle,
|
title: this.newCardTitle,
|
||||||
stackId: this.stack.id,
|
stackId: this.stack.id,
|
||||||
boardId: this.stack.boardId,
|
boardId: this.stack.boardId,
|
||||||
})
|
})
|
||||||
this.newCardTitle = ''
|
this.newCardTitle = ''
|
||||||
this.showAddCard = false
|
this.showAddCard = true
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.newCardInput.focus()
|
||||||
|
this.animate = false
|
||||||
|
})
|
||||||
|
this.$router.push({ name: 'card', params: { cardId: newCard.id } })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
OCP.Toast.error('Could not create card: ' + e.response.data.message)
|
OCP.Toast.error('Could not create card: ' + e.response.data.message)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -208,8 +233,8 @@ export default {
|
|||||||
padding: 3px;
|
padding: 3px;
|
||||||
margin: 3px -3px;
|
margin: 3px -3px;
|
||||||
margin-right: -10px;
|
margin-right: -10px;
|
||||||
margin-bottom: 5px;
|
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
background-color: var(--color-main-background-translucent);
|
background-color: var(--color-main-background-translucent);
|
||||||
|
|
||||||
h3, form {
|
h3, form {
|
||||||
@@ -223,10 +248,25 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stack--card-add {
|
.stack--card-add {
|
||||||
|
position: sticky;
|
||||||
|
top: 52px;
|
||||||
|
height: 52px;
|
||||||
|
z-index: 100;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
background-color: var(--color-main-background);
|
||||||
|
margin-left: -10px;
|
||||||
|
margin-right: -10px;
|
||||||
|
padding-top: 3px;
|
||||||
|
|
||||||
|
form {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
margin: 10px;
|
||||||
|
margin-top: 0;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
box-shadow: 0 0 3px var(--color-box-shadow);
|
box-shadow: 0 0 3px var(--color-box-shadow);
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
&.icon-loading-small:after,
|
&.icon-loading-small:after,
|
||||||
&.icon-loading-small-dark:after {
|
&.icon-loading-small-dark:after {
|
||||||
@@ -241,9 +281,22 @@ export default {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.stack .smooth-dnd-container.vertical {
|
||||||
|
margin-top: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rules to handle scrolling behaviour are inherited from Board.vue
|
* Rules to handle scrolling behaviour are inherited from Board.vue
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
.slide-top-enter-active,
|
||||||
|
.slide-top-leave-active {
|
||||||
|
transition: all 100ms ease;
|
||||||
|
}
|
||||||
|
.slide-top-enter, .slide-top-leave-to {
|
||||||
|
transform: translateY(-10px);
|
||||||
|
opacity: 0;
|
||||||
|
height: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -236,7 +236,7 @@ export default {
|
|||||||
descriptionSaveTimeout: null,
|
descriptionSaveTimeout: null,
|
||||||
descriptionSaving: false,
|
descriptionSaving: false,
|
||||||
hasActivity: capabilities && capabilities.activity,
|
hasActivity: capabilities && capabilities.activity,
|
||||||
hasComments: !!OC.appswebroots['comments']
|
hasComments: !!OC.appswebroots['comments'],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|||||||
@@ -48,16 +48,21 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div v-if="!editing" class="right">
|
<div v-if="!editing" class="right">
|
||||||
|
<transition name="zoom">
|
||||||
<div v-if="card.duedate" :class="dueIcon">
|
<div v-if="card.duedate" :class="dueIcon">
|
||||||
<span>{{ relativeDate }}</span>
|
<span>{{ relativeDate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<ul v-if="card.labels && card.labels.length > 0" class="labels" @click="openCard">
|
<transition-group name="zoom"
|
||||||
|
tag="ul"
|
||||||
|
class="labels"
|
||||||
|
@click="openCard">
|
||||||
<li v-for="label in card.labels" :key="label.id" :style="labelStyle(label)">
|
<li v-for="label in card.labels" :key="label.id" :style="labelStyle(label)">
|
||||||
<span>{{ label.title }}</span>
|
<span>{{ label.title }}</span>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</transition-group>
|
||||||
<div v-show="!compactMode" class="card-controls compact-item" @click="openCard">
|
<div v-show="!compactMode" class="card-controls compact-item" @click="openCard">
|
||||||
<CardBadges :id="id" />
|
<CardBadges :id="id" />
|
||||||
</div>
|
</div>
|
||||||
@@ -132,6 +137,13 @@ export default {
|
|||||||
return moment(this.card.duedate).format('LLLL')
|
return moment(this.card.duedate).format('LLLL')
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
watch: {
|
||||||
|
currentCard(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.$nextTick(() => this.$el.scrollIntoView())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openCard() {
|
openCard() {
|
||||||
this.$router.push({ name: 'card', params: { cardId: this.id } })
|
this.$router.push({ name: 'card', params: { cardId: this.id } })
|
||||||
@@ -154,6 +166,8 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@import './../../css/animations';
|
||||||
|
|
||||||
$card-spacing: 10px;
|
$card-spacing: 10px;
|
||||||
$card-padding: 10px;
|
$card-padding: 10px;
|
||||||
|
|
||||||
|
|||||||
25
src/css/animations.scss
Normal file
25
src/css/animations.scss
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
.fade-enter-active {
|
||||||
|
transition: opacity 250ms;
|
||||||
|
}
|
||||||
|
.fade-enter, .fade-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.zoom-appear-active-class,
|
||||||
|
.zoom-enter-active {
|
||||||
|
animation: zoom-in var(--animation-quick);
|
||||||
|
}
|
||||||
|
.zoom-leave-active {
|
||||||
|
animation: zoom-in var(--animation-quick) reverse;
|
||||||
|
will-change: transform;
|
||||||
|
}
|
||||||
|
@keyframes zoom-in {
|
||||||
|
0% {
|
||||||
|
transform: scale(0);
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user