blob: 6607f4044d4d2a7371758d8a24084f39e25c7c0d [file] [log] [blame]
/*
* Copyright (C) 2022 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
"use strict";
function createLists(store, count) {
for (let i = 0; i < count; i++) {
store.lists.push(new ObservableReminderList(store, listName(i)));
}
}
function createReminders(store, count) {
store.lists.forEach((list, listIndex) => {
for (let i = 0; i < count; i++) {
const text = nouns[((listIndex * 10) + i) % nouns.length];
list.reminders.push(new ObservableReminder(list, text));
}
});
}
function randomItem(array) {
return array[array.length * Math.random() | 0];
}
function listName(index) {
const date = new Date();
date.setDate(date.getDate() + index);
return date.toDateString();
}
const nouns = ["magazine", "passion", "statement", "extent", "judgment", "setting", "piano", "control", "employer", "speech", "presence", "indication", "philosophy", "worker", "supermarket", "atmosphere", "actor", "chest", "garbage", "feedback", "pizza", "speaker", "selection", "effort", "math", "gate", "profession", "recognition", "software", "coffee", "reading", "hotel", "concept", "poem", "population", "property", "office", "phone", "suggestion", "theory", "drawer", "death", "cigarette", "alcohol", "marketing", "session", "cheek", "establishment", "family", "revenue", "location", "mode", "awareness", "preference", "consequence", "cookie", "loss", "poetry", "cabinet", "difference", "story", "penalty", "king", "opinion", "climate", "imagination", "criticism", "meal", "desk", "dealer", "introduction", "physics", "customer", "child", "problem", "medicine", "church", "unit", "tongue", "communication", "storage", "stranger", "presentation", "discussion", "excitement", "role", "ladder", "estate", "town", "proposal", "employee", "administration", "variety", "television", "president", "inspector", "negotiation", "height", "comparison", "decision"];
const adjectives = ["fast", "cheerful", "hungry", "fantastic", "squealing", "critical", "yielding", "abashed", "shocking", "quickest"];
function addRandomTags(store) {
forEachReminder(store, reminder => {
if (Math.random() < 0.5) {
reminder.tags.add(randomItem(adjectives));
}
if (Math.random() < 0.5) {
reminder.tags.add(randomItem(adjectives));
}
});
}
function completeSomeReminders(store) {
forEachReminder(store, reminder => {
reminder.isCompleted = Math.random() < 0.5;
});
}
function sortRemindersByCompleted(store) {
store.lists.forEach(list => {
list.reminders.sort((a, b) => b.isCompleted - a.isCompleted);
});
}
function pushBackRemindersWithTag(store, tag) {
for (let i = store.lists.length - 2; i >= 0; i--) {
const list = store.lists[i];
const nextList = store.lists[i + 1];
for (let j = list.reminders.length - 1; j >= 0; j--) {
const reminder = list.reminders[j];
if (reminder.tags.has(tag)) {
reminder.moveToAnotherList(nextList);
}
}
}
}
function removeRemindersWithTag(store, tag) {
store.lists.forEach(list => {
list.reminders = list.reminders.filter(reminder => !reminder.tags.has(tag));
});
}
function removeCompletedReminders(store) {
store.lists.forEach(list => {
list.reminders = list.reminders.filter(reminder => !reminder.isCompleted);
});
}
function removeFirstAndLastRemindersFromEachList(store) {
store.lists.forEach(list => {
list.reminders.shift();
list.reminders.pop();
});
}
function removeEmptyLists(store) {
for (let i = store.lists.length - 1; i >= 0; i--) {
const list = store.lists[i];
if (list.isEmpty) {
store.lists.splice(i, 1);
}
}
}
function forEachReminder(store, callback) {
store.lists.forEach(list => {
list.reminders.forEach(callback);
});
}
function renderTags(entries) {
return entries.map(([tag, count]) => `#${tag} (${count})`).join(", ") || "<none>";
}
class ReminderStore {
constructor() {
this.lists = [];
this.allTimeCompletedTags = new Map;
}
get allTags() {
const map = new Map;
forEachReminder(this, reminder => {
reminder.tags.forEach(tag => {
const oldCount = map.get(tag) ?? 0;
map.set(tag, oldCount + 1);
});
});
return map;
}
get completedRemidersCount() {
return this.lists.reduce((count, list) => count + list.completedRemidersCount, 0);
}
get remindersCount() {
return this.lists.reduce((count, list) => count + list.reminders.length, 0);
}
get popularTags() {
return this.popularTagsWithCount.map(e => e[0]);
}
get popularTagsWithCount() {
return [...this.allTags].sort((a, b) => b[1] - a[1]).slice(0, 5);
}
render() {
return [
this.lists.map(list => list.render()).join("\n") || "<empty>",
"-".repeat(31),
`completed: ${this.completedRemidersCount} / ${this.remindersCount}`,
"-".repeat(31),
`current popular tags: ${renderTags(this.popularTagsWithCount)}`,
`all-time completed tags: ${renderTags([...this.allTimeCompletedTags])}`,
].join("\n");
}
}
class ReminderList {
constructor(store, name) {
this._store = store;
this.name = name;
this.reminders = [];
}
get store() {
return this._store;
}
get indexInStore() {
return this._store.lists.indexOf(this);
}
get isEmpty() {
return !this.reminders.length;
}
get completedRemidersCount() {
return this.reminders.filter(reminder => reminder.isCompleted).length;
}
render() {
const content = this.isEmpty ? "<empty>" : this.reminders.map(reminder => reminder.render()).join("\n");
return `--- ${this.name} (${this.completedRemidersCount} / ${this.reminders.length}) ---\n${content}`;
}
}
class Reminder {
constructor(list, text) {
this._list = list;
this.text = text;
this.tags = new Set();
this.isCompleted = false;
}
get list() {
return this._list;
}
get indexInList() {
return this._list.reminders.indexOf(this);
}
_isCompletedChanged(isCompleted) {
if (isCompleted) {
const tagsMap = this._list.store.allTimeCompletedTags;
this.tags.forEach(tag => {
const oldCount = tagsMap.get(tag) ?? 0;
tagsMap.set(tag, oldCount + 1);
});
}
}
moveToAnotherList(newList) {
this._list.reminders.splice(this.indexInList, 1);
this._list = newList;
newList.reminders.unshift(this);
}
render() {
const tags = this.tags.size ? ` (${Array.from(this.tags, tag => `#${tag}`).join(" ")})` : "";
return `[${this.isCompleted ? "x" : " "}] ${this.text}${tags}`;
}
}
async function test(store, render) {
await render(() => createLists(store, 10));
await render(() => createReminders(store, 10));
await render(() => addRandomTags(store));
const { popularTags } = store;
await render(() => pushBackRemindersWithTag(store, popularTags[0]));
await render(() => removeRemindersWithTag(store, popularTags[2]));
await render(() => removeFirstAndLastRemindersFromEachList(store));
await render(() => completeSomeReminders(store));
await render(() => sortRemindersByCompleted(store));
await render(() => removeCompletedReminders(store));
await render(() => removeEmptyLists(store));
}