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 HelloWorld from './components/Welcome.vue'
|
||||||
import { store } from './store.js'
|
import { store } from './store.js'
|
||||||
import {clearSession} from "./store.js";
|
import {clearSession} from "./store.js";
|
||||||
|
import FatalError from "./components/FatalError.vue";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logout the user.
|
* Logout the user.
|
||||||
*/
|
*/
|
||||||
@ -25,6 +27,10 @@ function logOut() {
|
|||||||
<RouterLink to="/about">About</RouterLink>
|
<RouterLink to="/about">About</RouterLink>
|
||||||
<a v-if="store.session" href="" @click="logOut()">Log Out</a>
|
<a v-if="store.session" href="" @click="logOut()">Log Out</a>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
|
<FatalError v-if="store.fatalError" :error-msg="store.fatalError"/>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
@ -39,6 +39,9 @@
|
|||||||
--color-input: rgba(0,0,0,.85);
|
--color-input: rgba(0,0,0,.85);
|
||||||
--color-input-border-focus: rgba(0,0,0,.2);
|
--color-input-border-focus: rgba(0,0,0,.2);
|
||||||
--color-error-text: darkred;
|
--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) {
|
@media (prefers-color-scheme: dark) {
|
||||||
@ -56,6 +59,9 @@
|
|||||||
--color-input: rgba(255,255,255,.8);
|
--color-input: rgba(255,255,255,.8);
|
||||||
--color-input-border-focus: rgba(0,0,0,.4);
|
--color-input-border-focus: rgba(0,0,0,.4);
|
||||||
--color-error-text: red;
|
--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;
|
color: white;
|
||||||
border-color: #1890ff;
|
border-color: #1890ff;
|
||||||
}
|
}
|
||||||
|
&.btn-large {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
padding: 6px 25px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-container {
|
||||||
|
margin: 30px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
form .error {
|
form .error {
|
||||||
|
@ -6,21 +6,50 @@
|
|||||||
-->
|
-->
|
||||||
<template>
|
<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 id="filter-form">
|
||||||
<div class="todo-v-container">
|
<label class="switch">
|
||||||
<input class="todo-title" type="text" v-bind="task.title">
|
<input type="checkbox" v-model="showCompleted">
|
||||||
<input class="todo-description" type="text" v-bind="task.description">
|
Show completed
|
||||||
</div>
|
</label>
|
||||||
<div class="todo-meta">
|
|
||||||
<img src="@/assets/img/trash-solid.png" class="btn-icon">
|
<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>
|
||||||
|
|
||||||
</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>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
@ -29,30 +58,103 @@ import {store} from "../store";
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "TodoRoot",
|
name: "TodoRoot",
|
||||||
|
components: [],
|
||||||
|
|
||||||
/** Current state of the component */
|
/** Current state of the component */
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
/** whether to show completed items */
|
||||||
|
showCompleted: true,
|
||||||
/* array of todo lists currently known */
|
/* array of todo lists currently known */
|
||||||
lists: [
|
lists: [
|
||||||
{ id: 1, title: "Default"},
|
{ id: 1, title: "Default"},
|
||||||
{ id: 2, title: "House" },
|
{ id: 2, title: "House" },
|
||||||
{ id: 3, title: "Car" },
|
{ id: 3, title: "Car" },
|
||||||
|
{ id: 4, title: "Work" },
|
||||||
],
|
],
|
||||||
/* the id of the list currently being shown */
|
/* the id of the list currently being shown */
|
||||||
currentList: 1,
|
currentList: 1,
|
||||||
/* known tasks */
|
/* known tasks */
|
||||||
tasks: [
|
tasks: [
|
||||||
{ id: 1, completed: false, title: "Go to dentist", description: "Last time is way too long ago...", list: { id: 1 } },
|
{ 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: 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: 2 } },
|
{ id: 3, completed: false, title: "Clean windows", description: "Regualar stuff", list: { id: 1 } },
|
||||||
{ id: 4, completed: false, title: "Oil ove hinges", description: "They are stiff and screechy", list: { id: 2 } },
|
{ 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: {
|
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
|
* 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.
|
* mounted lifecycle hook.
|
||||||
*/
|
*/
|
||||||
@ -107,26 +231,111 @@ export default {
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
#todo-container {
|
||||||
|
height: calc(100vh - 200px);
|
||||||
|
overflow: auto;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
.todo-item {
|
.todo-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
background-color: #aaa;
|
background-color: var(--color-item-background);
|
||||||
margin-bottom: 5px;
|
box-shadow: var(--item-box-shadow);
|
||||||
padding: 10px;
|
margin-bottom: 10px;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: .3em;
|
||||||
|
border: 3px solid transparent;
|
||||||
|
&.completed {
|
||||||
|
border: 3px solid #41ab57;
|
||||||
|
}
|
||||||
input {
|
input {
|
||||||
border: none;
|
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 {
|
.todo-title {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
}
|
}
|
||||||
.todo-description {
|
}
|
||||||
color: #555;
|
|
||||||
}
|
h1 {
|
||||||
|
margin-bottom: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.todo-v-container {
|
.todo-v-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
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>
|
</style>
|
@ -31,8 +31,6 @@ function login() {
|
|||||||
|
|
||||||
<TodoRoot v-if="loggedIn()" />
|
<TodoRoot v-if="loggedIn()" />
|
||||||
|
|
||||||
<FatalError v-if="store.fatalError" :error-msg="store.fatalError"/>
|
|
||||||
|
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user