1
0
mirror of https://github.com/morris/vanilla-todo.git synced 2025-08-20 04:41:26 +02:00
Files
vanilla-todo/public/scripts/TodoLogic.js
Morris Brodersen 1bc68ef68a fix #14
2024-06-23 17:03:30 +02:00

358 lines
7.3 KiB
JavaScript

import { formatDateId } from './util.js';
import { uuid } from './uuid.js';
/**
* @typedef {{
* id: string;
* listId: string;
* index: number;
* label: string;
* done: boolean;
* fixed: boolean;
* }} TodoDataItem
*/
/**
* @typedef {{
* id: string;
* index: number;
* title: string;
* }} TodoDataCustomList
*/
/**
* @typedef {{
* items: TodoDataItem[];
* customLists: TodoDataCustomList[];
* at: string;
* customAt: number;
* }} TodoData
*/
export class TodoLogic {
/**
* @param {Date} now
* @returns {TodoData}
*/
static initTodoData(now = new Date()) {
return {
items: [],
customLists: [],
at: formatDateId(now),
customAt: 0,
};
}
static getTodoListsByDay(data, range) {
const listsByDay = [];
for (let i = 0; i < 2 * range; ++i) {
const t = new Date(`${data.at}T00:00:00`);
t.setDate(t.getDate() - range + i);
const id = formatDateId(t);
listsByDay.push({
id,
items: TodoLogic.getTodoItemsForList(data, id),
position: -range + i,
});
}
return listsByDay;
}
static getTodoItemsForList(data, listId) {
return data.items
.filter((item) => item.listId === listId)
.sort((a, b) => a.index - b.index);
}
/**
* @param {TodoData} data
* @param {{listId: string, label: string}} input
* @returns {TodoData}
*/
static addTodoItem(data, input, now = new Date()) {
let index = 0;
for (const item of data.items) {
if (item.listId === input.listId) {
index = Math.max(index, item.index + 1);
}
}
return {
...data,
items: [
...data.items,
{
...input,
id: uuid(),
index,
done: false,
fixed: this.isListInThePast(input.listId, now),
},
],
};
}
/**
* @param {TodoData} data
* @param {{id: string, done: boolean}} input
* @returns {TodoData}
*/
static checkTodoItem(data, input) {
return {
...data,
items: data.items.map((item) =>
item.id === input.id ? { ...item, done: input.done } : item,
),
};
}
/**
* @param {TodoData} data
* @param {{id: string, label: string}} input
* @returns {TodoData}
*/
static editTodoItem(data, input) {
return {
...data,
items: data.items.map((item) =>
item.id === input.id ? { ...item, label: input.label } : item,
),
};
}
/**
* @param {TodoData} data
* @param {{id: string, listId: string, index: number}} input
* @returns {TodoData}
*/
static moveTodoItem(data, input, now = new Date()) {
const itemToMove = data.items.find((item) => item.id === input.id);
if (!itemToMove) return data;
// Reinsert item at target list and index
let list = data.items.filter(
(item) => item.listId === input.listId && item.id !== input.id,
);
list.splice(input.index, 0, {
...itemToMove,
listId: input.listId,
fixed: this.isListInThePast(input.listId, now),
});
list = TodoLogic.setIndexes(list);
// Reinsert updated list
let items = data.items.filter(
(item) => item.listId !== input.listId && item.id !== input.id,
);
items = [...items, ...list];
return {
...data,
items,
};
}
/**
* @param {TodoData} data
* @param {{id: string}} input
* @returns {TodoData}
*/
static deleteTodoItem(data, input) {
return {
...data,
items: data.items.filter((item) => item.id !== input.id),
};
}
/**
* @param {TodoData} data
* @param {Date} now
* @returns {TodoData}
*/
static movePastTodoItems(data, now = new Date()) {
const todayListId = formatDateId(now);
let targetIndex = 0;
for (const item of data.items) {
if (item.listId === todayListId && item.index > targetIndex) {
targetIndex = item.index;
}
}
return {
...data,
items: data.items.map((item) => {
if (
!item.done &&
!item.fixed &&
this.isListInThePast(item.listId, now)
) {
return { ...item, listId: todayListId, index: targetIndex++ };
}
return item;
}),
};
}
static isListInThePast(listId, now = new Date()) {
const todayListId = formatDateId(now);
return listId.match(/\d\d\d\d-\d\d-\d\d/) && listId < todayListId;
}
//
static getCustomTodoLists(data) {
return data.customLists
.map((list) => ({
id: list.id,
index: list.index,
title: list.title,
items: TodoLogic.getTodoItemsForList(data, list.id),
}))
.sort((a, b) => a.index - b.index);
}
/**
* @param {TodoData} data
* @returns {TodoData}
*/
static addCustomTodoList(data) {
let index = 0;
for (const customList of data.customLists) {
index = Math.max(index, customList.index + 1);
}
return {
...data,
customLists: [
...data.customLists,
{
id: uuid(),
index,
title: '',
},
],
};
}
/**
* @param {TodoData} data
* @param {{id: string, title: string}} input
* @returns {TodoData}
*/
static editCustomTodoList(data, input) {
return {
...data,
customLists: data.customLists.map((customList) =>
customList.id === input.id
? { ...customList, title: input.title }
: customList,
),
};
}
/**
* @param {TodoData} data
* @param {{id: string, index: number}} input
* @returns {TodoData}
*/
static moveCustomTodoList(data, input) {
const customListToMove = data.customLists.find(
(customList) => customList.id === input.id,
);
let customLists = data.customLists
.filter((customList) => customList.id !== input.id)
.sort((a, b) => a.index - b.index);
customLists.splice(input.index, 0, customListToMove);
customLists = TodoLogic.setIndexes(customLists);
return {
...data,
customLists,
};
}
/**
* @param {TodoData} data
* @param {{id: string}} input
* @returns {TodoData}
*/
static deleteCustomTodoList(data, input) {
return {
...data,
customLists: data.customLists.filter(
(customList) => customList.id !== input.id,
),
};
}
//
/**
* @param {TodoData} data
* @param {number} delta
* @returns {TodoData}
*/
static seekDays(data, delta) {
const t = new Date(`${data.at}T00:00:00`);
t.setDate(t.getDate() + delta);
return { ...data, at: formatDateId(t) };
}
/**
* @param {TodoData} data
* @returns {TodoData}
*/
static seekToToday(data) {
return { ...data, at: formatDateId(new Date()) };
}
/**
* @param {TodoData} data
* @param {Date} date
* @returns {TodoData}
*/
static seekToDate(data, date) {
return { ...data, at: formatDateId(date) };
}
/**
* @param {TodoData} data
* @param {number} delta
* @returns {TodoData}
*/
static seekCustomTodoLists(data, delta) {
return {
...data,
customAt: Math.max(
0,
Math.min(data.customLists.length - 1, data.customAt + delta),
),
};
}
//
/**
* @template {{index?: number}} T
* @param {T[]} array
* @returns {T[]}
*/
static setIndexes(array) {
return array.map((item, index) =>
item.index === index ? item : { ...item, index },
);
}
}