From 624324e73412c88629bc3898ad9d97b27eb1e509 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 16 Jan 2025 05:05:55 +0800 Subject: [PATCH 1/4] Add pyright to dev dependencies --- poetry.lock | 33 ++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index e2b6469..b0a0a2e 100644 --- a/poetry.lock +++ b/poetry.lock @@ -359,6 +359,17 @@ files = [ {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] +[[package]] +name = "nodeenv" +version = "1.9.1" +description = "Node.js virtual environment builder" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "nodeenv-1.9.1-py2.py3-none-any.whl", hash = "sha256:ba11c9782d29c27c70ffbdda2d7415098754709be8a7056d79a737cd901155c9"}, + {file = "nodeenv-1.9.1.tar.gz", hash = "sha256:6ec12890a2dab7946721edbfbcd91f3319c6ccc9aec47be7c7e6b7011ee6645f"}, +] + [[package]] name = "pygrist-mini" version = "2024.1" @@ -373,6 +384,26 @@ files = [ [package.dependencies] requests = ">=2.31,<3.0" +[[package]] +name = "pyright" +version = "1.1.392.post0" +description = "Command line wrapper for pyright" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyright-1.1.392.post0-py3-none-any.whl", hash = "sha256:252f84458a46fa2f0fd4e2f91fc74f50b9ca52c757062e93f6c250c0d8329eb2"}, + {file = "pyright-1.1.392.post0.tar.gz", hash = "sha256:3b7f88de74a28dcfa90c7d90c782b6569a48c2be5f9d4add38472bdaac247ebd"}, +] + +[package.dependencies] +nodeenv = ">=1.6.0" +typing-extensions = ">=4.1" + +[package.extras] +all = ["nodejs-wheel-binaries", "twine (>=3.4.1)"] +dev = ["twine (>=3.4.1)"] +nodejs = ["nodejs-wheel-binaries"] + [[package]] name = "requests" version = "2.32.3" @@ -480,4 +511,4 @@ watchdog = ["watchdog (>=2.3)"] [metadata] lock-version = "2.0" python-versions = ">=3.10" -content-hash = "f5f43ff94b6954cb1a89aa5dfca579aa91c6a007411ee9c15c8b71ca209ffac1" +content-hash = "ffaaa603aa56572f18888b91f554b546d1dc254a6035f1aa9dc41dd36c5326ba" diff --git a/pyproject.toml b/pyproject.toml index 0f47e6e..74f1862 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -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" From 603223e908cedd37226406df6ab39f9416a4d13f Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 16 Jan 2025 05:07:42 +0800 Subject: [PATCH 2/4] Fix: remove redundant inclusion of FullCalendar --- availability/templates/base.html | 1 - 1 file changed, 1 deletion(-) diff --git a/availability/templates/base.html b/availability/templates/base.html index b883870..88e4492 100644 --- a/availability/templates/base.html +++ b/availability/templates/base.html @@ -4,7 +4,6 @@ - {% block in_head %} From 8f86c9935879568eb4df7d6a30459fe9a2ebafb9 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 16 Jan 2025 05:08:52 +0800 Subject: [PATCH 3/4] Re-show response on submit --- availability/app.py | 11 ++++++----- availability/templates/index.html | 1 + 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/availability/app.py b/availability/app.py index 8c8ed8c..00aa86a 100644 --- a/availability/app.py +++ b/availability/app.py @@ -522,11 +522,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}'") diff --git a/availability/templates/index.html b/availability/templates/index.html index d1c84dd..bd785ba 100644 --- a/availability/templates/index.html +++ b/availability/templates/index.html @@ -21,6 +21,7 @@
Instructions
  • {{ av_request.message }}
  • {% endif %} {% if has_spans %} +
  • You can always change your response by visiting your response again.
  • For the time spans shown in green, please drag your mouse to indicate your available times. Edit by dragging. Shift-click to delete. Please indicate your entire available time, not the From cabd312db0f7c0223ef2e4600a2f179f1aef7221 Mon Sep 17 00:00:00 2001 From: Andreas Kloeckner Date: Thu, 16 Jan 2025 05:09:25 +0800 Subject: [PATCH 4/4] Add timezone support --- README.md | 1 + availability/app.py | 10 +++++--- availability/static/availability.js | 39 ++++++++++++++++++++++++----- availability/templates/base.html | 1 + availability/templates/index.html | 32 +++++++++++++++++------ dev.sh | 2 ++ 6 files changed, 69 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 6d3b883..9cdd4e9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/availability/app.py b/availability/app.py index 00aa86a..6b9b942 100644 --- a/availability/app.py +++ b/availability/app.py @@ -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 = [] @@ -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", @@ -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, diff --git a/availability/static/availability.js b/availability/static/availability.js index 3355436..e82d765 100644 --- a/availability/static/availability.js +++ b/availability/static/availability.js @@ -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, }); } @@ -84,7 +91,7 @@ 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', () => { @@ -92,6 +99,7 @@ function initialize(initialDate, nDays, events, hasSpans) { // eslint-disable-next-line no-undef const calendar = new FullCalendar.Calendar(calendarEl, { // plugins: [timeGridPlugin], + timeZone: timezones[0], initialView: 'timeGridNDay', headerToolbar: { start: false, @@ -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); + }); }, ); } diff --git a/availability/templates/base.html b/availability/templates/base.html index 88e4492..bd8fa56 100644 --- a/availability/templates/base.html +++ b/availability/templates/base.html @@ -5,6 +5,7 @@ + {% block in_head %} {% endblock %} diff --git a/availability/templates/index.html b/availability/templates/index.html index bd785ba..7f808e3 100644 --- a/availability/templates/index.html +++ b/availability/templates/index.html @@ -1,14 +1,17 @@ {% extends "base.html" %} {% block in_head %} - + + + {% endblock %} @@ -37,8 +40,6 @@
    Instructions
  • For time slots shown in blue, please click to indicate your availability (green) or unavailability (red).
  • {% endif %} -
  • The calendar is shown in the local time zone known to your browser. - So please fill out the poll using your own local times.
  • If using this on a touch device: 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 @@ -49,17 +50,34 @@
    Instructions
    Poll for {{ av_request.name }}
    -
    - + {% if av_request.message %}
    {{ av_request.message }}
    {% endif %}
    +
    +
    + +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    +
    +
    -
    {% endblock %} diff --git a/dev.sh b/dev.sh index fd31375..9c2d915 100755 --- a/dev.sh +++ b/dev.sh @@ -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