Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add timezone support #6

Merged
merged 4 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ env = GRIST_ROOT_URL=https://grist.tiker.net
env = GRIST_API_KEY_FILE=/home/grist-av/.grist-api-key
env = GRIST_DOC_ID=rLJPGJ9RLJ4TRVx4AxT2tW
env = SECRET_KEY=CHANGE_ME
env = CAL_TIMEZONES=America/Chicago,local,UTC

# Optional. Only effective if both are provided.
env = NOTIFY_FROM=andreask@illinois.edu
Expand Down
21 changes: 13 additions & 8 deletions availability/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@ def render_calendar(
if slots is None:
slots = []

timezones = [tzname.strip()
for tzname in os.environ.get("CAL_TIMEZONES", "local,UTC").split(",")]

initial_date = None
last_date = None
events = []
Expand Down Expand Up @@ -322,8 +325,8 @@ def render_calendar(
if has_spans:
for span in spans:
events.append({
"start": span.start.isoformat(),
"end": span.end.isoformat(),
"start": span.start.timestamp() * 1000,
"end": span.end.timestamp() * 1000,
"editable": True,
"extendedProps": {
"type": "span",
Expand All @@ -350,7 +353,8 @@ def render_calendar(
js_url=url_for("static", filename="availability.js"),
has_slots=has_slots,
allow_maybe=av_request.allow_maybe,
has_spans=has_spans)
has_spans=has_spans,
timezones=timezones)


def send_notify(av_request: AvailabilityRequest, text_response: str,
Expand Down Expand Up @@ -522,11 +526,12 @@ def availabilit(key: str):

# }}}

return respond_with_message(
"Thank you for submitting your availability. "
"If you need to edit your availability, you may do so by revisiting "
"the same link."
)
flash(
"Thank you for submitting your availability. "
"If you need to edit your availability, you may do so by revisiting "
"the same link.")
return render_calendar(av_request, req_timespans, cal_spans, cal_slots)

else:
raise ValueError(f"unexpected request method: '{request.method}'")

Expand Down
39 changes: 33 additions & 6 deletions availability/static/availability.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,22 +53,29 @@ function calSelect(info) {
styleEvent(ev);
}

function fullcalDateToISO(date, calendar) {
// eslint-disable-next-line no-undef
return FullCalendar.Luxon3.toLuxonDateTime(date, calendar).toISO();
}

function onSubmit() {
const slots = [];
const spans = [];

document.calendarInstance.getEvents().forEach((ev) => {
const cal = document.calendarInstance;

cal.getEvents().forEach((ev) => {
if (ev.extendedProps.type === 'slot') {
slots.push({
rspan_id: ev.extendedProps.rspan_id,
start: ev.start,
end: ev.end,
start: fullcalDateToISO(ev.start, cal),
end: fullcalDateToISO(ev.end, cal),
available: ev.extendedProps.available,
});
} else if (ev.extendedProps.type === 'span') {
spans.push({
start: ev.start,
end: ev.end,
start: fullcalDateToISO(ev.start, cal),
end: fullcalDateToISO(ev.end, cal),
maybe: ev.extendedProps.maybe,
});
}
Expand All @@ -84,14 +91,15 @@ function onSubmit() {
}

// eslint-disable-next-line no-unused-vars
function initialize(initialDate, nDays, events, hasSpans) {
function initialize(initialDate, nDays, events, hasSpans, timezones) {
document.addEventListener(
'DOMContentLoaded',
() => {
const calendarEl = document.getElementById('calendar');
// eslint-disable-next-line no-undef
const calendar = new FullCalendar.Calendar(calendarEl, {
// plugins: [timeGridPlugin],
timeZone: timezones[0],
initialView: 'timeGridNDay',
headerToolbar: {
start: false,
Expand Down Expand Up @@ -122,6 +130,25 @@ function initialize(initialDate, nDays, events, hasSpans) {

document.calendarInstance = calendar;
document.getElementById('submitButton').addEventListener('click', onSubmit);
document.getElementById('calPreviousButton').addEventListener('click', () => {
calendar.prev();
});
document.getElementById('calNextButton').addEventListener('click', () => {
calendar.next();
});

const tzSelect = document.getElementById('timezone');
for (let i = 0; i < timezones.length; i += 1) {
const opt = document.createElement('option');
const tzName = timezones[i];
opt.value = tzName;
opt.innerHTML = tzName;
tzSelect.appendChild(opt);
}

tzSelect.addEventListener('change', () => {
calendar.setOption('timeZone', tzSelect.value);
});
},
);
}
Expand Down
2 changes: 1 addition & 1 deletion availability/templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
<meta charset='utf-8' />
<!-- <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.11.8/dist/umd/popper.min.js" integrity="sha384-I7E8VVD/ismYTF4hNIPjVp/Zjvgyol6VFvRkX/vR+Vc4jQkC+hVqc2pM8ODewa9r" crossorigin="anonymous"></script> -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.min.js" integrity="sha384-BBtl+eGJRgqQAUMxJ7pMwbEyER4l1g+O15P+16Ep7Q9Q+zqX6gSbd85u4mG4QzX+" crossorigin="anonymous"></script>
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.9/index.global.min.js'></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1">
{% block in_head %}
{% endblock %}
Expand Down
33 changes: 26 additions & 7 deletions availability/templates/index.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
{% extends "base.html" %}

{% block in_head %}
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js'></script>
<script src='https://cdn.jsdelivr.net/npm/luxon@3.5.0/build/global/luxon.min.js'></script>
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.15/index.global.min.js'></script>
<script src='https://cdn.jsdelivr.net/npm/@fullcalendar/luxon3@6.1.15/index.global.min.js'></script>
<script src='{{ js_url }}'></script>
<script>
const events = {{ events | tojson }};
const allowMaybe = {{ allow_maybe | tojson }};
const initialDate = {{ initial_date }};
const nDays = {{ number_of_days }};
initialize(initialDate, nDays, events, {{ has_spans | tojson }});
initialize(initialDate, nDays, events,
{{ has_spans | tojson }}, {{ timezones | tojson }});
</script>
{% endblock %}

Expand All @@ -21,6 +24,7 @@ <h5 class="card-title">Instructions</h5>
<li><b>{{ av_request.message }}</b></li>
{% endif %}
{% if has_spans %}
<li>You can always change your response by visiting your response again.</li>
<li>For the time spans shown in green, please drag your mouse to indicate
your available times. Edit by dragging. Shift-click to delete.
<b>Please indicate your entire available time, <em>not</em> the
Expand All @@ -36,8 +40,6 @@ <h5 class="card-title">Instructions</h5>
<li>For time slots shown in blue, please click to indicate your availability
(green) or unavailability (red).</li>
{% endif %}
<li>The calendar is shown in the <b>local time zone</b> known to your browser.
So please fill out the poll using your own local times.</li>
<li>If using this on a <b>touch device</b>:
To create a time range: hold on a time until a highlight appears, then drag.
To edit a time range: hold on that time range until selected, then drag
Expand All @@ -48,17 +50,34 @@ <h5 class="card-title">Instructions</h5>
</div>
</div>
<h5 class="mt-3">Poll for {{ av_request.name }}</h5>
<div id='calendar'></div>
<form id="calendarForm" method="POST" class="mt-3">
<div class="form-floating mb-3">
<textarea class="form-control" id="response" name="response" placeholder="..."
>{{ av_request.response }}</textarea>
<label for="response">Comments / Response to questions (see help text)</label>
<label for="response">Enter response text/comment here (optional, see below)</label>
{% if av_request.message %}
<div class="form-text">{{ av_request.message }}</div>
{% endif %}
</div>
<div class="row mb-3">
<div class="col-sm-1">
<button type="button" id="submitButton" class="btn btn-primary">Submit</button>
</div>
<div class="col-sm-1">
<div class="input-group">
<button type="button" id="calPreviousButton" class="btn btn-secondary"><i class="bi bi-caret-left-fill"></i></button>
<button type="button" id="calNextButton" class="btn btn-secondary"><i class="bi bi-caret-right-fill"></i></button>
</div>
</div>
<div class="col-sm-10">
<div class="input-group">
<label for="timezone" class="input-group-text">Timezone:</label>
<select class="form-select" aria-label="Timezone selector" id="timezone">
</select>
</div>
</div>
</div>
<div id='calendar'></div>
<input type="hidden" id="calendarState" name="calendarState"></input>
<button type="button" id="submitButton" class="btn btn-primary">Submit</button>
</form>
{% endblock %}
2 changes: 2 additions & 0 deletions dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export GRIST_DOC_ID="s7VzXiAHXbwgivucYprb6z"
export NOTIFY_FROM="inform@tiker.net"
export NOTIFY_TO="inform@tiker.net"

export CAL_TIMEZONES="America/Chicago,local,UTC"

export SECRET_KEY="CHANGE_ME"

poetry run flask --app=availability.app run --debug
33 changes: 32 additions & 1 deletion poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pygrist-mini = "^2024.1"
[tool.poetry.dev-dependencies]
ruff = "^0.6.1"
mypy = "^1.11.2"
pyright = "^1.1.392"

[tool.ruff]
target-version = "py38"
Expand Down
Loading