adding items, set them complete, filter, ...
This commit is contained in:
parent
31348bbc63
commit
5576a46537
@ -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>
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
1
src/assets/img/check-gray.svg
Normal file
1
src/assets/img/check-gray.svg
Normal 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 |
1
src/assets/img/check-green.svg
Normal file
1
src/assets/img/check-green.svg
Normal 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 |
@ -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 {
|
||||
|
@ -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)">
|
||||
|
||||
</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>
|
@ -31,8 +31,6 @@ function login() {
|
||||
|
||||
<TodoRoot v-if="loggedIn()" />
|
||||
|
||||
<FatalError v-if="store.fatalError" :error-msg="store.fatalError"/>
|
||||
|
||||
</main>
|
||||
</template>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user