146 lines
3.9 KiB
Python
146 lines
3.9 KiB
Python
from flask_wtf import FlaskForm
|
|
from flask_wtf.file import FileAllowed, FileField, FileRequired
|
|
from wtforms import (
|
|
BooleanField,
|
|
IntegerField,
|
|
SelectField,
|
|
StringField,
|
|
SubmitField,
|
|
TextAreaField,
|
|
)
|
|
from wtforms.fields import DateTimeLocalField
|
|
from wtforms.validators import (
|
|
DataRequired,
|
|
Length,
|
|
NumberRange,
|
|
Optional,
|
|
ValidationError,
|
|
)
|
|
|
|
from .models import TASK_PRIORITIES, TASK_STATUSES
|
|
|
|
|
|
STATUS_LABELS = {
|
|
"not_started": "Not started",
|
|
"in_progress": "In progress",
|
|
"done": "Done",
|
|
}
|
|
PRIORITY_LABELS = {
|
|
"urgent": "Urgent",
|
|
"important": "Important",
|
|
"normal": "Normal",
|
|
}
|
|
|
|
|
|
def not_blank(_form: FlaskForm, field: StringField) -> None:
|
|
if not field.data or not field.data.strip():
|
|
raise ValidationError("This field cannot be blank.")
|
|
|
|
|
|
class TaskForm(FlaskForm):
|
|
title = StringField(
|
|
"Task title",
|
|
validators=[not_blank, Length(max=200)],
|
|
)
|
|
description = TextAreaField(
|
|
"Notes",
|
|
validators=[Optional(), Length(max=5000)],
|
|
)
|
|
status = SelectField(
|
|
"Status",
|
|
choices=[(value, STATUS_LABELS[value]) for value in TASK_STATUSES],
|
|
default="not_started",
|
|
)
|
|
priority = SelectField(
|
|
"Priority",
|
|
choices=[(value, PRIORITY_LABELS[value]) for value in TASK_PRIORITIES],
|
|
default="normal",
|
|
)
|
|
due_date = DateTimeLocalField(
|
|
"Due date and time",
|
|
format="%Y-%m-%dT%H:%M",
|
|
validators=[Optional()],
|
|
)
|
|
submit = SubmitField("Save task")
|
|
|
|
|
|
class SubtaskForm(FlaskForm):
|
|
title = StringField(
|
|
"Next small step",
|
|
validators=[not_blank, Length(max=100)],
|
|
)
|
|
submit = SubmitField("Add step")
|
|
|
|
|
|
class ActionForm(FlaskForm):
|
|
submit = SubmitField()
|
|
|
|
|
|
def valid_chunk_plan(_form: FlaskForm, field: StringField) -> None:
|
|
if not field.data or not field.data.strip():
|
|
return
|
|
values = [value.strip() for value in field.data.split(",")]
|
|
if len(values) > 12:
|
|
raise ValidationError("Use at most 12 focus blocks.")
|
|
try:
|
|
minutes = [int(value) for value in values]
|
|
except ValueError as exc:
|
|
raise ValidationError(
|
|
"Use comma-separated whole minutes, such as 5, 5, 10."
|
|
) from exc
|
|
if any(value < 1 or value > 120 for value in minutes):
|
|
raise ValidationError("Each focus block must be 1 to 120 minutes.")
|
|
|
|
|
|
class FocusSettingsForm(FlaskForm):
|
|
timer_minutes = IntegerField(
|
|
"Countdown length in minutes",
|
|
validators=[Optional(), NumberRange(min=1, max=180)],
|
|
)
|
|
context = StringField(
|
|
"Helpful context",
|
|
validators=[Optional(), Length(max=100)],
|
|
)
|
|
chunk_plan = StringField(
|
|
"Focus blocks",
|
|
validators=[Optional(), Length(max=120), valid_chunk_plan],
|
|
)
|
|
submit = SubmitField("Save focus setup")
|
|
|
|
def parsed_chunks(self) -> list[int]:
|
|
if not self.chunk_plan.data or not self.chunk_plan.data.strip():
|
|
return []
|
|
return [
|
|
int(value.strip())
|
|
for value in self.chunk_plan.data.split(",")
|
|
]
|
|
|
|
|
|
class MarkdownImportForm(FlaskForm):
|
|
markdown_file = FileField(
|
|
"Markdown or text file",
|
|
validators=[
|
|
FileRequired(),
|
|
FileAllowed(["md", "markdown", "txt"], "Use a .md or .txt file."),
|
|
],
|
|
)
|
|
confirm = BooleanField(
|
|
"I reviewed the import format and want to add these tasks.",
|
|
validators=[DataRequired()],
|
|
)
|
|
submit = SubmitField("Import task list")
|
|
|
|
|
|
class BackupImportForm(FlaskForm):
|
|
backup_file = FileField(
|
|
"Steady JSON backup",
|
|
validators=[FileRequired(), FileAllowed(["json"], "Use a .json file.")],
|
|
)
|
|
replace_existing = BooleanField(
|
|
"Replace all of my existing tasks instead of merging"
|
|
)
|
|
confirm = BooleanField(
|
|
"I understand that restore may add or replace tasks and preferences.",
|
|
validators=[DataRequired()],
|
|
)
|
|
submit = SubmitField("Restore backup")
|