Improve list item CSS
This commit is contained in:
parent
dbdb824269
commit
35b53653a1
7 changed files with 306 additions and 84 deletions
|
|
@ -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'))
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue