341 lines
8.6 KiB
Vue
341 lines
8.6 KiB
Vue
<!--
|
|
Root component for the Todo app that is displayed if the user is logged in.
|
|
|
|
Part of the basebox sample Todo app.
|
|
https://basebox.tech
|
|
-->
|
|
<template>
|
|
|
|
<main>
|
|
|
|
<h1>
|
|
ToDo List
|
|
</h1>
|
|
|
|
<div id="filter-form">
|
|
<label class="switch">
|
|
<input type="checkbox" v-model="showCompleted">
|
|
Show completed
|
|
</label>
|
|
|
|
<div class="form-control">
|
|
<label for="list-selector">List: </label>
|
|
<select id="list-selector" v-model="currentList">
|
|
<option :value="0">All</option>
|
|
<option v-for="item in lists" :key="item.id" :value="item.id">{{ item.title }}</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="todo-container">
|
|
|
|
<transition-group name="list" tag="div">
|
|
<div :class="todoItemClass(task)" v-for="(task, i) in filteredItems" :id="'task-' + task.id" :key="task.id">
|
|
<div :class="todoCheckedClass(task)" @click="toggleCompleted(task)">
|
|
|
|
</div>
|
|
<div class="todo-v-container">
|
|
<input class="todo-title" type="text" v-model="task.title">
|
|
<input class="todo-description" type="text" v-model="task.description">
|
|
</div>
|
|
<div class="todo-meta">
|
|
<img src="@/assets/img/trash-solid.png" class="btn-icon">
|
|
</div>
|
|
</div>
|
|
</transition-group>
|
|
</div>
|
|
|
|
<div class="btn-container">
|
|
<button type="button" @click="addItem()" class="btn btn-primary btn-large">Add Item</button>
|
|
</div>
|
|
|
|
</main>
|
|
</template>
|
|
|
|
<script>
|
|
import {gqlQuery} from "../util/net";
|
|
import {store} from "../store";
|
|
|
|
export default {
|
|
name: "TodoRoot",
|
|
components: [],
|
|
|
|
/** Current state of the component */
|
|
data() {
|
|
return {
|
|
/** whether to show completed items */
|
|
showCompleted: true,
|
|
/* array of todo lists currently known */
|
|
lists: [
|
|
{ id: 1, title: "Default"},
|
|
{ id: 2, title: "House" },
|
|
{ id: 3, title: "Car" },
|
|
{ id: 4, title: "Work" },
|
|
],
|
|
/* the id of the list currently being shown */
|
|
currentList: 1,
|
|
/* known tasks */
|
|
tasks: [
|
|
{ id: 1, completed: false, title: "Go to dentist", description: "Last time is way too long ago...", list: { id: 1 } },
|
|
{ id: 2, completed: true, title: "Change engine oil", description: "Use the good one", list: { id: 3 } },
|
|
{ id: 3, completed: false, title: "Clean windows", description: "Regualar stuff", list: { id: 1 } },
|
|
{ id: 4, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
{ id: 5, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
{ id: 6, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
{ id: 7, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
{ id: 8, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
{ id: 9, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
{ id: 10, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
{ id: 11, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
{ id: 12, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
{ id: 13, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
{ id: 14, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
{ id: 15, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
{ id: 16, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
{ id: 17, completed: false, title: "Oil stove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
|
],
|
|
}
|
|
},
|
|
|
|
methods: {
|
|
|
|
/**
|
|
* Add an item to the current list.
|
|
*/
|
|
addItem() {
|
|
if (this.currentList === 0) {
|
|
window.alert("Please select a list at the top right before adding an item.");
|
|
return;
|
|
}
|
|
|
|
/* fake ID of new item */
|
|
const newItemId = this.tasks.length + 1;
|
|
|
|
this.tasks.push({
|
|
id: newItemId,
|
|
completed: false,
|
|
title: "Enter task here",
|
|
description: "",
|
|
list: {
|
|
id: this.currentList,
|
|
}
|
|
});
|
|
|
|
/* wait a moment, then scroll list to the bottom */
|
|
setTimeout(function() {
|
|
document.getElementById("todo-container").scrollTo({
|
|
top: 100000,
|
|
behavior: "smooth"
|
|
});
|
|
/* select text in new item's title field */
|
|
const titleInput = document.querySelector(`#task-${newItemId} .todo-title`);
|
|
titleInput.setSelectionRange(0, 1000);
|
|
titleInput.focus();
|
|
}, 100);
|
|
},
|
|
|
|
/**
|
|
* Toggle completed state of a todo item.
|
|
* @param task the todo item to toggle.
|
|
*/
|
|
toggleCompleted(task) {
|
|
task.completed = !task.completed;
|
|
},
|
|
|
|
/**
|
|
* Return class object for a todo checked
|
|
*
|
|
* @param task the item
|
|
*/
|
|
todoCheckedClass(task) {
|
|
return {
|
|
"todo-checked": true,
|
|
"completed": task.completed,
|
|
"uncompleted": !task.completed,
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Return class object for a todo item
|
|
*
|
|
* @param task the item
|
|
*/
|
|
todoItemClass(task) {
|
|
return {
|
|
"todo-item": true,
|
|
"completed": task.completed,
|
|
}
|
|
},
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
/**
|
|
* Return array of tasks to show.
|
|
*/
|
|
filteredItems() {
|
|
return this.tasks.filter((item) => {
|
|
/* hide completed items if the option to show them is not set */
|
|
if (!this.showCompleted && item.completed) {
|
|
return false;
|
|
}
|
|
/* only return items of the current list */
|
|
if (this.currentList && item.list.id !== this.currentList) {
|
|
return false;
|
|
}
|
|
return true;
|
|
})
|
|
|
|
}
|
|
|
|
},
|
|
|
|
/**
|
|
* mounted lifecycle hook.
|
|
*/
|
|
mounted() {
|
|
if (!store.session) {
|
|
console.error("TodoRoot component must not be loaded if user is not logged in.");
|
|
return;
|
|
}
|
|
|
|
/* Load user info and his/her lists and todos */
|
|
gqlQuery(`query {
|
|
getUser(username: "${store.session.userName}") {
|
|
name
|
|
lists {
|
|
id
|
|
title
|
|
}
|
|
tasks {
|
|
id
|
|
title
|
|
description
|
|
completed
|
|
list {
|
|
id
|
|
}
|
|
}
|
|
}
|
|
}`).then(rspJson => {
|
|
|
|
}).catch(err => {
|
|
|
|
});
|
|
|
|
},
|
|
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
#todo-container {
|
|
height: calc(100vh - 200px);
|
|
overflow: auto;
|
|
margin-top: 20px;
|
|
}
|
|
|
|
.todo-item {
|
|
display: flex;
|
|
background-color: var(--color-item-background);
|
|
box-shadow: var(--item-box-shadow);
|
|
margin-bottom: 10px;
|
|
padding: 15px;
|
|
border-radius: .3em;
|
|
border: 3px solid transparent;
|
|
&.completed {
|
|
border: 3px solid #41ab57;
|
|
}
|
|
input {
|
|
border: none;
|
|
border-bottom: 1px solid var(--color-border);
|
|
outline: none;
|
|
margin-bottom: 15px;
|
|
color: var(--color-item-text);
|
|
background: transparent;
|
|
&:focus {
|
|
outline: none;
|
|
}
|
|
}
|
|
.todo-title {
|
|
font-size: 1.5rem;
|
|
}
|
|
}
|
|
|
|
h1 {
|
|
margin-bottom: 25px;
|
|
}
|
|
|
|
.todo-v-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
flex-grow: 1;
|
|
background-color: inherit;
|
|
}
|
|
|
|
.todo-meta {
|
|
padding: 5px 5px 5px 25px;
|
|
}
|
|
|
|
.btn-icon {
|
|
width: 16px;
|
|
height: 16px;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.todo-checked {
|
|
width: 80px;
|
|
cursor: pointer;
|
|
margin-right: 20px;
|
|
min-height: 80px;
|
|
border-radius: 50%;
|
|
border: 4px solid var(--color-border);
|
|
background-size: 80%;
|
|
background-position: center center;
|
|
background-repeat: no-repeat;
|
|
&.uncompleted {
|
|
&:hover {
|
|
background-image: url(../assets/img/check-gray.svg);
|
|
}
|
|
}
|
|
&.completed {
|
|
border-color: #41ab57;
|
|
background-image: url(../assets/img/check-green.svg);
|
|
}
|
|
}
|
|
|
|
.header-buttons {
|
|
float:right;
|
|
font-size: 1rem;
|
|
}
|
|
|
|
.switch {
|
|
input {
|
|
transform: scale(150%);
|
|
margin-right: 6px;
|
|
}
|
|
}
|
|
|
|
.list-enter-active {
|
|
animation: bounce-in 0.4s;
|
|
}
|
|
.list-leave-active {
|
|
animation: bounce-in 0.4s reverse;
|
|
}
|
|
|
|
@keyframes bounce-in {
|
|
0% {
|
|
transform: scale(0);
|
|
}
|
|
100% {
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
#filter-form {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
</style> |