Commit 42d48a4a authored by ransome1's avatar ransome1
Browse files

Added cluster based rendering of todos to improve performance

parent 7b7cb513
......@@ -95,27 +95,10 @@ jobs:
uses: github/codeql-action/autobuild@v1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
# mirror:
# needs: codeql
# name: Mirror code to opencode.net
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v1
# - name: Mirror + trigger CI
# uses: SvanBoxel/gitlab-mirror-and-ci-action@master
# with:
# args: "https://www.opencode.net/ransome/sleek.git"
# env:
# GITLAB_HOSTNAME: "opencode.net"
# GITLAB_USERNAME: "ransome"
# GITLAB_PASSWORD: ${{ secrets.gitlab_password }}
# GITLAB_PROJECT_ID: "https://www.opencode.net/ransome/sleek/edit"
# GITHUB_TOKEN: ${{ secrets.github_token }}
mirror:
name: Mirror code to opencode.net
runs-on: ubuntu-latest
# needs: codeql
needs: codeql
steps: # <-- must use actions/checkout@v1 before mirroring!
- uses: actions/checkout@v1
- uses: pixta-dev/repository-mirroring-action@v1
......@@ -124,21 +107,3 @@ jobs:
git@www.opencode.net:ransome/sleek.git
ssh_private_key: # <-- use 'secrets' to pass credential information.
${{ secrets.GITLAB_SSH_PRIVATE_KEY }}
# publish-aur-package:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
#
# - name: Publish AUR package
# uses: KSXGitHub/github-actions-deploy-aur@v2
# with:
# pkgname: sleek
# pkgbuild: ./PKGBUILD
# commit_username: ${{ secrets.AUR_USERNAME }}
# commit_email: ${{ secrets.AUR_EMAIL }}
# ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
# commit_message: Update to latest sleek release
{
"name": "sleek",
"productName": "sleek",
"version": "1.0.5-3",
"version": "1.0.5-4",
"description": "Todo app based on todo.txt for Linux, Windows and MacOS, free and open-source",
"synopsis": "Todo app based on todo.txt for Linux, Windows and MacOS, free and open-source",
"category": "ProjectManagement",
......@@ -53,7 +53,10 @@
"mac": {
"target": {
"target": "default",
"arch": ["arm64", "x64"]
"arch": [
"arm64",
"x64"
]
},
"icon": "assets/icons/sleek.icns",
"category": "public.app-category.productivity"
......
......@@ -157,7 +157,7 @@ svg {
code, pre {
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace !important;
background-color: transparent !important;
background-color: #ebebeb;
color: inherit !important;
}
......
This diff is collapsed.
......@@ -155,7 +155,7 @@
</div>
</nav>
</div>
<div class="column content">
<div id="todoTableWrapper" class="column content">
<div id="todoTableSearchContainer" class="control has-icons-left">
<div class="column">
<input id="todoTableSearch" class="input is-medium" type="search" placeholder="Search" tabindex="10">
......
"use strict";
import { userData, appData, handleError, translations, setUserData, _paq } from "../render.js";
import { userData, appData, handleError, translations, setUserData, _paq, startBuilding } from "../render.js";
import { RecExtension } from "./todotxtExtensions.mjs";
import { categories } from "./filters.mjs";
import { generateRecurrence } from "./recurrences.mjs";
import { convertDate, isToday, isTomorrow, isPast } from "./date.mjs";
import { show } from "./form.mjs";
const modalForm = document.getElementById("modalForm");
const todoTableWrapper = document.getElementById("todoTableWrapper");
const todoTableContainer = document.getElementById("todoTableContainer");
// ########################################################################################################################
// CONFIGURE MARKDOWN PARSER
......@@ -44,8 +45,67 @@ const todoTableBodyCellSpacerTemplate = document.createElement("div");
const todoTableBodyCellDueDateTemplate = document.createElement("span");
const todoTableBodyCellRecurrenceTemplate = document.createElement("span");
const item = { previous: "" }
let items;
let visibleRows;
let
items,
clusterCounter,
clusterSize = Math.ceil(window.innerHeight/35), // 35 being the pixel height of one todo in compact mode
clusterThreshold = 0,
stopBuilding = false,
visibleRows = 0;
todoTableWrapper.addEventListener("scroll", function(event) {
if((event.target.scrollHeight - event.target.scrollTop === event.target.clientHeight) && visibleRows<items.filtered.length) {
stopBuilding = false;
startBuilding(null, true);
}
});
function configureTodoTableTemplate(append) {
try {
if(!append) {
todoTableContainer.innerHTML = "";
visibleRows = 0;
clusterThreshold = 0;
stopBuilding = false;
}
todoTableBodyCellMoreTemplate.setAttribute("class", "flex-row todoTableItemMore");
todoTableBodyCellMoreTemplate.setAttribute("role", "cell");
todoTableBodyCellMoreTemplate.innerHTML = `
<div class="dropdown is-right">
<div class="dropdown-trigger">
<a href="#"><i class="fas fa-ellipsis-v"></i></a>
</div>
<div class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a class="dropdown-item">` + translations.useAsTemplate + `</a>
<a href="#" class="dropdown-item">` + translations.edit + `</a>
<a class="dropdown-item">` + translations.delete + `</a>
</div>
</div>
</div>
`;
todoTableBodyRowTemplate.setAttribute("role", "rowgroup");
todoTableBodyRowTemplate.setAttribute("class", "flex-table");
todoTableBodyCellCheckboxTemplate.setAttribute("class", "flex-row checkbox");
todoTableBodyCellCheckboxTemplate.setAttribute("role", "cell");
todoTableBodyCellTextTemplate.setAttribute("class", "flex-row text");
todoTableBodyCellTextTemplate.setAttribute("role", "cell");
todoTableBodyCellTextTemplate.setAttribute("tabindex", 0);
todoTableBodyCellTextTemplate.setAttribute("href", "#");
todoTableBodyCellTextTemplate.setAttribute("title", translations.editTodo);
tableContainerCategoriesTemplate.setAttribute("class", "categories");
todoTableBodyCellPriorityTemplate.setAttribute("role", "cell");
todoTableBodyCellSpacerTemplate.setAttribute("role", "cell");
todoTableBodyCellDueDateTemplate.setAttribute("class", "flex-row itemDueDate");
todoTableBodyCellDueDateTemplate.setAttribute("role", "cell");
todoTableBodyCellRecurrenceTemplate.setAttribute("class", "flex-row recurrence");
todoTableBodyCellRecurrenceTemplate.setAttribute("role", "cell");
return Promise.resolve("Success: Table templates set up");
} catch(error) {
error.functionName = configureTodoTableTemplate.name;
return Promise.reject(error);
}
}
function generateItems(content) {
try {
items = { objects: TodoTxt.parse(content, [ new DueExtension(), new RecExtension(), new HiddenExtension() ]) }
......@@ -99,42 +159,50 @@ function generateGroups(items) {
return 0;
});
}
// sort the items within the groups
items.forEach((group) => {
group[1] = sortTodoData(group[1]);
});
return Promise.resolve(items)
}
function generateTable(groups) {
function generateTable(groups, append) {
// prepare the templates for the table
return configureTodoTableTemplate().then(function(response) {
return configureTodoTableTemplate(append).then(function(response) {
clusterCounter = 0;
console.info(response);
visibleRows = 0;
for (let group in groups) {
if(stopBuilding) {
stopBuilding = false;
break;
}
// create a divider row
let dividerRow;
// completed todos
if(userData.sortCompletedLast && groups[group][0]==="completed") {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table group\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"></div></div>"))
dividerRow = document.createRange().createContextualFragment("<div id=\"" + userData.sortBy + groups[group][0] + "\" class=\"flex-table group\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"></div></div>")
// for priority, context and project
} else if(groups[group][0]!="null" && userData.sortBy!="dueString") {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table " + userData.sortBy + " group\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"><span class=\"button " + groups[group][0] + "\">" + groups[group][0].replace(/,/g, ', ') + "</span></div></div>"))
dividerRow = document.createRange().createContextualFragment("<div id=\"" + userData.sortBy + groups[group][0] + "\" class=\"flex-table " + userData.sortBy + " group\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"><span class=\"button " + groups[group][0] + "\">" + groups[group][0].replace(/,/g, ', ') + "</span></div></div>")
// if sorting is by due date
} else if(userData.sortBy==="dueString" && groups[group][1][0].due) {
if(isToday(groups[group][1][0].due)) {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row isToday\" role=\"cell\"><span class=\"button\">" + translations.today + "</span></div></div>"));
dividerRow= document.createRange().createContextualFragment("<div id=\"" + userData.sortBy + groups[group][0] + "\" class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row isToday\" role=\"cell\"><span class=\"button\">" + translations.today + "</span></div></div>")
} else if(isTomorrow(groups[group][1][0].due)) {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row isTomorrow\" role=\"cell\"><span class=\"button\">" + translations.tomorrow + "</span></div></div>"));
dividerRow = document.createRange().createContextualFragment("<div id=\"" + userData.sortBy + groups[group][0] + "\" class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row isTomorrow\" role=\"cell\"><span class=\"button\">" + translations.tomorrow + "</span></div></div>")
} else if(isPast(groups[group][1][0].due)) {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row isPast\" role=\"cell\"><span class=\"button\">" + groups[group][0] + "</span></div></div>"));
dividerRow = document.createRange().createContextualFragment("<div id=\"" + userData.sortBy + groups[group][0] + "\" class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row isPast\" role=\"cell\"><span class=\"button\">" + groups[group][0] + "</span></div></div>")
} else {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"><span class=\"button\">" + groups[group][0] + "</span></div></div>"))
dividerRow = document.createRange().createContextualFragment("<div id=\"" + userData.sortBy + groups[group][0] + "\" class=\"flex-table group due\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"><span class=\"button\">" + groups[group][0] + "</span></div></div>")
}
// create an empty divider row
} else {
tableContainerContent.appendChild(document.createRange().createContextualFragment("<div class=\"flex-table group\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"></div></div>"))
}
// sort items within this group
const sortedGroup = sortTodoData(groups[group][1]);
//const sortedGroup = groups[group][1];
// build the fragments per group
for (let item in sortedGroup) {
let todo = sortedGroup[item];
} /*else {
dividerRow = document.createRange().createContextualFragment("<div class=\"flex-table group\" role=\"rowgroup\"><div class=\"flex-row\" role=\"cell\"></div></div>")
}*/
// add divider row only if it doesn't exist yet
if(!document.getElementById(userData.sortBy + groups[group][0]) && dividerRow) tableContainerContent.appendChild(dividerRow);
for (let item in groups[group][1]) {
let todo = groups[group][1][item];
// if this todo is not a recurring one the rec value will be set to null
if(!todo.rec) {
todo.rec = null;
......@@ -163,6 +231,14 @@ function generateTable(groups) {
});
}
}
if(clusterCounter<clusterThreshold) {
clusterCounter++;
continue;
} else if((visibleRows===clusterSize+clusterThreshold) || visibleRows===items.filtered.length ) {
clusterThreshold = visibleRows;
stopBuilding = true;
break;
}
tableContainerContent.appendChild(generateTableRow(todo));
}
// TODO add a catch
......@@ -177,6 +253,7 @@ function generateTable(groups) {
}
function generateTableRow(todo) {
try {
clusterCounter++;
visibleRows++;
// create nodes from templates
let todoTableBodyRow = todoTableBodyRowTemplate.cloneNode(true);
......@@ -494,49 +571,6 @@ function checkIsTodoVisible(todo) {
}
return true;
}
function configureTodoTableTemplate() {
try {
todoTableContainer.innerHTML = "";
todoTableBodyCellMoreTemplate.setAttribute("class", "flex-row todoTableItemMore");
todoTableBodyCellMoreTemplate.setAttribute("role", "cell");
todoTableBodyCellMoreTemplate.innerHTML = `
<div class="dropdown is-right">
<div class="dropdown-trigger">
<a href="#"><i class="fas fa-ellipsis-v"></i></a>
</div>
<div class="dropdown-menu" role="menu">
<div class="dropdown-content">
<a class="dropdown-item">` + translations.useAsTemplate + `</a>
<a href="#" class="dropdown-item">` + translations.edit + `</a>
<a class="dropdown-item">` + translations.delete + `</a>
</div>
</div>
</div>
`;
todoTableBodyRowTemplate.setAttribute("role", "rowgroup");
todoTableBodyRowTemplate.setAttribute("class", "flex-table");
todoTableBodyCellCheckboxTemplate.setAttribute("class", "flex-row checkbox");
todoTableBodyCellCheckboxTemplate.setAttribute("role", "cell");
todoTableBodyCellTextTemplate.setAttribute("class", "flex-row text");
todoTableBodyCellTextTemplate.setAttribute("role", "cell");
todoTableBodyCellTextTemplate.setAttribute("tabindex", 0);
todoTableBodyCellTextTemplate.setAttribute("href", "#");
todoTableBodyCellTextTemplate.setAttribute("title", translations.editTodo);
tableContainerCategoriesTemplate.setAttribute("class", "categories");
todoTableBodyCellPriorityTemplate.setAttribute("role", "cell");
todoTableBodyCellSpacerTemplate.setAttribute("role", "cell");
todoTableBodyCellDueDateTemplate.setAttribute("class", "flex-row itemDueDate");
todoTableBodyCellDueDateTemplate.setAttribute("role", "cell");
todoTableBodyCellRecurrenceTemplate.setAttribute("class", "flex-row recurrence");
todoTableBodyCellRecurrenceTemplate.setAttribute("role", "cell");
return Promise.resolve("Success: Table templates set up");
} catch(error) {
error.functionName = configureTodoTableTemplate.name;
return Promise.reject(error);
}
}
function generateNotification(todo, offset) {
try {
let notifications = userData.notifications;
......
......@@ -56,6 +56,7 @@ const todoTableSearch = document.getElementById("todoTableSearch");
const todoTableSearchContainer = document.getElementById("todoTableSearchContainer");
const welcomeToSleek = document.getElementById("welcomeToSleek");
let
append = false,
_paq, a0,
a1,
appData,
......@@ -861,9 +862,9 @@ function showOnboarding(variable) {
function showResultStats() {
try {
// we show some information on filters if any are set
if(todos.visibleRows!=todos.items.objects.length) {
if(todos.items.filtered.length!=todos.items.objects.length) {
resultStats.classList.add("is-active");
resultStats.firstElementChild.innerHTML = translations.visibleTodos + "&nbsp;<strong>" + todos.visibleRows + " </strong>&nbsp;" + translations.of + "&nbsp;<strong>" + todos.items.objects.length + "</strong>";
resultStats.firstElementChild.innerHTML = translations.visibleTodos + "&nbsp;<strong>" + todos.items.filtered.length + " </strong>&nbsp;" + translations.of + "&nbsp;<strong>" + todos.items.objects.length + "</strong>";
return Promise.resolve("Info: Result box is shown");
} else {
resultStats.classList.remove("is-active");
......@@ -939,7 +940,7 @@ function showFiles() {
return Promise.reject(error);
}
}
async function startBuilding(searchString) {
async function startBuilding(searchString, append) {
try {
t0 = performance.now();
......@@ -950,7 +951,7 @@ async function startBuilding(searchString) {
const groups = await todos.generateGroups(todos.items.filtered);
await todos.generateTable(groups);
await todos.generateTable(groups, append);
userData = await getUserData();
......@@ -958,14 +959,14 @@ async function startBuilding(searchString) {
showResultStats();
t1 = performance.now();
console.info("Table build:", t1 - t0, "ms");
console.info("Table build:", performance.now() - t0, "ms");
} catch(error) {
error.functionName = startBuilding.name;
return Promise.reject(error);
}
}
window.onload = async function () {
a0 = performance.now();
......
......@@ -140,7 +140,7 @@ svg {
}
code, pre {
font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, monospace!important;
background-color: transparent!important;
background-color: $light-grey;
color: inherit!important;
}
nav {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment