adding items, set them complete, filter, ...

This commit is contained in:
Markus Thielen 2023-03-13 11:17:55 +01:00
parent 31348bbc63
commit 5576a46537
7 changed files with 251 additions and 22 deletions

View File

@ -3,6 +3,8 @@ import { RouterLink, RouterView } from 'vue-router'
import HelloWorld from './components/Welcome.vue'
import { store } from './store.js'
import {clearSession} from "./store.js";
import FatalError from "./components/FatalError.vue";
/**
* Logout the user.
*/
@ -25,6 +27,10 @@ function logOut() {
<RouterLink to="/about">About</RouterLink>
<a v-if="store.session" href="" @click="logOut()">Log Out</a>
</nav>
<FatalError v-if="store.fatalError" :error-msg="store.fatalError"/>
</div>
</header>

View File

@ -39,6 +39,9 @@
--color-input: rgba(0,0,0,.85);
--color-input-border-focus: rgba(0,0,0,.2);
--color-error-text: darkred;
--color-item-background: white;
--color-item-text: rgb(33, 53, 71);
--item-box-shadow: 0 3px 12px rgba(0, 0, 0, .07), 0 1px 4px rgba(0, 0, 0, .07);
}
@media (prefers-color-scheme: dark) {
@ -56,6 +59,9 @@
--color-input: rgba(255,255,255,.8);
--color-input-border-focus: rgba(0,0,0,.4);
--color-error-text: red;
--color-item-background: #242424;
--color-item-text: white;
--item-box-shadow: 0 3px 12px rgba(0, 0, 0, .07), 0 1px 4px rgba(0, 0, 0, .07);
}
}

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 100 100" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect x="0" y="0" width="100" height="100" style="fill:none;fill-rule:nonzero;"/><path d="M37.5,67.375l-17.375,-17.375l-5.917,5.875l23.292,23.292l50,-50l-5.875,-5.875l-44.125,44.083Z" style="fill:#848484;fill-rule:nonzero;"/></svg>

After

Width:  |  Height:  |  Size: 673 B

View File

@ -0,0 +1 @@
<svg fill="#4caf50" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h24v24H0z" fill="none"/><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>

After

Width:  |  Height:  |  Size: 179 B

View File

@ -81,6 +81,14 @@ a,
color: white;
border-color: #1890ff;
}
&.btn-large {
font-size: 1.25rem;
padding: 6px 25px;
}
}
.btn-container {
margin: 30px 0;
}
form .error {

View File

@ -6,21 +6,50 @@
-->
<template>
<h1>ToDo List</h1>
<main>
<div id="todo-container">
<h1>
ToDo List
</h1>
<div :class="todoItemClass(task)" v-for="(task, i) in tasks" :id="i">
<div class="todo-v-container">
<input class="todo-title" type="text" v-bind="task.title">
<input class="todo-description" type="text" v-bind="task.description">
</div>
<div class="todo-meta">
<img src="@/assets/img/trash-solid.png" class="btn-icon">
<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>
<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)">
&nbsp;
</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>
@ -29,30 +58,103 @@ 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: "", list: { id: 3 } },
{ id: 3, completed: false, title: "Clean windows", description: "Regualar stuff", list: { id: 2 } },
{ id: 4, completed: false, title: "Oil ove hinges", description: "They are stiff and screechy", list: { id: 2 } },
{ 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
*
@ -67,6 +169,28 @@ export default {
},
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.
*/
@ -107,26 +231,111 @@ export default {
<style lang="scss" scoped>
#todo-container {
height: calc(100vh - 200px);
overflow: auto;
margin-top: 20px;
}
.todo-item {
display: flex;
background-color: #aaa;
margin-bottom: 5px;
padding: 10px;
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;
}
.todo-description {
color: #555;
}
}
h1 {
margin-bottom: 25px;
}
.todo-v-container {
display: flex;
flex-direction: row;
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>

View File

@ -31,8 +31,6 @@ function login() {
<TodoRoot v-if="loggedIn()" />
<FatalError v-if="store.fatalError" :error-msg="store.fatalError"/>
</main>
</template>