Improve list item CSS

This commit is contained in:
digimint 2025-11-22 04:36:54 -06:00
parent dbdb824269
commit 35b53653a1
Signed by: digimint
GPG key ID: 8DF1C6FD85ABF748
7 changed files with 306 additions and 84 deletions

View file

@ -14,6 +14,6 @@ class Task(db.Model):
due: Mapped[datetime] = mapped_column(DateTime(timezone=False))
created: Mapped[datetime] = mapped_column(DateTime(timezone=False), default=now)
complete: Mapped[bool] = mapped_column(Boolean, default=False)
completed: Mapped[int] = mapped_column(DateTime(timezone=False), nullable=True)
completed: Mapped[datetime|None] = mapped_column(DateTime(timezone=False), nullable=True)
namespace: Mapped[int] = mapped_column(Integer, ForeignKey('namespace.id', ondelete='CASCADE'))
owner: Mapped[int] = mapped_column(Integer, ForeignKey('user.id', ondelete='CASCADE'))

View file

@ -1,5 +1,5 @@
from dataclasses import dataclass
from datetime import datetime, timezone
from datetime import datetime, timedelta, timezone
from typing import Self
import humanize
@ -34,6 +34,8 @@ class TaskForUser:
due_rel: str
created: datetime
complete: bool
completed: datetime|None
overdue: bool
namespace_id: int
can_edit: bool
@ -41,6 +43,33 @@ class TaskForUser:
can_complete: bool
can_uncomplete: bool
@property
def delay_style(self) -> str:
''' Returns an appropriate 'animation-delay' property for
recently-completed tasks; otherwise an empty string.
Intended for use in jinja templating
'''
if self.just_complete and self.completed is not None:
delta = now() - self.completed
return f'animation-delay: {-1*delta.seconds}s;'
else:
return ''
@property
def just_complete(self) -> bool:
''' Returns whether the task was recently finished (within 5 minutes).
Used for jinja templates.
'''
return (
self.complete
and (self.completed is not None)
and (
(now() - self.completed)
< timedelta(minutes=5)
)
)
@classmethod
def from_task(
cls,
@ -71,6 +100,12 @@ class TaskForUser:
_due_str(tsk.due),
tsk.created,
tsk.complete,
(
tsk.completed.replace(tzinfo=timezone.utc)
if tsk.completed is not None
else None
),
tsk.due.replace(tzinfo=timezone.utc) < now(),
tsk.namespace,
NPT.EDIT_ALL_TASKS in perms,
NPT.DELETE_ALL_TASKS in perms,

View file

@ -1,31 +1,160 @@
.list{
width: 100%;
@keyframes task-reward-fade-even {
0% {
background-color : var(--block-grey-bg);
color : var(--block-grey-text);
fill : var(--block-grey-text);
stroke : var(--block-grey-text);
}
0.5% {
background-color : var(--block-good-bg);
color : var(--block-good-text);
fill : var(--block-good-text);
stroke : var(--block-good-text);
}
80% {
background-color : var(--block-good-bg);
color : var(--block-good-text);
fill : var(--block-good-text);
stroke : var(--block-good-text);
}
100% {
background-color : var(--block-grey-bg);
color : var(--block-grey-text);
fill : var(--block-grey-text);
stroke : var(--block-grey-text);
}
}
@keyframes task-reward-fade-odd {
0% {
background-color : var(--block-grey-2-bg);
color : var(--block-grey-2-text);
fill : var(--block-grey-2-text);
stroke : var(--block-grey-2-text);
}
0.5% {
background-color : var(--block-good-2-bg);
color : var(--block-good-2-text);
fill : var(--block-good-2-text);
stroke : var(--block-good-2-text);
}
80% {
background-color : var(--block-good-2-bg);
color : var(--block-good-2-text);
fill : var(--block-good-2-text);
stroke : var(--block-good-2-text);
}
100% {
background-color : var(--block-grey-2-bg);
color : var(--block-grey-2-text);
fill : var(--block-grey-2-text);
stroke : var(--block-grey-2-text);
}
}
.list{
width: 100%;
--table-row-even-bg : var(--block-2-bg);
--table-row-even-text : var(--block-2-text);
--table-row-even-border-width: var(--block-2-border-width);
--table-row-even-border-color: var(--block-2-border-color);
--table-row-odd-bg : var(--block-3-bg);
--table-row-odd-text : var(--block-3-text);
--table-row-odd-border-width: var(--block-3-border-width);
--table-row-odd-border-color: var(--block-3-border-color);
tr {
--table-row-bg : var(--table-row-even-bg);
--table-row-text : var(--table-row-even-text);
--table-row-border-width : var(--table-row-even-border-width);
--table-row-border-color : var(--table-row-even-border-color);
background-color: var(--table-row-bg);
color: var(--table-row-text);
border: var(--table-row-border-width) solid var(--table-row-border-color);
transition: background-color 2s;
transition: color 2s;
transition: border 2s;
transition: all 2s;
.icon svg {
fill: var(--table-row-text);
stroke: var(--table-row-text);
transition: fill 2s;
transition: stroke 2s;
}
&:nth-child(odd) {
--table-row-bg : var(--table-row-odd-bg);
--table-row-text : var(--table-row-odd-text);
--table-row-border-width : var(--table-row-odd-border-width);
--table-row-border-color : var(--table-row-odd-border-color);
}
&.overdue {
--table-row-even-bg : var(--block-bad-bg);
--table-row-even-text : var(--block-bad-text);
--table-row-even-border-width: var(--block-bad-border-width);
--table-row-even-border-color: var(--block-bad-border-color);
--table-row-odd-bg : var(--block-bad-2-bg);
--table-row-odd-text : var(--block-bad-2-text);
--table-row-odd-border-width: var(--block-bad-2-border-width);
--table-row-odd-border-color: var(--block-bad-2-border-color);
}
&.complete {
--table-row-even-bg : var(--block-grey-bg);
--table-row-even-text : var(--block-grey-text);
--table-row-even-border-width : var(--block-grey-border-width);
--table-row-even-border-color : var(--block-grey-border-color);
--table-row-odd-bg : var(--block-grey-2-bg);
--table-row-odd-text : var(--block-grey-2-text);
--table-row-odd-border-width : var(--block-grey-2-border-width);
--table-row-odd-border-color : var(--block-grey-2-border-color);
&.just-complete{
animation-name: task-reward-fade-even;
animation-duration: 300s;
.checkbox .icon svg {
animation-name: task-reward-fade-even;
animation-duration: 300s;
}
&:nth-child(odd) {
animation-name: task-reward-fade-odd;
.checkbox .icon svg {
animation-name: task-reward-fade-odd;
}
}
}
}
}
table.list{
border-collapse: collapse;
.task-table-row {
&.complete {
font-style: italic;
text-decoration: line-through;
}
}
.list tr {
background-color: var(--table-row-bg-1);
color: var(--on-table-row);
}
.list tr:nth-child(odd) {
background-color: var(--table-row-bg-2);
color: var(--on-table-row);
}
.list .task-header-row {
.task-header-row {
border-bottom: 8px solid var(--bg);
}
.list td, .list th{
td, th{
border-right: 4px solid var(--bg);
border-left: 4px solid var(--bg);
border-top: var(--table-row-border-width) solid var(--table-row-border-color);
border-bottom: var(--table-row-border-width) solid var(--table-row-border-color);
}
.list td {
td {
padding-left: 1rem;
&.nopad {
@ -37,77 +166,82 @@
}
}
.list p {
p {
margin: 0;
}
.list .detail-view {
.detail-view {
display: none;
border-bottom: 4px solid var(--bg);
border-top: 1px solid var(--bg);
&.shown {
display: table-row;
}
}
.list .detail-view-elem {
.detail-view-elem {
padding: 1rem;
.small-details {
font-size: small;
font-style: italic;
}
hr {
color: var(--table-row-text);
}
.detail-view-header {
display: flex;
h1 {
margin: 0 1rem;
font-size: larger;
}
&::before {
content: "";
flex: 1 1;
background: repeating-linear-gradient(
60deg,
var(--table-row-text) 0,
var(--table-row-text) 0.25rem,
transparent 0.35rem,
transparent 1.0rem,
var(--table-row-text) 1.1rem
);
width: 100%;
max-width: 2rem;
}
&::after{
content: "";
flex: 1 1;
background: repeating-linear-gradient(
60deg,
var(--table-row-text) 0,
var(--table-row-text) 0.25rem,
transparent 0.35rem,
transparent 1.0rem,
var(--table-row-text) 1.1rem
);
width: 100%;
}
}
.main-description {
margin-top: 0.5rem;
}
}
.list .detail-view.shown {
display: table-row;
}
}
.list .detail-view-elem .small-details {
font-size: small;
font-style: italic;
}
.list .detail-view-elem hr {
color: var(--on-table-row);
}
.list .detail-view-elem .detail-view-header h1 {
margin: 0 1rem;
font-size: larger;
}
.list .detail-view-elem .detail-view-header {
display: flex;
}
.list .detail-view-elem .detail-view-header::before{
content: "";
flex: 1 1;
background: repeating-linear-gradient(
60deg,
var(--on-table-row) 0,
var(--on-table-row) 0.25rem,
transparent 0.35rem,
transparent 1.0rem,
var(--on-table-row) 1.1rem
);
width: 100%;
max-width: 2rem;
}
.list .detail-view-elem .detail-view-header::after{
content: "";
flex: 1 1;
background: repeating-linear-gradient(
60deg,
var(--on-table-row) 0,
var(--on-table-row) 0.25rem,
transparent 0.35rem,
transparent 1.0rem,
var(--on-table-row) 1.1rem
);
width: 100%;
}
.list .detail-view-elem .main-description {
margin-top: 0.5rem;
}
table.list{
border-collapse: collapse;
}

View file

@ -15,6 +15,52 @@
--body-font: sans-serif;
--footer-font: sans-serif;
--block-2-bg: #331826;
--block-2-border-color: var(--block-2-bg);
--block-2-text: #ffc4d1;
--block-2-border-width: 0;
--block-3-bg: #462837;
--block-3-border-color: var(--block-3-bg);
--block-3-text: #ffc4d1;
--block-3-border-width: 0;
--block-neutral-border-width : 0;
--block-neutral-2-border-width : 0;
--block-neutral-bg : #182633;
--block-neutral-border-color : var(--block-neutral-bg);
--block-neutral-text : #c4ecff;
--block-neutral-2-bg : #283e46;
--block-neutral-2-border-color : var(--block-neutral-2-bg);
--block-neutral-2-text : #c4e0ff;
--block-grey-border-width : 0;
--block-grey-2-border-width : 0;
--block-grey-bg : #262726;
--block-grey-border-color : var(--block-grey-bg);
--block-grey-text : #b6b6b6;
--block-grey-2-bg : #484948;
--block-grey-2-border-color : var(--block-grey-2-bg);
--block-grey-2-text : #a8a8a8;
--block-good-border-width : 0;
--block-good-2-border-width : 0;
--block-good-bg : #003f1c;
--block-good-border-color : var(--block-good-bg);
--block-good-text : #c4ffce;
--block-good-2-bg : #005333;
--block-good-2-border-color : var(--block-good-2-bg);
--block-good-2-text : #c4ffce;
--block-bad-border-width : 1px;
--block-bad-2-border-width : 1px;
--block-bad-bg : #360a0a;
--block-bad-border-color : #fd1c42;
--block-bad-text : #ff798f;
--block-bad-2-bg : #4b0d0d;
--block-bad-2-border-color : #fd1c42;
--block-bad-2-text : #ffa7b5;
--table-row-bg-1: #331826;
--table-row-bg-2: #462837;
--on-table-row: #ffc4d1;

View file

@ -153,6 +153,10 @@ function update_reltimes(){
).forEach((el) => {
const time = new Date(Number(el.dataset.timestamp)*1000)
const delta = time - Date.now()
const tr = el.parentElement.parentElement.parentElement
if(delta < 0 && !tr.classList.contains('overdue')){
tr.classList.add('overdue')
}
el.textContent = sophontize(delta, time)
})
}

View file

@ -11,7 +11,7 @@
{% endmacro %}
{% macro inline_task(task, list_id=0) %}
<tr class="task-table-row" id="{{ tlist_cid('row', task.id, list_id) }}">
<tr class="task-table-row{{' overdue' if task.overdue else ''}}{{' complete' if task.complete else ''}}{{' just-complete' if task.just_complete else ''}}" id="{{ tlist_cid('row', task.id, list_id) }}" style="{{task.delay_style}}">
<td class="checkbox" id="{{ tlist_cid('check', task.id, list_id) }}">
{% if task.complete %}
{% if task.can_uncomplete %}
@ -46,7 +46,7 @@
</td>
</tr>
<tr></tr> <!-- placeholder for CSS styling -->
<tr class="detail-view tl-{{ list_id }}" id="{{ tlist_cid('task-detail', task.id, list_id) }}">
<tr class="detail-view tl-{{ list_id }}{{' overdue' if task.overdue else ''}}{{' complete' if task.complete else ''}}{{' just-complete' if task.just_complete else ''}}" id="{{ tlist_cid('task-detail', task.id, list_id) }}" style="{{task.delay_style}}">
<td class="detail-view-elem" colspan=3>
<div class="detail-view-header">
<h1>Task Details: {{ task.name }}</h1>

View file

@ -15,6 +15,7 @@ from taskflower.form.task import task_edit_form_for_task, task_form_for_user
from taskflower.sanitize.task import TaskForUser
from taskflower.types.either import Either, Left, Right, reduce_either
from taskflower.types.option import Option
from taskflower.util.time import now
from taskflower.web.errors import (
ResponseErrorNotFound,
response_from_exception
@ -111,6 +112,7 @@ def complete(id: int):
def _do_complete(tsk: Task) -> Either[Exception, Task]:
try:
tsk.complete = True
tsk.completed = now()
db.session.commit()
return Right(tsk)
except Exception as e:
@ -166,6 +168,7 @@ def uncomplete(id: int):
def _do_uncomplete(tsk: Task) -> Either[Exception, Task]:
try:
tsk.complete = False
tsk.completed = None
db.session.commit()
return Right(tsk)
except Exception as e: