Updated design and backend
This commit is contained in:
parent
b5d909f832
commit
8b9ce75959
7 changed files with 158 additions and 67 deletions
|
@ -1,6 +1,7 @@
|
||||||
import argparse
|
import argparse
|
||||||
import configparser
|
import configparser
|
||||||
import logging
|
import logging
|
||||||
|
import locale
|
||||||
## dev imports
|
## dev imports
|
||||||
from pprint import pp
|
from pprint import pp
|
||||||
import sys
|
import sys
|
||||||
|
@ -23,6 +24,8 @@ args = parser.parse_args()
|
||||||
config = configparser.ConfigParser()
|
config = configparser.ConfigParser()
|
||||||
config.read(args.config)
|
config.read(args.config)
|
||||||
|
|
||||||
|
locale.setlocale(locale.LC_ALL, config.get('locale', 'locale', fallback=None))
|
||||||
|
|
||||||
logging.basicConfig(level=config.get('logging', 'level', fallback='WARN'))
|
logging.basicConfig(level=config.get('logging', 'level', fallback='WARN'))
|
||||||
log = logging.getLogger('saturn')
|
log = logging.getLogger('saturn')
|
||||||
|
|
||||||
|
@ -30,7 +33,9 @@ log.info('Starting saturn')
|
||||||
|
|
||||||
|
|
||||||
davclient = get_davclient(config['caldav'])
|
davclient = get_davclient(config['caldav'])
|
||||||
app = create_app(davclient)
|
app = create_app(
|
||||||
|
davclient,
|
||||||
|
)
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(
|
app.run(
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
import re
|
||||||
|
from datetime import date, datetime, time
|
||||||
|
|
||||||
import caldav
|
import caldav
|
||||||
|
|
||||||
from datetime import date, datetime
|
from .event import Event
|
||||||
|
|
||||||
|
|
||||||
def get_davclient(config):
|
def get_davclient(config):
|
||||||
|
@ -10,6 +13,15 @@ def get_davclient(config):
|
||||||
password=config.get('password', fallback=None),
|
password=config.get('password', fallback=None),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_comparable_dt(event):
|
||||||
|
if type(event.dtstart.value) == date:
|
||||||
|
return datetime.combine(
|
||||||
|
event.dtstart.value,
|
||||||
|
time.min,
|
||||||
|
).timestamp()
|
||||||
|
else:
|
||||||
|
return event.dtstart.value.timestamp()
|
||||||
|
|
||||||
def get_events(davclient):
|
def get_events(davclient):
|
||||||
pri = davclient.principal()
|
pri = davclient.principal()
|
||||||
|
|
||||||
|
@ -18,12 +30,7 @@ def get_events(davclient):
|
||||||
|
|
||||||
for cal in pri.calendars():
|
for cal in pri.calendars():
|
||||||
events += [event.instance.vevent
|
events += [event.instance.vevent
|
||||||
for event in cal.date_search(start=date.today())
|
for event in cal.date_search(start=date.today())]
|
||||||
if type(event.instance.vevent.dtstart.value) == datetime]
|
events.sort(key=get_comparable_dt)
|
||||||
events_day += [event.instance.vevent
|
|
||||||
for event in cal.date_search(start=date.today())
|
|
||||||
if type(event.instance.vevent.dtstart.value) != datetime]
|
|
||||||
events.sort(key=lambda event:event.dtstart.value)
|
|
||||||
events_day.sort(key=lambda event:event.dtstart.value)
|
|
||||||
|
|
||||||
return events, events_day
|
return [Event(event) for event in events]
|
||||||
|
|
47
saturn/calendar/event.py
Normal file
47
saturn/calendar/event.py
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class Event:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
vobject,
|
||||||
|
):
|
||||||
|
for attr in [
|
||||||
|
'dtstart',
|
||||||
|
'dtend',
|
||||||
|
'summary',
|
||||||
|
'description',
|
||||||
|
'location',
|
||||||
|
'rrule',
|
||||||
|
]:
|
||||||
|
setattr(self, f'_{attr}',
|
||||||
|
getattr(getattr(vobject, attr, None), 'value', None))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dtstart(self):
|
||||||
|
return self._dtstart
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dtend(self):
|
||||||
|
return self._dtend
|
||||||
|
|
||||||
|
@property
|
||||||
|
def summary(self):
|
||||||
|
return self._summary
|
||||||
|
|
||||||
|
@property
|
||||||
|
def description(self):
|
||||||
|
return self._description
|
||||||
|
|
||||||
|
@property
|
||||||
|
def location(self):
|
||||||
|
return self._location
|
||||||
|
|
||||||
|
@property
|
||||||
|
def freq(self):
|
||||||
|
if self._rrule is None: return None
|
||||||
|
return re.search(
|
||||||
|
r'FREQ=([A-Z]+?);',
|
||||||
|
self._rrule,
|
||||||
|
).group(1)
|
||||||
|
|
|
@ -4,17 +4,18 @@ from datetime import date, datetime
|
||||||
from ..calendar import get_events
|
from ..calendar import get_events
|
||||||
|
|
||||||
|
|
||||||
def create_app(davclient):
|
def create_app(
|
||||||
|
davclient,
|
||||||
|
):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def home():
|
def home():
|
||||||
events, events_day = get_events(davclient)
|
events = get_events(davclient)
|
||||||
|
|
||||||
return flask.render_template(
|
return flask.render_template(
|
||||||
'index.html',
|
'index.html',
|
||||||
events = events,
|
events = events,
|
||||||
events_day = events_day,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
body {
|
body {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns:
|
grid-template-columns:
|
||||||
1fr
|
|
||||||
1fr;
|
1fr;
|
||||||
grid-template-rows:
|
grid-template-rows:
|
||||||
auto
|
auto
|
||||||
|
@ -22,13 +21,10 @@ body > * {
|
||||||
margin: 1rem;
|
margin: 1rem;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns:
|
grid-template-columns:
|
||||||
1fr
|
6rem
|
||||||
1fr;
|
1fr;
|
||||||
grid-template-rows:
|
row-gap: .5rem;
|
||||||
auto
|
column-gap: 1rem;
|
||||||
auto
|
|
||||||
auto;
|
|
||||||
gap: .5rem;
|
|
||||||
background: var(--ui-01);
|
background: var(--ui-01);
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
@ -39,23 +35,33 @@ body > * {
|
||||||
grid-column: 1 / span 2;
|
grid-column: 1 / span 2;
|
||||||
}
|
}
|
||||||
.event > .date {
|
.event > .date {
|
||||||
grid-row: 2;
|
grid-column: 1;
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: auto;
|
||||||
|
gap: 1rem;
|
||||||
|
text-align: center;
|
||||||
}
|
}
|
||||||
.event > .start-date {
|
.event > .date > div {
|
||||||
grid-column: 1;
|
display: grid;
|
||||||
text-align: right;
|
grid-template-columns: auto;
|
||||||
}
|
}
|
||||||
.event > .end-date {
|
.date .day {
|
||||||
|
font-size: 3em;
|
||||||
|
}
|
||||||
|
.date .time {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
.event .end-date,
|
||||||
|
.event .other {
|
||||||
|
color: var(--text-02);
|
||||||
|
}
|
||||||
|
.event > .info {
|
||||||
grid-column: 2;
|
grid-column: 2;
|
||||||
}
|
display: grid;
|
||||||
.event > .description {
|
grid-template-columns: 1fr;
|
||||||
grid-column: 1;
|
grid-template-rows:
|
||||||
}
|
1fr
|
||||||
.event > .other {
|
auto;
|
||||||
grid-column: 2;
|
gap: 1rem;
|
||||||
}
|
|
||||||
.event > .description,
|
|
||||||
.event > .other > * {
|
|
||||||
padding-top: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
--ui-background: white;
|
--ui-background: white;
|
||||||
--ui-01: var(--gray-10);
|
--ui-01: var(--gray-10);
|
||||||
--text-01: var(--gray-100);
|
--text-01: var(--gray-100);
|
||||||
|
--text-02: var(--gray-70);
|
||||||
|
|
||||||
--gray-10: #f4f4f4;
|
--gray-10: #f4f4f4;
|
||||||
|
--gray-70: #525252;
|
||||||
--gray-100: #161616;
|
--gray-100: #161616;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
{% block style %}
|
{% block style %}
|
||||||
{{ super() }}
|
{{ super() }}
|
||||||
<link rel="stylesheet" href="/static/css/index.css" />
|
<link rel="stylesheet" href="/static/css/index.css" />
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/remixicon/fonts/remixicon.css" rel="stylesheet">
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block body %}
|
{% block body %}
|
||||||
|
@ -11,40 +12,62 @@
|
||||||
<div id="events">
|
<div id="events">
|
||||||
{% for event in events %}
|
{% for event in events %}
|
||||||
<div class="event">
|
<div class="event">
|
||||||
<div class="summary">{{ event.summary.value }}</div>
|
<div class="summary">{{ event.summary }}</div>
|
||||||
<div class="date start-date">{{ event.dtstart.value.isoformat() }}</div>
|
<div class="date">
|
||||||
<div class="date end-date">{{ event.dtend.value.isoformat() }}</div>
|
<div class="start-date">
|
||||||
{% if event.description %}
|
<span class="weekday">
|
||||||
<div class="description">{{ event.description.value }}</div>
|
{{ event.dtstart.strftime('%A').capitalize() }}
|
||||||
|
</span>
|
||||||
|
<span class="day">
|
||||||
|
{{ event.dtstart.strftime('%d') }}
|
||||||
|
</span>
|
||||||
|
<span class="month">
|
||||||
|
{{ event.dtstart.strftime('%B').capitalize() }}
|
||||||
|
</span>
|
||||||
|
<span class="time">
|
||||||
|
{{ event.dtstart.strftime('%H:%M') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="end-date">
|
||||||
|
{% if not event.dtend.date or event.dtend.date() != event.dtstart.date() %}
|
||||||
|
<span class="weekday">
|
||||||
|
{{ event.dtend.strftime('%A').capitalize() }}
|
||||||
|
</span>
|
||||||
|
<span class="day">
|
||||||
|
{{ event.dtend.strftime('%d') }}
|
||||||
|
</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if event.dtend.month != event.dtstart.month %}
|
||||||
|
<span class="month">
|
||||||
|
{{ event.dtend.strftime('%B').capitalize() }}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
<span class="time">
|
||||||
|
{{ event.dtend.strftime('%H:%M') }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<div class="description">
|
||||||
|
{% if event.description %}
|
||||||
|
{{ event.description }}
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
<div class="other">
|
<div class="other">
|
||||||
{% if event.location %}
|
{% if event.location %}
|
||||||
<div>{{ event.location.value }}</div>
|
<div>
|
||||||
|
<i class="ri-map-pin-line ri-fw ri-sm"></i>
|
||||||
|
{{ event.location }}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if event.rrule %}
|
{% if event.freq %}
|
||||||
<div>{{ event.rrule.value }}</div>
|
<div>
|
||||||
|
<i class="ri-repeat-line ri-fw ri-sm"></i>
|
||||||
|
{{ event.freq}}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
<div id="events_day">
|
|
||||||
{% for event in events_day %}
|
|
||||||
<div class="event">
|
|
||||||
<div class="summary">{{ event.summary.value }}</div>
|
|
||||||
<div class="date start-date">{{ event.dtstart.value }}</div>
|
|
||||||
<div class="date end-date">{{ event.dtend.value }}</div>
|
|
||||||
{% if event.description %}
|
|
||||||
<div class="description">{{ event.description.value }}</div>
|
|
||||||
{% endif %}
|
|
||||||
<div class="other">
|
|
||||||
{% if event.location %}
|
|
||||||
<div>{{ event.location.value }}</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if event.rrule %}
|
|
||||||
<div>{{ event.rrule.value }}</div>
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
|
Reference in a new issue