From c6590f91ddd4555a3b4ec90fcc8430a437500921 Mon Sep 17 00:00:00 2001 From: CasVT Date: Fri, 23 Feb 2024 12:37:10 +0100 Subject: [PATCH] Frontend Update --- .vscode/launch.json | 11 + backend/reminders.py | 13 +- backend/users.py | 3 +- frontend/static/css/admin.css | 73 ++-- frontend/static/css/general.css | 123 +++--- frontend/static/css/info.css | 65 +-- frontend/static/css/library.css | 103 ++++- frontend/static/css/login.css | 37 +- frontend/static/css/notification.css | 92 ++-- frontend/static/css/page_not_found.css | 1 + frontend/static/css/settings.css | 40 +- frontend/static/js/admin.js | 67 ++- frontend/static/js/general.js | 60 +-- frontend/static/js/library.js | 303 ++++++------- frontend/static/js/login.js | 98 +++-- frontend/static/js/notification.js | 436 ++++++++++--------- frontend/static/js/settings.js | 22 +- frontend/static/js/show.js | 95 ++--- frontend/static/js/templates.js | 14 +- frontend/static/js/window.js | 142 ++++--- frontend/templates/admin.html | 36 +- frontend/templates/login.html | 19 +- frontend/templates/page_not_found.html | 4 +- frontend/templates/reminders.html | 561 ++++++++++++++----------- 24 files changed, 1328 insertions(+), 1090 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..d81e89d --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "configurations": [ + { + "name": "Main File", + "type": "debugpy", + "request": "launch", + "python": "/bin/python3", + "program": "${workspaceFolder}/MIND.py" + } + ] +} \ No newline at end of file diff --git a/backend/reminders.py b/backend/reminders.py index 6ada583..65e2a75 100644 --- a/backend/reminders.py +++ b/backend/reminders.py @@ -166,6 +166,11 @@ class Reminder: ).fetchone() reminder = dict(reminder) + reminder["weekdays"] = [ + int(n) + for n in reminder["weekdays"].split(",") + if n + ] if reminder["weekdays"] else None reminder['notification_services'] = self._get_notification_services() return reminder @@ -277,7 +282,7 @@ class Reminder: if repeated_reminder: next_time = _find_next_time( data["time"], - RepeatQuantity(data["repeat_quantity"]), + RepeatQuantity(data["repeat_quantity"]) if data["repeat_quantity"] else None, data["repeat_interval"], weekdays ) @@ -408,6 +413,12 @@ class Reminders: (self.user_id,) ) ] + for r in reminders: + r["weekdays"] = [ + int(n) + for n in r["weekdays"].split(",") + if n + ] if r["weekdays"] else None # Sort result reminders.sort(key=sort_by.value[0], reverse=sort_by.value[1]) diff --git a/backend/users.py b/backend/users.py index e881ae2..7802775 100644 --- a/backend/users.py +++ b/backend/users.py @@ -23,7 +23,7 @@ class User: def __init__(self, id: int) -> None: result = get_db(dict).execute( - "SELECT username, admin FROM users WHERE id = ? LIMIT 1;", + "SELECT username, admin, salt FROM users WHERE id = ? LIMIT 1;", (id,) ).fetchone() if not result: @@ -32,6 +32,7 @@ class User: self.username: str = result['username'] self.user_id = id self.admin: bool = result['admin'] == 1 + self.salt: bytes = result['salt'] return @property diff --git a/frontend/static/css/admin.css b/frontend/static/css/admin.css index 41f5e5c..e8eb719 100644 --- a/frontend/static/css/admin.css +++ b/frontend/static/css/admin.css @@ -49,7 +49,7 @@ main { overflow-y: auto; padding: .5rem; - padding-top: calc(1rem + var(--nav-width)); + padding-top: var(--nav-width); } #settings-form { @@ -63,12 +63,13 @@ h2 { width: 100%; border-bottom: 1px solid var(--color-gray); - padding: 1rem 1rem 0rem 1rem; + padding: 1rem 1rem .25rem 1rem; font-size: clamp(1rem, 10vw, 2rem); } -.table-container { +.settings-table-container, +.user-table-container { width: 100%; overflow-x: auto; @@ -108,6 +109,10 @@ h2 { font-size: .9rem; } +.settings-table td > p { + margin-top: .25rem; +} + .number-input { width: fit-content; display: flex; @@ -126,8 +131,12 @@ h2 { text-align: right; } +.number-input > * { + padding: .5rem 1rem; +} + .number-input > p { - padding: .75rem .75rem .75rem 0rem; + padding-left: 0; } .settings-table select { @@ -170,11 +179,10 @@ h2 { #user-table { min-width: 25rem; border-spacing: 0px; - margin-bottom: 2rem; } -#user-table th, -#user-table td { +#user-table :where(th, td) { + height: 2.65rem; padding: .25rem .5rem; text-align: left; } @@ -183,54 +191,53 @@ h2 { border-top: 1px solid var(--color-gray); } -#user-table th:first-child, -#user-table td:first-child { +#user-table :where(th, td):first-child { + width: 10rem; padding-left: 2rem; } -#user-table th:last-child, -#user-table td:last-child { +#user-table :where(td, td):nth-child(2) { + width: 15rem; +} + +#user-table :where(th, td):last-child { + width: 5.75rem; display: flex; + align-items: center; gap: 1rem; padding-right: 2rem; } -#user-table td:first-child { - width: 100%; +#user-table button { + height: 1.25rem; } #user-table svg { aspect-ratio: 1/1; - width: 1.25rem; - height: 1.25rem; + height: 100%; + width: auto; } -#new-username-input, -#new-password-input { - width: 40%; - padding: .25rem; -} - -#user-list form { - margin-top: .5rem; - width: 75%; -} - -#user-list input[type="password"] { +#user-table input { + width: 100%; padding: .25rem; } @media (max-width: 40rem) { - #settings-form, - .table-container { + #settings-form { justify-content: flex-start; } - + h2 { text-align: center; padding-inline: 0; } + .settings-table-container, + .user-table-container { + justify-content: left; + } + .settings-table tbody { display: flex; flex-direction: column; @@ -239,6 +246,7 @@ h2 { .settings-table tr { display: inline-flex; flex-direction: column; + padding-left: 1rem; } .settings-table td { @@ -246,14 +254,11 @@ h2 { } .settings-table td:first-child { + width: 100%; text-align: left; } .settings-table td:nth-child(2) { min-width: 0; } - - #user-table { - width: 100%; - } } diff --git a/frontend/static/css/general.css b/frontend/static/css/general.css index a332f21..adeacc8 100644 --- a/frontend/static/css/general.css +++ b/frontend/static/css/general.css @@ -28,30 +28,32 @@ img { width: 100%; } -button { +button, +label { border: 0; border-radius: 4px; background-color: var(--color-dark); color: var(--color-light); } -button:hover { +button:hover, +label:hover { cursor: pointer; } input:not([type="checkbox"]), select, -textarea { - width: 100%; - +textarea, +.as-button { border: 2px solid var(--color-gray); border-radius: 4px; - padding: .75rem; + padding: .6rem; outline: 0; - box-shadow: var(--default-shadow); background-color: var(--color-dark); color: var(--color-light); + box-shadow: var(--default-shadow); + font-size: 1rem; } @@ -69,6 +71,21 @@ svg rect { fill: var(--color-light); } +::-webkit-scrollbar { + width: 12px; + height: 12px; + background-color: var(--color-dark); +} + +::-webkit-scrollbar-thumb { + -webkit-border-radius: 6px; + background-color: var(--color-gray); +} + +::-webkit-scrollbar-thumb:hover { + background-color: var(--color-light-gray); +} + /* */ /* Utility classes */ /* */ @@ -76,17 +93,25 @@ svg rect { display: none !important; } -.error { +.error, +.error-container p { color: var(--color-error) !important; font-size: 1rem !important; } +.error-container p { + display: block !important; +} + .error-icon path, -.error-icon rect { +.error-icon rect, +.error-container path, +.error-container rect { fill: var(--color-error) !important; } -.error-input { +.error-input, +.error-container input { border: 2px solid var(--color-error) !important; } @@ -173,11 +198,11 @@ header img { padding-block: var(--rem-clamp); } -.nav-divider.show-nav > nav { +body:has(#nav-switch:checked) .nav-divider > nav { left: var(--rem-clamp); } -.nav-divider.show-nav > .window-container { +body:has(#nav-switch:checked) .nav-divider > .window-container { margin-left: calc(var(--nav-width) + var(--rem-clamp)); } @@ -237,72 +262,70 @@ nav > div > button svg { } /* */ -/* Main window */ +/* Window management */ /* */ .window-container { margin-left: calc(4rem + var(--rem-clamp)); width: 100%; - + display: flex; - overflow-y: auto; - overflow-x: hidden; - + overflow: hidden; + transition: margin-left .3s ease-in-out; } -.window-container > div { +.window-container > :where(#home, .extra-window-container) { width: 100%; flex: 0 0 auto; - - transform: translateX(0); - transition: transform .5s ease-in-out; + translate: 0 0; + transition: translate .5s ease-in-out; } -.window-container > div.show-window { - transform: translateX(-100%); +.window-container.inter-window-ani > :where(#home, .extra-window-container) { + transition: translate .5s ease-in-out, + transform .5s ease-in-out; } -.window-container > div:not(#home) { +.extra-window-container { + --y-offset: 0%; + transform: translateY(var(--y-offset)); +} + +.extra-window-container > div { + height: 100%; + overflow-y: auto; +} + +.window-container.show-window > :where(#home, .extra-window-container) { + translate: -100% 0; +} + +.window-container.show-window > .extra-window-container { + transform: translateY(var(--y-offset)); +} + +/* */ +/* Styling extra window */ +/* */ +.extra-window-container > div { padding: var(--rem-clamp); } -.window-container > div:not(#home) > h2 { +.extra-window-container > div > h2 { text-align: center; font-size: clamp(1.3rem, 5vw, 2rem); margin-bottom: 2rem; } -.window-container > div:not(#home) > h2:not(:first-of-type) { +.extra-window-container > div > h2:not(:first-of-type) { margin-top: 1.5rem; } -.window-container > div:not(#home) > p { +.extra-window-container > div > p { text-align: center; } -.tab-selector { - width: 100%; - display: flex; - justify-content: center; - align-items: center; - flex-wrap: wrap; - gap: 1rem; - - padding-inline: .5rem; - padding-top: 1rem; -} - -.tab-selector > button { - border: 2px solid var(--color-gray); - padding: .5rem 1rem; - transition: background-color .3s ease-in-out; -} - -.tab-selector > button[data-selected="true"] { - background-color: var(--color-gray); -} - @media (max-width: 543px) { .window-container { margin-left: 0; @@ -311,4 +334,4 @@ nav > div > button svg { nav { left: -100%; } -} \ No newline at end of file +} diff --git a/frontend/static/css/info.css b/frontend/static/css/info.css index 85b1e42..da01f60 100644 --- a/frontend/static/css/info.css +++ b/frontend/static/css/info.css @@ -10,10 +10,9 @@ gap: 1rem; } -.form-container > form input, -.form-container > form select, -.form-container > form textarea, -.form-container > form button { +.form-container > form :where( + input, select, textarea, button, label +) { padding: 1rem; } @@ -27,14 +26,14 @@ gap: var(--gap); } -.sub-inputs > input, -.sub-inputs > select, -.sub-inputs > button { +.sub-inputs > :where( + input, select, button, label +) { width: calc(50% - (var(--gap) / 2)); } .form-container > form > button, -.sub-inputs > button { +.sub-inputs > :where(button, label) { display: flex; justify-content: center; align-items: center; @@ -55,8 +54,17 @@ opacity: 0; } -.color-list { +#color-button { + --color: var(--color-dark); + background-color: var(--color); +} + +#info-form:has(#color-toggle:checked) .color-list { display: flex; +} + +.color-list { + display: none; justify-content: center; align-items: center; gap: 1rem; @@ -68,25 +76,26 @@ box-shadow: var(--default-shadow); } -.color-list > button { - height: 1.5rem; - width: 1.5rem; - +.color-list > label { padding: 1rem; border: 1px solid transparent; background-color: var(--color); } -.color-list > button[data-selected='true'] { +.color-list > label:has(input:checked) { border-color: var(--color-white); } -.notification-service-list { +#info-form:has(#notification-service-selection-toggle:checked) .notification-service-selection { + display: flex; +} + +.notification-service-selection { width: 100%; max-height: 10rem; overflow-y: auto; - display: flex; + display: none; flex-direction: column; border: 2px solid var(--color-gray); @@ -95,19 +104,20 @@ box-shadow: var(--default-shadow); } -.notification-service-list > div { +.notification-service-selection > div { display: flex; gap: 1rem; padding: .5rem .75rem; } -.notification-service-list > div:not(:first-child) { +.notification-service-selection > div:not(:first-child) { border-top: 1px solid var(--color-gray); } .form-container > form .repeat-options > button { width: calc((100% / 3) - (var(--gap) / 1.5)); min-width: min-content; + padding: 1rem 0rem; } .repeat-bar { @@ -214,6 +224,7 @@ div.options > button { .sub-inputs > input, .sub-inputs > select, .sub-inputs > button, + .sub-inputs > label, .form-container > form .repeat-options > button { width: 100%; } @@ -258,8 +269,8 @@ div.options > button { } #info.show-add-static-reminder #template-selection, -#info.show-add-static-reminder #color-toggle, -#info.show-add-static-reminder #toggle-notification-service-list { +#info.show-add-static-reminder #color-button, +#info.show-add-static-reminder #notification-service-selection-button { width: 100%; } @@ -279,8 +290,8 @@ div.options > button { margin-top: -1rem; } -#info.show-add-template #color-toggle, -#info.show-add-template #toggle-notification-service-list { +#info.show-add-template #color-button, +#info.show-add-template #notification-service-selection-button { width: 100%; } @@ -292,7 +303,7 @@ div.options > button { display: none; } -#info.show-edit-reminder #color-toggle { +#info.show-edit-reminder #color-button { width: 100%; } @@ -310,8 +321,8 @@ div.options > button { margin-top: -1rem; } -#info.show-edit-static-reminder #color-toggle, -#info.show-edit-static-reminder #toggle-notification-service-list { +#info.show-edit-static-reminder #color-button, +#info.show-edit-static-reminder #notification-service-selection-button { width: 100%; } @@ -330,7 +341,7 @@ div.options > button { margin-top: -1rem; } -#info.show-edit-template #color-toggle, -#info.show-edit-template #toggle-notification-service-list { +#info.show-edit-template #color-button, +#info.show-edit-template #notification-service-selection-button { width: 100%; } diff --git a/frontend/static/css/library.css b/frontend/static/css/library.css index bdf57cb..9d10110 100644 --- a/frontend/static/css/library.css +++ b/frontend/static/css/library.css @@ -1,8 +1,10 @@ +/* */ /* SEARCH BAR */ +/* */ .search-container { max-width: 40rem; margin: auto; - + padding-block: clamp(1rem, 4vw, 2rem); } @@ -12,24 +14,28 @@ .search-bar { display: flex; - + border: 2px solid var(--color-gray); border-radius: 4px; - + box-shadow: var(--default-shadow); } -.search-bar button { - width: 3.5rem; - padding: .8rem; +.search-bar :where(button, label) { + width: clamp(2rem, 7.5vw, 3rem); + flex-shrink: 0; + + display: flex; + justify-content: center; + align-items: center; } -.search-bar button svg { - width: 1rem; +.search-bar :where(button, label) svg { height: 1rem; } .search-bar input { + width: 100%; border: 0; padding-block: 1rem; box-shadow: none; @@ -37,7 +43,7 @@ #clear-button { opacity: 0; - + transition: opacity .1s linear; } @@ -46,26 +52,44 @@ } #sort-input { - width: min-content; + width: clamp(6rem, 25vw, 12rem); border: 0; box-shadow: none; } +.window-container:has( + :where(#static-tab-selector, #template-tab-selector):checked +) #sort-input > option:where( + [value="time"], [value="time_reversed"] +) { + display: none; +} + +/* */ /* REMINDER LIST */ -#reminder-tab, -#static-reminder-tab, -#template-tab { +/* */ +.tab-container > div { --gap: 1rem; --entry-width: 13rem; max-width: 43rem; margin-inline: auto; - display: flex; + display: none; justify-content: left; gap: var(--gap); flex-wrap: wrap; padding: 1rem; + + transition: max-width .75s ease-in-out; +} + +body:has(#wide-toggle:checked) .tab-container > div { + max-width: 85rem; +} + +#home { + overflow-y: auto; } .entry.add-entry { @@ -122,10 +146,61 @@ button.entry.fit { font-weight: 500; } +/* */ +/* Tab selector */ +/* */ +.tab-selector { + width: 100%; + display: flex; + justify-content: center; + align-items: center; + flex-wrap: wrap; + gap: 1rem; + + padding-inline: 1rem; + padding-top: 1rem; +} + +.tab-selector > input { + display: none; +} + +.tab-selector > label { + min-width: 9.55rem; + + padding: .5rem 1rem; + border-radius: 4px; + border: 2px solid var(--color-gray); + background-color: var(--color-dark); + color: var(--color-light); + + text-align: center; + + transition: background-color .3s ease-in-out; +} + +.tab-selector > input:checked + label { + background-color: var(--color-gray); +} + +.window-container:has(#reminder-tab-selector:checked) #reminder-tab, +.window-container:has(#static-tab-selector:checked) #static-reminder-tab, +.window-container:has(#template-tab-selector:checked) #template-tab { + display: flex; +} + @media (max-width: 543px) { header > div { transform: translateX(0); } + + .tab-selector > label { + flex: 1 0 25%; + } + + #wide-button { + display: none; + } .entry { flex-grow: 1; diff --git a/frontend/static/css/login.css b/frontend/static/css/login.css index 7456c82..a88256b 100644 --- a/frontend/static/css/login.css +++ b/frontend/static/css/login.css @@ -4,7 +4,7 @@ main { overflow-y: hidden; } -main.show-create > .form-container { +main:has(#form-switch:checked) .form-container { transform: translateY(-100%); } @@ -16,7 +16,7 @@ main.show-create > .form-container { align-items: center; padding: 1rem; - + transition: transform .25s ease-in-out; } @@ -40,8 +40,24 @@ form h2 { font-size: clamp(1.2rem, 7vw, 2rem); } +#username-error-container, +#password-error-container { + width: 100%; + + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + gap: .5rem; +} + form input { - max-width: 20rem; + width: min(100%, 20rem); +} + +#username-error-container p, +#password-error-container p { + width: min(100%, 19rem); } .switch-button { @@ -50,6 +66,11 @@ form input { color: var(--color-light); text-decoration: underline; + text-align: center; +} + +.switch-button:hover { + cursor: pointer; } button[type="submit"] { @@ -57,13 +78,3 @@ button[type="submit"] { font-size: 1.1rem; } - -p.error { - width: 100%; - max-width: 20rem; - margin-top: -.5rem; - - padding-inline: .5rem; - - text-align: left; -} \ No newline at end of file diff --git a/frontend/static/css/notification.css b/frontend/static/css/notification.css index 3fc8a69..495e66e 100644 --- a/frontend/static/css/notification.css +++ b/frontend/static/css/notification.css @@ -8,19 +8,30 @@ } #add-service-button { - padding: .5rem 8rem; + width: min(100%, 17rem); + height: 2rem; + + display: flex; + justify-content: center; + align-items: center; + + border-radius: 4px; background-color: var(--color-gray); } #add-service-button > svg { height: 1rem; width: 1rem; + transition: transform .125s linear; +} + +.table-container:has(#service-list-toggle:checked) #add-service-button > svg { + transform: rotate(45deg); } .overflow-container { margin-inline: auto; - width: 100%; - max-width: 50rem; + width: min(100%, 50rem); overflow-x: auto; } @@ -29,6 +40,10 @@ border-spacing: 0px; } +.overflow-container > table:not(:has(tbody > tr)) { + display: none; +} + .overflow-container > table th, .overflow-container > table td { text-align: left; @@ -38,7 +53,7 @@ padding: .5rem; } -#services-list > tr > td { +.overflow-container td { border-top: 1px solid var(--color-gray); padding: .25rem; } @@ -46,7 +61,7 @@ .title-column { min-width: 9.5rem; width: 25%; - + padding-left: 1.5rem; padding-right: 1rem; } @@ -56,17 +71,18 @@ width: 65%; } -#services-list input { - border-radius: 2px; +.overflow-container table input { + width: 100%; + border-radius: 4px; padding: .25rem; box-shadow: none; } -#services-list input:read-only { +.overflow-container input:read-only { border-color: transparent; } -#services-list > tr > td.action-column { +.overflow-container .action-column { min-width: 4rem; width: 20%; @@ -77,18 +93,24 @@ padding-right: 1.5rem; } -.action-column svg { - width: 1rem; - height: 1rem; +.action-column > button { + display: flex; + justify-content: center; + align-items: center; } -#services-list > tr:not(.edit) > td.action-column > button[data-type="edit"], -#services-list > tr.edit > td.action-column > button[data-type="save"] { - display: inline-block; +.overflow-container .action-column svg { + width: 1.25rem; + height: 1.25rem; } -#services-list > tr.edit > td.action-column > button[data-type="edit"], -#services-list > tr:not(.edit):not(#add-row) > td.action-column > button[data-type="save"] { +tr:has(input:not(:read-only)) button[data-type="save"], +tr:has(input:read-only) button[data-type="edit"] { + display: flex; +} + +tr:has(input:not(:read-only)) button[data-type="edit"], +tr:has(input:read-only) button[data-type="save"] { display: none; } @@ -99,14 +121,22 @@ display: none; } -.overflow-container.show-add #add-service-container { +.overflow-container:has(#service-list-toggle:checked) table { + display: none; +} + +.overflow-container:has(#service-list-toggle:checked) #add-service-container { display: block; } -.overflow-container.show-add > table { +.overflow-container:has(#add-service-toggle:checked) #service-list { display: none; } +.overflow-container:has(#add-service-toggle:checked) #add-service-window { + display: flex; +} + #service-list { display: flex; gap: 1rem; @@ -130,14 +160,6 @@ font-size: 1.1rem; } -#add-service-container.show-add-window #add-service-window { - display: flex; -} - -#add-service-container.show-add-window #service-list { - display: none; -} - /* */ /* Add service form */ /* */ @@ -205,7 +227,7 @@ min-height: 5rem; max-height: 15rem; overflow-y: auto; - + align-items: center; background-color: var(--color-dark); @@ -228,19 +250,23 @@ } .add-row { - height: 2rem; - width: 80%; + width: min(100%, 21rem); display: flex; + justify-content: center; + flex-wrap: wrap; gap: 1rem; } .add-row input { flex-grow: 1; + height: 2rem; + min-width: 0rem; font-size: .8rem; } .add-row button { + height: 2rem; padding: .35rem .75rem; background-color: var(--color-gray); border-radius: 4px; @@ -265,3 +291,9 @@ height: inherit; fill: var(--color-dark); } + +@media (max-width: 543px) { + #service-list button { + flex-grow: 1; + } +} diff --git a/frontend/static/css/page_not_found.css b/frontend/static/css/page_not_found.css index 646e607..eef4dfd 100644 --- a/frontend/static/css/page_not_found.css +++ b/frontend/static/css/page_not_found.css @@ -14,6 +14,7 @@ h2 { p { font-size: clamp(1rem, 4.2vw, 1.75rem); + text-align: center; } a { diff --git a/frontend/static/css/settings.css b/frontend/static/css/settings.css index f0d9889..8bfb152 100644 --- a/frontend/static/css/settings.css +++ b/frontend/static/css/settings.css @@ -1,45 +1,39 @@ -.settings-container { - max-width: 50rem; +#settings-table { margin-inline: auto; - - text-align: center; + width: min(100%, 50rem); } -.settings-container > h3 { - margin-bottom: 1.25rem; - - padding-bottom: .5rem; - border-bottom: 2px solid var(--color-gray); - - font-size: 1.4rem; +#settings-table td { + padding: .5rem; + width: 50%; } -.settings-container > h3:not(:first-of-type) { - margin-top: 2.5rem; +#settings-table tr > td:first-child { + text-align: right; + font-weight: 500; } -.settings-container button { +#settings-table button { padding: .5rem 2rem; background-color: var(--color-gray); box-shadow: var(--default-shadow); } -.settings-container input, -.settings-container textarea, -.settings-container select { - max-width: 20rem; +#settings-table tr:has(#default-service-input) { + display: none; +} + +#settings-table tr:has(#default-service-input option) { + display: table-row; } #change-password-form { display: flex; - flex-direction: column; - justify-content: center; - align-items: center; gap: 1rem; } -#delete-account-button { +#settings-table #delete-account-button { background-color: var(--color-error); } @@ -67,4 +61,4 @@ text-decoration: none; font-size: 1.2rem; -} \ No newline at end of file +} diff --git a/frontend/static/js/admin.js b/frontend/static/js/admin.js index de1d604..39becc2 100644 --- a/frontend/static/js/admin.js +++ b/frontend/static/js/admin.js @@ -1,7 +1,12 @@ const setting_inputs = { - 'allow_new_accounts': document.querySelector('#allow-new-accounts-input'), - 'login_time': document.querySelector('#login-time-input'), - 'login_time_reset': document.querySelector('#login-time-reset-input') + allow_new_accounts: document.querySelector('#allow-new-accounts-input'), + login_time: document.querySelector('#login-time-input'), + login_time_reset: document.querySelector('#login-time-reset-input') +}; + +const user_inputs = { + username: document.querySelector('#new-username-input'), + password: document.querySelector('#new-password-input') }; function checkLogin() { @@ -58,8 +63,8 @@ function toggleAddUser() { const el = document.querySelector('#add-user-row'); if (el.classList.contains('hidden')) { // Show row - document.querySelector('#new-username-input').value = ''; - document.querySelector('#new-password-input').value = ''; + user_inputs.username.value = ''; + user_inputs.password.value = ''; el.classList.remove('hidden'); } else { // Hide row @@ -68,9 +73,11 @@ function toggleAddUser() { }; function addUser() { + user_inputs.username.classList.remove('error-input'); + user_inputs.username.title = ''; const data = { - 'username': document.querySelector('#new-username-input').value, - 'password': document.querySelector('#new-password-input').value + 'username': user_inputs.username.value, + 'password': user_inputs.password.value }; fetch(`${url_prefix}/api/admin/users?api_key=${api_key}`, { 'method': 'POST', @@ -85,12 +92,23 @@ function addUser() { loadUsers(); }) .catch(e => { - console.log(e); + if (e === "UsernameTaken") { + user_inputs.username.classList.add('error-input'); + user_inputs.username.title = 'Username already taken'; + + } else if (e === "UsernameInvalid") { + user_inputs.username.classList.add('error-input'); + user_inputs.username.title = 'Username contains invalid characters'; + + } else + console.log(e); }); }; function editUser(id) { - const new_password = document.querySelector(`#user-table tr[data-id="${id}"] input`).value; + const new_password = document.querySelector( + `#user-table tr[data-id="${id}"] input` + ).value; fetch(`${url_prefix}/api/admin/users/${id}?api_key=${api_key}`, { 'method': 'PUT', 'headers': {'Content-Type': 'application/json'}, @@ -103,7 +121,7 @@ function deleteUser(id) { document.querySelector(`#user-table tr[data-id="${id}"]`).remove(); fetch(`${url_prefix}/api/admin/users/${id}?api_key=${api_key}`, { 'method': 'DELETE' - }) + }); }; function loadUsers() { @@ -117,9 +135,10 @@ function loadUsers() { entry.dataset.id = user.id; const username = document.createElement('td'); - const username_text = document.createElement('p'); - username_text.innerText = user.username; - username.appendChild(username_text); + username.innerText = user.username; + entry.appendChild(username); + + const password = document.createElement('td'); const new_password_form = document.createElement('form'); new_password_form.classList.add('hidden'); new_password_form.action = `javascript:editUser(${user.id})`; @@ -127,23 +146,29 @@ function loadUsers() { new_password.type = 'password'; new_password.placeholder = 'New password'; new_password_form.appendChild(new_password); - username.appendChild(new_password_form); - entry.appendChild(username); + password.appendChild(new_password_form); + entry.appendChild(password); const actions = document.createElement('td'); entry.appendChild(actions); const edit_user = document.createElement('button'); - edit_user.onclick = e => e.currentTarget.parentNode.previousSibling.querySelector('form').classList.toggle('hidden'); - edit_user.innerHTML = icons.edit; + edit_user.onclick = e => e + .currentTarget + .parentNode + .previousSibling + .querySelector('form') + .classList + .toggle('hidden'); + edit_user.innerHTML = Icons.edit; actions.appendChild(edit_user); if (user.username !== 'admin') { const delete_user = document.createElement('button'); delete_user.onclick = e => deleteUser(user.id); - delete_user.innerHTML = icons.delete; + delete_user.innerHTML = Icons.delete; actions.appendChild(delete_user); - } + }; table.appendChild(entry); }); @@ -156,9 +181,9 @@ checkLogin(); loadSettings(); loadUsers(); -document.querySelector('#logout-button').onclick = (e) => logout(); +document.querySelector('#logout-button').onclick = e => logout(); document.querySelector('#settings-form').action = 'javascript:submitSettings();'; document.querySelector('#add-user-button').onclick = e => toggleAddUser(); document.querySelector('#add-user-form').action = 'javascript:addUser()'; document.querySelector('#download-db-button').onclick = e => - window.location.href = `${url_prefix}/api/admin/database?api_key=${api_key}` + window.location.href = `${url_prefix}/api/admin/database?api_key=${api_key}`; diff --git a/frontend/static/js/general.js b/frontend/static/js/general.js index 5258aeb..547e8d8 100644 --- a/frontend/static/js/general.js +++ b/frontend/static/js/general.js @@ -1,46 +1,49 @@ -const types = { +// The duration of the animation set for the window translation +const window_ani_ms = 500; + +const Types = { 'reminder': document.getElementById('reminder-tab'), 'static_reminder': document.getElementById('static-reminder-tab'), 'template': document.getElementById('template-tab') }; -const icons = { +const Icons = { 'save': '', 'edit': '', 'delete': '', 'add': '' }; -const info_classes = [ +const InfoClasses = [ 'show-add-reminder', 'show-add-template', 'show-add-static-reminder', 'show-edit-reminder', 'show-edit-template', 'show-edit-static-reminder' ]; -function toggleNav() { - document.querySelector('.nav-divider').classList.toggle('show-nav'); -}; - function showWindow(id) { - document.querySelectorAll('.window-container > div').forEach(e => { - if (e.id === id || e.id === 'home') { - e.classList.remove('hidden'); - setTimeout(() => e.classList.add('show-window'), 0); - } else { - e.classList.add('hidden'); - e.classList.remove('show-window'); - }; - }); -}; - -function hideWindow() { - document.querySelectorAll('.show-window').forEach(e => { - e.classList.remove('show-window'); - }); - setTimeout(() => { - document.querySelectorAll('.window-container > div:not(#home)').forEach( - e => e.classList.add('hidden') + const window_container = document.querySelector('.window-container'); + if (id === "home") { + window_container.classList.remove( + 'show-window', + 'inter-window-ani' ); - }, 500); + } else { + const extra_window_container = + document.querySelector('.extra-window-container'); + + const offset = [ + ...extra_window_container.children + ].indexOf(document.getElementById(id)) * -100; + + extra_window_container.style.setProperty( + '--y-offset', + `${offset}%` + ) + window_container.classList.add('show-window'); + setTimeout( + () => window_container.classList.add('inter-window-ani'), + window_ani_ms + ); + }; }; function logout() { @@ -59,7 +62,10 @@ function logout() { const default_values = { 'api_key': null, 'locale': 'en-GB', - 'default_service': null + 'default_service': null, + 'sorting_reminders': 'time', + 'sorting_static': 'title', + 'sorting_templates': 'title' }; function setupLocalStorage() { diff --git a/frontend/static/js/library.js b/frontend/static/js/library.js index 8f5ac0e..6491944 100644 --- a/frontend/static/js/library.js +++ b/frontend/static/js/library.js @@ -1,73 +1,80 @@ -const sorting_options = {}; -sorting_options[types.reminder.id] = [ - ['time', 'Time'], - ['time_reversed', 'Time Reversed'], - ['title', 'Title'], - ['title_reversed', 'Title Reversed'], - ['date_added', 'Date Added'], - ['date_added_reversed', 'Date Added Reversed'] -]; -sorting_options[types.static_reminder.id] = [ - ['title', 'Title'], - ['title_reversed', 'Title Reversed'], - ['date_added', 'Date Added'], - ['date_added_reversed', 'Date Added Reversed'] -]; -sorting_options[types.template.id] = [ - ['title', 'Title'], - ['title_reversed', 'Title Reversed'], - ['date_added', 'Date Added'], - ['date_added_reversed', 'Date Added Reversed'] -]; - -function showTab(button) { - // Apply styling to selected button - document.querySelectorAll('.tab-selector > button').forEach( - b => b.dataset.selected = b === button ? 'true' : 'false' - ); - - // Show desired tab and hide all others - document.querySelectorAll('#home > div:not(.tab-selector):not(.search-container)').forEach( - e => e.classList.add('hidden') - ); - document.getElementById(button.dataset.target).classList.remove('hidden'); - - fillSortOptions(); - document.querySelector('#search-input').value = ''; +const NavButtons = { + home: document.querySelector('#home-button'), + notification_services: document.querySelector('#notification-services-button'), + settings: document.querySelector('#settings-button'), + log_out: document.querySelector('#logout-button') }; -// -// Filling library -// -function getWeekDays(locale) -{ - let baseDate = new Date(Date.UTC(2017, 0, 2)); // just a Monday - let weekDays = []; - for(i = 0; i < 7; i++) +const LibEls = { + tab_selector: document.querySelector('.tab-selector'), + tab_container: document.querySelector('.tab-container'), + search_bar: { + form: document.querySelector('#search-form'), + input: document.querySelector('#search-input'), + clear: document.querySelector('#clear-button'), + sort: document.querySelector('#sort-input'), + wide: document.querySelector('#wide-button') + } +}; + +// +// Helpers +// +function getSorting(type, key=false) { + let sorting_key; + if (type === Types.reminder) + sorting_key = 'sorting_reminders'; + else if (type === Types.static_reminder) + sorting_key = 'sorting_static'; + else if (type === Types.template) + sorting_key = 'sorting_templates'; + + if (key) + return sorting_key; + else + return getLocalStorage(sorting_key)[sorting_key]; +}; + +function getWeekDays(locale) { + const baseDate = new Date(Date.UTC(2017, 0, 2)); // just a Monday + const weekDays = []; + for (i = 0; i < 7; i++) { weekDays.push(baseDate.toLocaleDateString(locale, { weekday: 'short' })); baseDate.setDate(baseDate.getDate() + 1); } return weekDays; }; -const week_days = getWeekDays(getLocalStorage('locale')['locale']); +function getActiveTab() { + for (let t of Object.values(Types)) { + if (getComputedStyle(t).display === 'flex') + return t + }; + return null; +}; + +// +// Filling library +// function fillTable(table, results) { - table.querySelectorAll('button.entry:not(.add-entry)').forEach(e => e.remove()); + table.querySelectorAll('button.entry:not(.add-entry)').forEach( + e => e.remove() + ); results.forEach(r => { const entry = document.createElement('button'); entry.classList.add('entry'); entry.dataset.id = r.id; - entry.addEventListener('click', e => showEdit(r.id, table)); + entry.onclick = e => showEdit(r.id, table); if (r.color !== null) entry.style.setProperty('--color', r.color); - + const title = document.createElement('h2'); title.innerText = r.title; entry.appendChild(title); - if (table === types.reminder) { + if (table === Types.reminder) { const time = document.createElement('p'); let offset = new Date(r.time * 1000).getTimezoneOffset() * -60; let d = new Date((r.time + offset) * 1000); @@ -83,140 +90,43 @@ function fillTable(table, results) { formatted_date += interval_text; } else if (r.weekdays !== null) - formatted_date += ` (each ${r.weekdays.split(',').map(d => week_days[parseInt(d)]).join(', ')})`; + formatted_date += ` (each ${r.weekdays.map(d => week_days[d]).join(', ')})`; time.innerText = formatted_date; entry.appendChild(time); }; - + table.appendChild(entry); - - if (title.clientHeight < title.scrollHeight) - entry.classList.add('expand'); }); - table.querySelectorAll('button.entry:not(.add-entry)').forEach(r => r.classList.add('fit')); + evaluateSizing(); }; -function fillLibrary(url, type) { - fetch(url) - .then(response => { - if (!response.ok) return Promise.reject(response.status); - return response.json(); - }) - .then(json => fillTable(type, json.result)) - .catch(e => { - if (e === 401) - window.location.href = `${url_prefix}/`; - else - console.log(e); - }); -}; +function fillLibrary(type=null) { + let tab_type = type || getActiveTab(); -function fillReminders() { - const sorting = document.querySelector('#sort-input').value; - fillLibrary(`/api/reminders?api_key=${api_key}&sort_by=${sorting}`, types.reminder); -}; - -function fillStaticReminders(assume_sorting=false) { - let sorting; - if (assume_sorting) - sorting = sorting_options[types.static_reminder.id][0][0]; - else - sorting = document.querySelector('#sort-input').value; - fillLibrary(`/api/staticreminders?api_key=${api_key}&sort_by=${sorting}`, types.static_reminder); -} - -function fillTemplates(assume_sorting=false) { - let sorting; - if (assume_sorting) - sorting = sorting_options[types.template.id][0][0]; - else - sorting = document.querySelector('#sort-input').value; - fillLibrary(`/api/templates?api_key=${api_key}&sort_by=${sorting}`, types.template); -}; - -// -// Library search -// -function searchLibrary() { - const query = document.querySelector('#search-input').value, - tab = document.getElementById( - document.querySelector('.tab-selector > button[data-selected="true"]').dataset.target - ) - const sorting = document.querySelector('#sort-input').value; let url; - if (tab === types.reminder) - url = `${url_prefix}/api/reminders/search?api_key=${api_key}&query=${query}&sort_by=${sorting}`; - else if (tab === types.static_reminder) - url = `${url_prefix}/api/staticreminders/search?api_key=${api_key}&query=${query}&sort_by=${sorting}`; - else if (tab === types.template) - url = `${url_prefix}/api/templates/search?api_key=${api_key}&query=${query}&sort_by=${sorting}`; - else return; - - fillLibrary(url, tab); -}; - -function clearSearchLibrary() { - document.querySelector('#search-input').value = ''; - const tab = document.getElementById( - document.querySelector('.tab-selector > button[data-selected="true"]').dataset.target - ) - if (tab === types.reminder) - fillReminders(); - else if (tab === types.static_reminder) - fillStaticReminders(); - else if (tab === types.template) - fillTemplates(); - else return; -}; - -// -// Library sort -// -function fillSortOptions() { - const tab = document.getElementById( - document.querySelector('.tab-selector > button[data-selected="true"]').dataset.target - ) - const sort_options = sorting_options[tab.id]; - - const select = document.getElementById('sort-input'); - select.innerHTML = ''; - sort_options.forEach(o => { - const entry = document.createElement('option'); - entry.value = o[0] - entry.innerText = o[1] - select.appendChild(entry); - }); - select.querySelector(':first-child').setAttribute('selected', ''); -}; - -function applySorting() { - const query = document.querySelector('#search-input').value; - if (query !== '') { - searchLibrary(); + if (tab_type === Types.reminder) + url = `${url_prefix}/api/reminders`; + else if (tab_type === Types.static_reminder) + url = `${url_prefix}/api/staticreminders`; + else if (tab_type === Types.template) + url = `${url_prefix}/api/templates`; + else return; - }; - const sorting = document.getElementById('sort-input').value, - tab = document.getElementById( - document.querySelector('.tab-selector > button[data-selected="true"]').dataset.target - ) - - let url; - if (tab === types.reminder) - url = `${url_prefix}/api/reminders?api_key=${api_key}&sort_by=${sorting}`; - else if (tab === types.static_reminder) - url = `${url_prefix}/api/staticreminders?api_key=${api_key}&sort_by=${sorting}`; - else if (tab === types.template) - url = `${url_prefix}/api/templates?api_key=${api_key}&sort_by=${sorting}`; - else return; + const sorting = getSorting(tab_type); + const query = LibEls.search_bar.input.value; + if (query) + url = `${url}/search?api_key=${api_key}&sort_by=${sorting}&query=${query}`; + else + url = `${url}?api_key=${api_key}&sort_by=${sorting}`; fetch(url) .then(response => { if (!response.ok) return Promise.reject(response.status); return response.json(); }) - .then(json => fillTable(tab, json.result)) + .then(json => fillTable(tab_type, json.result)) .catch(e => { if (e === 401) window.location.href = `${url_prefix}/`; @@ -225,25 +135,50 @@ function applySorting() { }); }; +function saveSorting() { + const type = getActiveTab(); + const sorting_key = getSorting(type, key=true); + const value = LibEls.search_bar.sort.value; + setLocalStorage({[sorting_key]: value}); +}; + +function evaluateSizing() { + const tab = getActiveTab(); + const entries = [...tab.querySelectorAll('button:not(.add-entry)')]; + entries.forEach(e => e.classList.remove('fit')); + entries.forEach(e => { + const title = e.querySelector('h2'); + if (title.clientHeight < title.scrollHeight) + e.classList.add('expand'); + }); + entries.forEach(e => e.classList.add('fit')); +}; + // code run on load -document.querySelectorAll('.tab-selector > button').forEach(b => { - b.addEventListener('click', e => showTab(b)); +Object.values(Types).forEach(t => fillLibrary(t)); +setInterval(() => fillLibrary(Types.reminder), 60000); + +const week_days = getWeekDays(getLocalStorage('locale')['locale']); + +NavButtons.home.onclick = e => showWindow("home"); +NavButtons.notification_services.onclick = e => showWindow("notification"); +NavButtons.settings.onclick = e => showWindow("settings"); +NavButtons.log_out.onclick = e => logout(); + +LibEls.search_bar.form.action = 'javascript:fillLibrary();' +LibEls.search_bar.sort.value = getSorting(getActiveTab()); +LibEls.search_bar.sort.onchange = e => { + saveSorting(); + fillLibrary(); +}; +LibEls.search_bar.clear.onclick = e => { + LibEls.search_bar.input.value = ''; + fillLibrary(); +}; + +LibEls.tab_selector.querySelectorAll('input').forEach(r => r.onchange = e => { + evaluateSizing(); + LibEls.search_bar.input.value = ''; + LibEls.search_bar.sort.value = getSorting(getActiveTab()); }); - -document.getElementById('toggle-nav').addEventListener('click', e => toggleNav()); - -document.getElementById('home-button').addEventListener('click', e => hideWindow()); -document.getElementById('notification-services-button').addEventListener('click', e => showWindow('notification')); -document.getElementById('settings-button').addEventListener('click', e => showWindow('settings')); -document.getElementById('logout-button').addEventListener('click', e => logout()); - -fillSortOptions(); -fillReminders(); -fillStaticReminders(assume_sorting=true); -fillTemplates(assume_sorting=true); -setInterval(fillReminders, 60000); - -document.querySelector('#search-form').setAttribute('action', 'javascript:searchLibrary();'); -document.querySelector('#clear-button').addEventListener('click', e => clearSearchLibrary()); -document.querySelector('#sort-input').addEventListener('change', e => applySorting()); diff --git a/frontend/static/js/login.js b/frontend/static/js/login.js index 965a158..8343982 100644 --- a/frontend/static/js/login.js +++ b/frontend/static/js/login.js @@ -1,37 +1,36 @@ -const login_inputs = { - 'username': document.querySelector('#login-form > input[type="text"]'), - 'password': document.querySelector('#login-form > input[type="password"]') -}; - -const login_errors = { - 'username': document.getElementById('username-error'), - 'password': document.getElementById('password-error') -}; - -const create_inputs = { - 'username': document.querySelector('#create-form > input[type="text"]'), - 'password': document.querySelector('#create-form > input[type="password"]') -} - -const create_errors = { - 'username_invalid': document.getElementById('new-username-error'), - 'username_taken': document.getElementById('taken-username-error'), -}; - -function toggleWindow() { - document.querySelector('main').classList.toggle('show-create'); +const forms = { + 'login': { + 'form': document.querySelector('#login-form'), + 'inputs': { + 'username': document.querySelector('#login-form input[type="text"]'), + 'password': document.querySelector('#login-form input[type="password"]') + }, + 'errors': { + 'username': document.querySelector("#username-error-container"), + 'password': document.querySelector("#password-error-container") + } + }, + 'create': { + 'form': document.querySelector('#create-form'), + 'inputs': { + 'username': document.querySelector('#create-form input[type="text"]'), + 'password': document.querySelector('#create-form input[type="password"]') + }, + 'errors': { + 'username_invalid': document.querySelector('#new-username-error'), + 'username_taken': document.querySelector('#taken-username-error') + } + } }; function login(data=null) { - login_inputs.username.classList.remove('error-input'); - login_errors.username.classList.add('hidden'); - login_inputs.password.classList.remove('error-input'); - login_errors.password.classList.add('hidden'); + forms.login.errors.username.classList.remove('error-container'); + forms.login.errors.password.classList.remove('error-container'); if (data === null) data = { - 'username': login_inputs.username.value, - 'password': login_inputs.password.value + 'username': forms.login.inputs.username.value, + 'password': forms.login.inputs.password.value }; fetch(`${url_prefix}/api/auth/login`, { @@ -53,26 +52,23 @@ function login(data=null) { window.location.href = `${url_prefix}/reminders`; }) .catch(e => { - if (e === 401) { - login_inputs.password.classList.add('error-input'); - login_errors.password.classList.remove('hidden'); - } else if (e === 404) { - login_inputs.username.classList.add('error-input'); - login_errors.username.classList.remove('hidden'); - } else { + if (e === 404) + forms.login.errors.username.classList.add('error-container'); + else if (e === 401) + forms.login.errors.password.classList.add('error-container'); + else console.log(e); - }; }); }; function create() { - create_inputs.username.classList.remove('error-input'); - create_errors.username_invalid.classList.add('hidden'); - create_errors.username_taken.classList.add('hidden'); + forms.create.inputs.username.classList.remove('error-input'); + forms.create.errors.username_invalid.classList.add('hidden'); + forms.create.errors.username_taken.classList.add('hidden'); const data = { - 'username': create_inputs.username.value, - 'password': create_inputs.password.value + 'username': forms.create.inputs.username.value, + 'password': forms.create.inputs.password.value }; fetch(`${url_prefix}/api/user/add`, { 'method': 'POST', @@ -86,11 +82,11 @@ function create() { }) .catch(e => { if (e === 'UsernameInvalid') { - create_inputs.username.classList.add('error-input'); - create_errors.username_invalid.classList.remove('hidden'); + forms.create.inputs.username.classList.add('error-input'); + forms.create.errors.username_invalid.classList.remove('hidden'); } else if (e === 'UsernameTaken') { - create_inputs.username.classList.add('error-input'); - create_errors.username_taken.classList.remove('hidden'); + forms.create.inputs.username.classList.add('error-input'); + forms.create.errors.username_taken.classList.remove('hidden'); } else { console.log(e); }; @@ -98,7 +94,7 @@ function create() { }; function checkLogin() { - fetch(`${url_prefix}/api/auth/status?api_key=${JSON.parse(localStorage.getItem('MIND')).api_key}`) + fetch(`${url_prefix}/api/auth/status?api_key=${api_key}`) .then(response => { if (!response.ok) return Promise.reject(response.status); return response.json(); @@ -129,13 +125,15 @@ function checkAllowNewAccounts() { // code run on load if (localStorage.getItem('MIND') === null) - localStorage.setItem('MIND', JSON.stringify({'api_key': null, 'locale': 'en-GB', 'default_service': null})) + localStorage.setItem('MIND', JSON.stringify( + {'api_key': null, 'locale': 'en-GB', 'default_service': null} + )) const url_prefix = document.getElementById('url_prefix').dataset.value; +const api_key = JSON.parse(localStorage.getItem('MIND')).api_key; checkLogin(); checkAllowNewAccounts(); -document.getElementById('login-form').setAttribute('action', 'javascript:login();'); -document.getElementById('create-form').setAttribute('action', 'javascript:create();'); -document.querySelectorAll('.switch-button').forEach(e => e.addEventListener('click', e => toggleWindow())); +forms.login.form.action = 'javascript:login();'; +forms.create.form.action = 'javascript:create();'; diff --git a/frontend/static/js/notification.js b/frontend/static/js/notification.js index 518d97e..fba2f7d 100644 --- a/frontend/static/js/notification.js +++ b/frontend/static/js/notification.js @@ -1,121 +1,118 @@ -function fillNotificationSelection() { +const NotiEls = { + services_list: document.querySelector('#services-list'), + service_list: document.querySelector('#service-list'), + default_service_input: document.querySelector('#default-service-input'), + service_selection: document.querySelector('.notification-service-selection'), + notification_service_row: document.querySelector('.element-storage .notification-service-row'), + add_service_window: document.querySelector('#add-service-window'), + triggers: { + add_service: document.querySelector('#add-service-toggle'), + service_list: document.querySelector('#service-list-toggle') + } +}; + +// +// Fill lists and tables +// +function fillNotificationTable(json) { + NotiEls.services_list.innerHTML = ''; + json.result.forEach(service => { + const entry = NotiEls.notification_service_row.cloneNode(true); + entry.dataset.id = service.id; + + entry.querySelector('.title-column input').value = service.title; + + const url_input = entry.querySelector('.url-column input'); + url_input.value = service.url; + url_input.onkeydown = e => { + if (e.key === 'Enter') + saveService(service.id); + }; + + entry.querySelector('button[data-type="edit"]').onclick = e => + document.querySelectorAll(`tr[data-id="${service.id}"] input`).forEach( + e => e.removeAttribute('readonly') + ); + + entry.querySelector('button[data-type="save"]').onclick = e => + saveService(service.id); + + entry.querySelector('button[data-type="delete"]').onclick = e => + deleteService(service.id); + + NotiEls.services_list.appendChild(entry); + }); +}; + +function fillNotificationSelection(json) { + // Default service setting + NotiEls.default_service_input.innerHTML = ''; + const default_service = getLocalStorage('default_service')['default_service']; + json.result.forEach(service => { + const entry = document.createElement('option'); + entry.value = service.id; + entry.innerText = service.title; + if (default_service === service.id) + entry.setAttribute('selected', ''); + NotiEls.default_service_input.appendChild(entry); + }); + + if (!NotiEls.default_service_input.querySelector(`option[value="${default_service}"]`)) + setLocalStorage({'default_service': + parseInt(NotiEls.default_service_input.querySelector('option')?.value) + || null + }); + + // Selection when managing (static)reminders/templates + NotiEls.service_selection.innerHTML = ''; + json.result.forEach(service => { + const entry = document.createElement('div'); + + const select = document.createElement('input'); + select.dataset.id = service.id; + select.type = 'checkbox'; + entry.appendChild(select); + + const title = document.createElement('p'); + title.innerText = service.title; + entry.appendChild(title); + + NotiEls.service_selection.appendChild(entry); + }); + if (json.result.length > 0) + NotiEls.service_selection.querySelector(':first-child input').checked = true; +}; + +function setNoNotificationServiceMsg(json) { + if (json.result.length > 0) { + LibEls.tab_container.querySelectorAll('.add-entry').forEach(ae => { + ae.classList.remove('error', 'error-icon'); + if (ae.id === 'add-reminder') + ae.onclick = e => showAdd(Types.reminder); + else if (ae.id === 'add-static-reminder') + ae.onclick = e => showAdd(Types.static_reminder); + else if (ae.id === 'add-template') + ae.onclick = e => showAdd(Types.template); + }); + + } else { + LibEls.tab_container.querySelectorAll('.add-entry').forEach(ae => { + ae.classList.add('error', 'error-icon'); + ae.onclick = e => showWindow('notification'); + }); + }; +}; + +function fillNotificationServices() { fetch(`${url_prefix}/api/notificationservices?api_key=${api_key}`) .then(response => { if (!response.ok) return Promise.reject(response.status); return response.json(); }) .then(json => { - if (json.result.length) { - document.getElementById('add-reminder').classList.remove('error', 'error-icon'); - - const default_select = document.querySelector('#default-service-input'); - default_select.innerHTML = ''; - let default_service = getLocalStorage('default_service')['default_service']; - json.result.forEach(service => { - const entry = document.createElement('option'); - entry.value = service.id; - entry.innerText = service.title; - if (default_service === service.id) - entry.setAttribute('selected', ''); - default_select.appendChild(entry); - }); - if (!document.querySelector(`#default-service-input > option[value="${default_service}"]`)) - setLocalStorage({'default_service': - parseInt(document.querySelector('#default-service-input > option')?.value) - || null - }); - default_service = getLocalStorage('default_service')['default_service']; - - inputs.notification_service.innerHTML = ''; - json.result.forEach(service => { - const entry = document.createElement('div'); - - const select = document.createElement('input'); - select.dataset.id = service.id; - select.type = 'checkbox'; - entry.appendChild(select); - - const title = document.createElement('p'); - title.innerText = service.title; - entry.appendChild(title); - - inputs.notification_service.appendChild(entry); - }); - inputs.notification_service.querySelector(':first-child input').checked = true; - - const table = document.getElementById('services-list'); - table.innerHTML = ''; - json.result.forEach(service => { - const entry = document.createElement('tr'); - entry.dataset.id = service.id; - - const title_container = document.createElement('td'); - title_container.classList.add('title-column'); - const title = document.createElement('input'); - title.setAttribute('readonly', ''); - title.setAttribute('type', 'text'); - title.value = service.title; - title_container.appendChild(title); - entry.appendChild(title_container); - - const url_container = document.createElement('td'); - url_container.classList.add('url-column'); - const url = document.createElement('input'); - url.setAttribute('readonly', ''); - url.setAttribute('type', 'text'); - url.value = service.url; - url.addEventListener('keydown', e => { - if (e.key === 'Enter') - saveService(service.id); - }); - url_container.appendChild(url); - entry.appendChild(url_container); - - const actions = document.createElement('td'); - actions.classList.add('action-column'); - entry.appendChild(actions); - - const edit_button = document.createElement('button'); - edit_button.dataset.type = 'edit'; - edit_button.addEventListener('click', e => editService(service.id)); - edit_button.title = 'Edit'; - edit_button.setAttribute('aria-label', 'Edit'); - edit_button.innerHTML = icons.edit; - actions.appendChild(edit_button); - - const save_button = document.createElement('button'); - save_button.dataset.type = 'save'; - save_button.addEventListener('click', e => saveService(service.id)); - save_button.title = 'Save Edits'; - save_button.setAttribute('aria-label', 'Save Edits'); - save_button.innerHTML = icons.save; - actions.appendChild(save_button); - - const delete_button = document.createElement('button'); - delete_button.dataset.type = 'delete'; - delete_button.addEventListener('click', e => deleteService(service.id)); - delete_button.title = 'Delete'; - delete_button.setAttribute('aria-label', 'Delete'); - delete_button.innerHTML = icons.delete; - actions.appendChild(delete_button); - - table.appendChild(entry); - }); - } else { - document.getElementById('add-reminder').classList.add('error', 'error-icon'); - - inputs.notification_service.innerHTML = ''; - - const default_select = document.querySelector('#default-service-input'); - default_select.innerHTML = ''; - - const default_service = getLocalStorage('default_service')['default_service']; - if (!document.querySelector(`#default-service-input > option[value="${default_service}"]`)) - setLocalStorage({'default_service': - parseInt(document.querySelector('#default-service-input > option')?.value) - || null - }); - }; + fillNotificationTable(json); + fillNotificationSelection(json); + setNoNotificationServiceMsg(json); }) .catch(e => { if (e === 401) @@ -125,11 +122,9 @@ function fillNotificationSelection() { }); }; -function editService(id) { - document.querySelectorAll(`tr[data-id="${id}"] input`).forEach(e => e.removeAttribute('readonly')); - document.querySelector(`tr[data-id="${id}"]`).classList.add('edit'); -}; - +// +// Actions for table +// function saveService(id) { const row = document.querySelector(`tr[data-id="${id}"]`); const save_button = row.querySelector('button[data-type="save"]'); @@ -144,8 +139,8 @@ function saveService(id) { }) .then(response => { if (!response.ok) return Promise.reject(response.status); - - fillNotificationSelection(); + + fillNotificationServices(); }) .catch(e => { if (e === 401) @@ -153,7 +148,6 @@ function saveService(id) { else if (e === 400) { save_button.classList.add('error-icon'); save_button.title = 'Invalid Apprise URL'; - save_button.setAttribute('aria-label', 'Invalid Apprise URL'); } else console.log(e); }); @@ -169,9 +163,7 @@ function deleteService(id) { if (json.error !== null) return Promise.reject(json); row.remove(); - fillNotificationSelection(); - if (document.querySelectorAll('#services-list > tr').length === 0) - document.getElementById('add-reminder').classList.add('error', 'error-icon'); + fillNotificationServices(); }) .catch(e => { if (e.error === 'ApiKeyExpired' || e.error === 'ApiKeyInvalid') @@ -185,83 +177,31 @@ function deleteService(id) { }); }; -function testService() { - const test_button = document.querySelector('#test-service'); - - // Check regexes for input's - [...document.querySelectorAll('#add-service-window > input:not([data-regex=""])[data-regex]')] - .forEach(el => el.classList.remove('error-input')); - - const faulty_inputs = - [...document.querySelectorAll('#add-service-window > input:not([data-regex=""])[data-regex]')] - .filter(el => !new RegExp - ( - el.dataset.regex.split(',').slice(0, el.dataset.regex.split(',').length-1).join(','), - el.dataset.regex.split(',')[el.dataset.regex.split(',').length-1] - ).test(el.value) - ); - if (faulty_inputs.length > 0) { - faulty_inputs.forEach(el => el.classList.add('error-input')); +// +// Adding a service +// +function showServiceList(e) { + if (!e.target.checked) return; - }; - const data = { - 'url': buildAppriseURL() - }; - if (!data.url) { - test_button.classList.add('error-input'); - test_button.title = 'Required field missing'; + NotiEls.triggers.add_service.checked = false; + + if (notification_services !== null) return; - }; - fetch(`${url_prefix}/api/notificationservices/test?api_key=${api_key}`, { - 'method': 'POST', - 'headers': {'Content-Type': 'application/json'}, - 'body': JSON.stringify(data) - }) - .then(response => { - if (!response.ok) return Promise.reject(response.status); - - test_button.classList.remove('error-input'); - test_button.title = ''; - test_button.classList.add('show-sent'); - }) - .catch(e => { - if (e === 401) - window.location.href = `${url_prefix}/`; - else if (e === 400) { - test_button.classList.add('error-input'); - test_button.title = 'Invalid Apprise URL'; - } else - console.log(e); + + fetch(`${url_prefix}/api/notificationservices/available?api_key=${api_key}`) + .then(response => response.json()) + .then(json => { + notification_services = json.result; + json.result.forEach((result, index) => { + const entry = document.createElement('button'); + entry.innerText = result.name; + entry.onclick = e => showAddServiceWindow(index); + NotiEls.service_list.appendChild(entry); + }); }); }; -function toggleAddService() { - const cont = document.querySelector('.overflow-container'); - if (cont.classList.contains('show-add')) { - // Hide add - cont.classList.remove('show-add'); - hideAddServiceWindow(); - } else { - // Show add - if (notification_services === null) { - fetch(`${url_prefix}/api/notificationservices/available?api_key=${api_key}`) - .then(response => response.json()) - .then(json => { - notification_services = json.result; - const table = document.querySelector('#service-list'); - json.result.forEach((result, index) => { - const entry = document.createElement('button'); - entry.innerText = result.name; - entry.addEventListener('click', e => showAddServiceWindow(index)); - table.appendChild(entry); - }); - }); - }; - cont.classList.add('show-add'); - }; -}; - function createTitle() { const service_title = document.createElement('input'); service_title.id = 'service-title'; @@ -354,25 +294,25 @@ function createEntriesList(token) { add_row.classList.add('add-row', 'hidden'); const add_input = document.createElement('input'); add_input.type = 'text'; - add_input.addEventListener('keydown', e => { + add_input.onkeydown = e => { if (e.key === "Enter") { e.preventDefault(); e.stopImmediatePropagation(); addEntry(entries_list); }; - }); + }; add_row.appendChild(add_input); const add_entry_button = document.createElement('button'); add_entry_button.type = 'button'; add_entry_button.innerText = 'Add'; - add_entry_button.addEventListener('click', e => addEntry(entries_list)); + add_entry_button.onclick = e => addEntry(entries_list); add_row.appendChild(add_entry_button); entries_list.appendChild(add_row); const add_button = document.createElement('button'); add_button.type = 'button'; - add_button.innerHTML = icons.add; - add_button.addEventListener('click', e => toggleAddRow(add_row)); + add_button.innerHTML = Icons.add; + add_button.onclick = e => toggleAddRow(add_row); entries_list.appendChild(add_button); return entries_list; @@ -398,7 +338,7 @@ function addEntry(entries_list) { }; function showAddServiceWindow(index) { - const window = document.getElementById('add-service-window'); + const window = NotiEls.add_service_window; window.innerHTML = ''; window.dataset.index = index; @@ -423,10 +363,10 @@ function showAddServiceWindow(index) { const show_args = document.createElement('button'); show_args.type = 'button'; show_args.innerText = 'Show Advanced Settings'; - show_args.addEventListener('click', e => { + show_args.onclick = e => { window.querySelectorAll('[data-is_arg="true"]').forEach(el => el.classList.toggle('hidden')); show_args.innerText = show_args.innerText === 'Show Advanced Settings' ? 'Hide Advanced Settings' : 'Show Advanced Settings'; - }); + }; window.appendChild(show_args); }; @@ -474,7 +414,9 @@ function showAddServiceWindow(index) { }); if (vars[1] === 'args' && vars[0].length > 0) - window.querySelectorAll('[data-is_arg="true"]').forEach(el => el.classList.toggle('hidden')); + window.querySelectorAll('[data-is_arg="true"]').forEach( + el => el.classList.toggle('hidden') + ); }) // Bottom options @@ -484,13 +426,13 @@ function showAddServiceWindow(index) { const cancel = document.createElement('button'); cancel.type = 'button'; cancel.innerText = 'Cancel'; - cancel.addEventListener('click', e => toggleAddService()); + cancel.onclick = e => NotiEls.triggers.add_service.checked = false; options.appendChild(cancel); const test = document.createElement('button'); test.id = 'test-service'; test.type = 'button'; - test.addEventListener('click', e => testService()); + test.onclick = e => testService(); options.appendChild(test); const test_text = document.createElement('div'); test_text.innerText = 'Test'; @@ -504,17 +446,13 @@ function showAddServiceWindow(index) { add.innerText = 'Add'; options.appendChild(add); window.appendChild(options); - - document.getElementById('add-service-container').classList.add('show-add-window'); -}; -function hideAddServiceWindow() { - document.getElementById('add-service-container').classList.remove('show-add-window'); + NotiEls.triggers.add_service.checked = true; }; function buildAppriseURL() { - const data = notification_services[document.querySelector('#add-service-window').dataset.index]; - const inputs = document.querySelectorAll('#add-service-window > [data-map][data-is_arg="false"]'); + const data = notification_services[NotiEls.add_service_window.dataset.index]; + const inputs = NotiEls.add_service_window.querySelectorAll('[data-map][data-is_arg="false"]'); const values = {}; // Gather all values and format @@ -555,7 +493,7 @@ function buildAppriseURL() { template = template.replace(`{${key}}`, value); // Add args - const args = [...document.querySelectorAll('#add-service-window > [data-map][data-is_arg="true"]')] + const args = [...NotiEls.add_service_window.querySelectorAll('[data-map][data-is_arg="true"]')] .map(el => { if (['INPUT', 'SELECT'].includes(el.nodeName) && el.value && el.value !== el.dataset.default) return `${el.dataset.map}=${el.value}`; @@ -587,15 +525,66 @@ function buildAppriseURL() { return template; }; -function addService() { - const add_button = document.querySelector('#add-service-window > .options > button[type="submit"]'); - +function testService() { + const test_button = document.querySelector('#test-service'); + // Check regexes for input's - [...document.querySelectorAll('#add-service-window > input:not([data-regex=""])[data-regex]')] + [...NotiEls.add_service_window.querySelectorAll('input:not([data-regex=""])[data-regex]')] .forEach(el => el.classList.remove('error-input')); const faulty_inputs = - [...document.querySelectorAll('#add-service-window > input:not([data-regex=""])[data-regex]')] + [...NotiEls.add_service_window.querySelectorAll('input:not([data-regex=""])[data-regex]')] + .filter(el => !new RegExp + ( + el.dataset.regex.split(',').slice(0, el.dataset.regex.split(',').length-1).join(','), + el.dataset.regex.split(',')[el.dataset.regex.split(',').length-1] + ).test(el.value) + ); + if (faulty_inputs.length > 0) { + faulty_inputs.forEach(el => el.classList.add('error-input')); + return; + }; + + const data = { + 'url': buildAppriseURL() + }; + if (!data.url) { + test_button.classList.add('error-input'); + test_button.title = 'Required field missing'; + return; + }; + fetch(`${url_prefix}/api/notificationservices/test?api_key=${api_key}`, { + 'method': 'POST', + 'headers': {'Content-Type': 'application/json'}, + 'body': JSON.stringify(data) + }) + .then(response => { + if (!response.ok) return Promise.reject(response.status); + + test_button.classList.remove('error-input'); + test_button.title = ''; + test_button.classList.add('show-sent'); + }) + .catch(e => { + if (e === 401) + window.location.href = `${url_prefix}/`; + else if (e === 400) { + test_button.classList.add('error-input'); + test_button.title = 'Invalid Apprise URL'; + } else + console.log(e); + }); +}; + +function addService() { + const add_button = NotiEls.add_service_window.querySelector('.options > button[type="submit"]'); + + // Check regexes for input's + [...NotiEls.add_service_window.querySelectorAll('input:not([data-regex=""])[data-regex]')] + .forEach(el => el.classList.remove('error-input')); + + const faulty_inputs = + [...NotiEls.add_service_window.querySelectorAll('input:not([data-regex=""])[data-regex]')] .filter(el => !( (!el.required && el.value === '') @@ -631,8 +620,9 @@ function addService() { add_button.classList.remove('error-input'); add_button.title = ''; - toggleAddService(); - fillNotificationSelection(); + NotiEls.triggers.service_list.checked = false; + NotiEls.triggers.add_service.checked = false; + fillNotificationServices(); }) .catch(e => { if (e === 401) @@ -647,9 +637,9 @@ function addService() { // code run on load -fillNotificationSelection(); +fillNotificationServices(); let notification_services = null; -document.getElementById('add-service-button').addEventListener('click', e => toggleAddService()); -document.getElementById('add-service-window').setAttribute('action', 'javascript:addService();'); +NotiEls.triggers.service_list.onchange = showServiceList; +NotiEls.add_service_window.action = 'javascript:addService();'; diff --git a/frontend/static/js/settings.js b/frontend/static/js/settings.js index e73559d..1cc7e77 100644 --- a/frontend/static/js/settings.js +++ b/frontend/static/js/settings.js @@ -1,14 +1,24 @@ +const SettingsEls = { + locale_input: document.querySelector('#locale-input'), + default_service_input: document.querySelector('#default-service-input'), + change_password_form: document.querySelector('#change-password-form'), + delete_account_button: document.querySelector('#delete-account-button') +}; + function loadSettings() { - document.getElementById('locale-input').value = getLocalStorage('locale')['locale']; + // Default Service is handled by notification.fillNotificationSelection() + document.getElementById('locale-input').value = + getLocalStorage('locale')['locale']; }; function updateLocale(e) { setLocalStorage({'locale': e.target.value}); - window.location.reload(); + fillLibrary(Types.reminder); }; function updateDefaultService(e) { setLocalStorage({'default_service': parseInt(e.target.value)}); + // Add window is handled by show.showAdd() }; function changePassword() { @@ -45,7 +55,7 @@ function deleteAccount() { loadSettings(); -document.getElementById('locale-input').addEventListener('change', updateLocale); -document.querySelector('#default-service-input').addEventListener('change', updateDefaultService); -document.getElementById('change-password-form').setAttribute('action', 'javascript:changePassword()'); -document.getElementById('delete-account-button').addEventListener('click', e => deleteAccount()); +SettingsEls.locale_input.onchange = updateLocale; +SettingsEls.default_service_input.onchange = updateDefaultService; +SettingsEls.change_password_form.action = 'javascript:changePassword();'; +SettingsEls.delete_account_button.onclick = e => deleteAccount(); diff --git a/frontend/static/js/show.js b/frontend/static/js/show.js index 5c33b2b..fcf7836 100644 --- a/frontend/static/js/show.js +++ b/frontend/static/js/show.js @@ -4,32 +4,37 @@ function showAdd(type) { inputs.title.value = ''; inputs.text.value = ''; inputs.time.value = ''; - inputs.notification_service.querySelectorAll('input[type="checkbox"]').forEach(c => c.checked = false); - inputs.notification_service.querySelector(`input[type="checkbox"][data-id="${default_service}"]`).checked = true; - document.querySelectorAll('.weekday-bar > input[type="checkbox"]').forEach(el => el.checked = false); + inputs.notification_service.querySelectorAll('input[type="checkbox"]') + .forEach(c => c.checked = false); + inputs.notification_service.querySelector( + `input[type="checkbox"][data-id="${default_service}"]` + ).checked = true; + document.querySelectorAll('.weekday-bar > input[type="checkbox"]') + .forEach(el => el.checked = false); toggleNormal(); - toggleColor(true); + selectColor(colors[0]); + inputs.color_toggle.checked = false; document.getElementById('test-reminder').classList.remove('show-sent'); const cl = document.getElementById('info').classList; cl.forEach(c => { - if (info_classes.includes(c)) cl.remove(c) + if (InfoClasses.includes(c)) cl.remove(c) }); document.querySelector('.options > button[type="submit"]').innerText = 'Add'; - if (type === types.reminder) { + + document.querySelector('#test-reminder > div:first-child').innerText = 'Test'; + const title = document.querySelector('#info h2'); + if (type === Types.reminder) { cl.add('show-add-reminder'); - document.querySelector('#info h2').innerText = 'Add a reminder'; - document.querySelector('#test-reminder > div:first-child').innerText = 'Test'; + title.innerText = 'Add a reminder'; inputs.time.setAttribute('required', ''); - } else if (type === types.template) { + } else if (type === Types.template) { cl.add('show-add-template'); - document.querySelector('#info h2').innerText = 'Add a template'; - document.querySelector('#test-reminder > div:first-child').innerText = 'Test'; + title.innerText = 'Add a template'; inputs.time.removeAttribute('required'); - } else if (type === types.static_reminder) { + } else if (type === Types.static_reminder) { cl.add('show-add-static-reminder'); - document.querySelector('#info h2').innerText = 'Add a static reminder'; - document.querySelector('#test-reminder > div:first-child').innerText = 'Test'; + title.innerText = 'Add a static reminder'; inputs.time.removeAttribute('required'); } else return; @@ -38,14 +43,14 @@ function showAdd(type) { function showEdit(id, type) { let url; - if (type === types.reminder) { + if (type === Types.reminder) { url = `${url_prefix}/api/reminders/${id}?api_key=${api_key}`; inputs.time.setAttribute('required', ''); - } else if (type === types.template) { + } else if (type === Types.template) { url = `${url_prefix}/api/templates/${id}?api_key=${api_key}`; inputs.time.removeAttribute('required'); type_buttons.repeat_interval.removeAttribute('required'); - } else if (type === types.static_reminder) { + } else if (type === Types.static_reminder) { url = `${url_prefix}/api/staticreminders/${id}?api_key=${api_key}`; document.getElementById('test-reminder').classList.remove('show-sent'); inputs.time.removeAttribute('required'); @@ -59,26 +64,27 @@ function showEdit(id, type) { }) .then(json => { document.getElementById('info').dataset.id = id; - if (json.result.color !== null) { - if (inputs.color.classList.contains('hidden')) { - toggleColor(); - }; - selectColor(json.result['color']); - }; - + inputs.color_toggle.checked = false; + selectColor(json.result.color || colors[0]); inputs.title.value = json.result.title; - if (type === types.reminder) { + if (type === Types.reminder) { var trigger_date = new Date( - (json.result.time + new Date(json.result.time * 1000).getTimezoneOffset() * -60) * 1000 + (json.result.time + + new Date(json.result.time * 1000).getTimezoneOffset() + * -60 + ) * 1000 ); - inputs.time.value = trigger_date.toLocaleString('en-CA').slice(0,10) + 'T' + trigger_date.toTimeString().slice(0,5); + inputs.time.value = + trigger_date.toLocaleString('en-CA').slice(0,10) + + 'T' + + trigger_date.toTimeString().slice(0,5); }; inputs.notification_service.querySelectorAll('input[type="checkbox"]').forEach( c => c.checked = json.result.notification_services.includes(parseInt(c.dataset.id)) ); - if (type == types.reminder) { + if (type == Types.reminder) { if (json.result.repeat_interval !== null) { toggleRepeated(); type_buttons.repeat_interval.value = json.result.repeat_interval; @@ -106,32 +112,23 @@ function showEdit(id, type) { const cl = document.getElementById('info').classList; cl.forEach(c => { - if (info_classes.includes(c)) cl.remove(c) + if (InfoClasses.includes(c)) cl.remove(c) }); document.querySelector('.options > button[type="submit"]').innerText = 'Save'; - if (type === types.reminder) { + const title = document.querySelector('#info h2'); + const test_text = document.querySelector('#test-reminder > div:first-child'); + if (type === Types.reminder) { cl.add('show-edit-reminder'); - document.querySelector('#info h2').innerText = 'Edit a reminder'; - document.querySelector('#test-reminder > div:first-child').innerText = 'Test'; - } else if (type === types.template) { + title.innerText = 'Edit a reminder'; + test_text.innerText = 'Test'; + } else if (type === Types.template) { cl.add('show-edit-template'); - document.querySelector('#info h2').innerText = 'Edit a template'; - document.querySelector('#test-reminder > div:first-child').innerText = 'Test'; - } else if (type === types.static_reminder) { + title.innerText = 'Edit a template'; + test_text.innerText = 'Test'; + } else if (type === Types.static_reminder) { cl.add('show-edit-static-reminder'); - document.querySelector('#info h2').innerText = 'Edit a static reminder'; - document.querySelector('#test-reminder > div:first-child').innerText = 'Trigger'; + title.innerText = 'Edit a static reminder'; + test_text.innerText = 'Trigger'; } else return; }; - -// code run on load - -document.getElementById('add-reminder').addEventListener('click', e => { - if (document.getElementById('add-reminder').classList.contains('error')) - showWindow('notification'); - else - showAdd(types.reminder); -}); -document.getElementById('add-static-reminder').addEventListener('click', e => showAdd(types.static_reminder)); -document.getElementById('add-template').addEventListener('click', e => showAdd(types.template)); diff --git a/frontend/static/js/templates.js b/frontend/static/js/templates.js index aff1f18..5d65b64 100644 --- a/frontend/static/js/templates.js +++ b/frontend/static/js/templates.js @@ -26,9 +26,12 @@ function loadTemplateSelection() { function applyTemplate() { if (inputs.template.value === '0') { inputs.title.value = ''; - inputs.notification_service.querySelectorAll('input[type="checkbox"]:checked').forEach(c => c.checked = false) + inputs.notification_service.querySelectorAll( + 'input[type="checkbox"]:checked' + ).forEach(c => c.checked = false) inputs.text.value = ''; - toggleColor(true); + selectColor(colors[0]); + } else { fetch(`${url_prefix}/api/templates/${inputs.template.value}?api_key=${api_key}`) .then(response => { @@ -41,12 +44,7 @@ function applyTemplate() { c => c.checked = json.result.notification_services.includes(parseInt(c.dataset.id)) ); inputs.text.value = json.result.text; - if (json.result.color !== null) { - if (inputs.color.classList.contains('hidden')) - toggleColor(); - selectColor(json.result.color); - } else - toggleColor(true); + selectColor(json.result.color || colors[0]); }) .catch(e => { if (e === 401) diff --git a/frontend/static/js/window.js b/frontend/static/js/window.js index db2a160..016ba18 100644 --- a/frontend/static/js/window.js +++ b/frontend/static/js/window.js @@ -1,57 +1,54 @@ const colors = ["#3c3c3c", "#49191e", "#171a42", "#083b06", "#3b3506", "#300e40"]; const inputs = { - 'template': document.getElementById('template-selection'), - 'title': document.getElementById('title-input'), - 'time': document.getElementById('time-input'), - 'notification_service': document.querySelector('.notification-service-list'), - 'text': document.getElementById('text-input'), - 'color': document.querySelector('.color-list') + 'template': document.querySelector('#template-selection'), + 'color_toggle': document.querySelector('#color-toggle'), + 'color_button': document.querySelector('#color-button'), + 'color': document.querySelector('.color-list'), + 'title': document.querySelector('#title-input'), + 'time': document.querySelector('#time-input'), + 'notification_service': document.querySelector('.notification-service-selection'), + 'text': document.querySelector('#text-input'), }; const type_buttons = { - 'normal_button': document.getElementById('normal-button'), - 'repeat_button': document.getElementById('repeat-button'), - 'weekday_button': document.getElementById('weekday-button'), + 'normal_button': document.querySelector('#normal-button'), + 'repeat_button': document.querySelector('#repeat-button'), + 'weekday_button': document.querySelector('#weekday-button'), 'repeat_bar': document.querySelector('.repeat-bar'), - 'repeat_interval': document.getElementById('repeat-interval'), - 'repeat_quantity': document.getElementById('repeat-quantity'), + 'repeat_interval': document.querySelector('#repeat-interval'), + 'repeat_quantity': document.querySelector('#repeat-quantity'), 'weekday_bar': document.querySelector('.weekday-bar') }; -function loadColor() { - colors.forEach(color => { - const entry = document.createElement('button'); - entry.dataset.color = color; - entry.title = color; - entry.type = 'button'; - entry.style.setProperty('--color', color); - entry.addEventListener('click', e => selectColor(color)) - inputs.color.appendChild(entry); +function fillColors() { + colors.forEach((color, idx) => { + const entry_toggle = document.createElement('label'); + entry_toggle.title = color; + entry_toggle.style.setProperty('--color', color); + inputs.color.appendChild(entry_toggle); + + const entry = document.createElement('input'); + entry.type = 'radio'; + entry.name = 'color_selection'; + entry.value = color; + entry.checked = idx === 0; + entry.classList.add('hidden'); + entry.onchange = e => { + if (e.target === entry) + inputs.color_button.style.setProperty('--color', color); + }; + entry_toggle.appendChild(entry); + + if (idx === 0) + inputs.color_button.style.setProperty('--color', color); }); }; function selectColor(color_code) { - inputs.color.querySelector(`button[data-color="${color_code}"]`).dataset.selected = 'true'; - inputs.color.querySelectorAll(`button:not([data-color="${color_code}"])`).forEach(b => b.dataset.selected = 'false'); - return; -}; - -function toggleColor(hide=false) { - selectColor(colors[0]) - if (!hide) - inputs.color.classList.toggle('hidden'); - else - inputs.color.classList.add('hidden'); -}; - -function toggleNotificationService(hide=false) { - if (!hide) - inputs.notification_service.classList.toggle('hidden'); - else - inputs.notification_service.classList.add('hidden'); + inputs.color.querySelector(`label[title="${color_code}"]`).click(); }; function toggleNormal() { @@ -64,7 +61,8 @@ function toggleNormal() { type_buttons.repeat_interval.value = ''; type_buttons.weekday_bar.classList.add('hidden'); - type_buttons.weekday_bar.querySelectorAll('input[type="checkbox"]').forEach(el => el.checked = false); + type_buttons.weekday_bar.querySelectorAll('input[type="checkbox"]') + .forEach(el => el.checked = false); }; function toggleRepeated() { @@ -76,7 +74,8 @@ function toggleRepeated() { type_buttons.repeat_interval.setAttribute('required', ''); type_buttons.weekday_bar.classList.add('hidden'); - type_buttons.weekday_bar.querySelectorAll('input[type="checkbox"]').forEach(el => el.checked = false); + type_buttons.weekday_bar.querySelectorAll('input[type="checkbox"]') + .forEach(el => el.checked = false); }; function toggleWeekDay() { @@ -115,7 +114,7 @@ function testReminder() { }; const ns = [... - document.querySelectorAll('.notification-service-list input[type="checkbox"]:checked') + document.querySelectorAll('.notification-service-selection input[type="checkbox"]:checked') ].map(c => parseInt(c.dataset.id)) if (!ns.length) { input.classList.add('error-input'); @@ -168,16 +167,16 @@ function deleteInfo() { if (cl.contains('show-edit-reminder')) { // Delete reminder - fillReminders(); + fillLibrary(Types.reminder); } else if (cl.contains('show-edit-template')) { // Delete template - fillTemplates(); + fillLibrary(Types.template); loadTemplateSelection(); } else if (cl.contains('show-edit-static-reminder')) { // Delete static reminder - fillStaticReminders(); + fillLibrary(Types.static_reminder); }; - hideWindow(); + showWindow("home"); }) .catch(e => { if (e === 401) @@ -202,14 +201,13 @@ function submitInfo() { const data = { 'title': inputs.title.value, 'notification_services': [... - document.querySelectorAll('.notification-service-list input[type="checkbox"]:checked') + document.querySelectorAll('.notification-service-selection input[type="checkbox"]:checked') ].map(c => parseInt(c.dataset.id)), 'text': inputs.text.value, - 'color': null - }; - if (!inputs.color.classList.contains('hidden')) { - data['color'] = inputs.color.querySelector('button[data-selected="true"]').dataset.color; + 'color': inputs.color.querySelector('input:checked').value }; + if (data.color === colors[0]) + data.color = null; if (data.notification_services.length === 0) { inputs.notification_service.classList.add('error-input'); @@ -221,7 +219,10 @@ function submitInfo() { const cl = document.getElementById('info').classList; if (cl.contains('show-add-reminder')) { // Add reminder - data['time'] = (new Date(inputs.time.value) / 1000) + (new Date(inputs.time.value).getTimezoneOffset() * 60) + data['time'] = + (new Date(inputs.time.value) / 1000) + + (new Date(inputs.time.value).getTimezoneOffset() * 60); + if (type_buttons.repeat_button.dataset.selected === 'true') { data['repeat_quantity'] = type_buttons.repeat_quantity.value; data['repeat_interval'] = parseInt(type_buttons.repeat_interval.value) @@ -242,7 +243,7 @@ function submitInfo() { fetch_data.url = `${url_prefix}/api/reminders?api_key=${api_key}`; fetch_data.method = 'POST'; - fetch_data.call_back = fillReminders; + fetch_data.call_back = () => fillLibrary(Types.reminder); } else if (cl.contains('show-add-template')) { // Add template @@ -250,18 +251,21 @@ function submitInfo() { fetch_data.method = 'POST'; fetch_data.call_back = () => { loadTemplateSelection(); - fillTemplates(); + fillLibrary(Types.template); }; } else if (cl.contains('show-add-static-reminder')) { // Add static reminder fetch_data.url = `${url_prefix}/api/staticreminders?api_key=${api_key}`; fetch_data.method = 'POST'; - fetch_data.call_back = fillStaticReminders; + fetch_data.call_back = () => fillLibrary(Types.static_reminder); } else if (cl.contains('show-edit-reminder')) { // Edit reminder - data['time'] = (new Date(inputs.time.value) / 1000) + (new Date(inputs.time.value).getTimezoneOffset() * 60) + data['time'] = + (new Date(inputs.time.value) / 1000) + + (new Date(inputs.time.value).getTimezoneOffset() * 60); + if (type_buttons.repeat_button.dataset.selected === 'true') { data['repeat_quantity'] = type_buttons.repeat_quantity.value; data['repeat_interval'] = parseInt(type_buttons.repeat_interval.value) @@ -282,7 +286,7 @@ function submitInfo() { fetch_data.url = `${url_prefix}/api/reminders/${e_id}?api_key=${api_key}`; fetch_data.method = 'PUT'; - fetch_data.call_back = fillReminders; + fetch_data.call_back = () => fillLibrary(Types.reminder); } else if (cl.contains('show-edit-template')) { // Edit template @@ -290,14 +294,14 @@ function submitInfo() { fetch_data.method = 'PUT'; fetch_data.call_back = () => { loadTemplateSelection(); - fillTemplates(); + fillLibrary(Types.template); }; } else if (cl.contains('show-edit-static-reminder')) { // Edit a static reminder fetch_data.url = `${url_prefix}/api/staticreminders/${e_id}?api_key=${api_key}`; fetch_data.method = 'PUT'; - fetch_data.call_back = fillStaticReminders; + fetch_data.call_back = () => fillLibrary(Types.static_reminder); } else return; @@ -310,7 +314,7 @@ function submitInfo() { if (!response.ok) return Promise.reject(response.status); fetch_data.call_back() - hideWindow(); + showWindow("home"); }) .catch(e => { if (e === 401) { @@ -325,15 +329,13 @@ function submitInfo() { // code run on load -loadColor(); +fillColors(); -document.getElementById('template-selection').addEventListener('change', e => applyTemplate()); -document.getElementById('color-toggle').addEventListener('click', e => toggleColor()); -document.getElementById('toggle-notification-service-list').addEventListener('click', e => toggleNotificationService()); -document.getElementById('normal-button').addEventListener('click', e => toggleNormal()); -document.getElementById('repeat-button').addEventListener('click', e => toggleRepeated()); -document.getElementById('weekday-button').addEventListener('click', e => toggleWeekDay()); -document.getElementById('close-info').addEventListener('click', e => hideWindow()); -document.getElementById('delete-info').addEventListener('click', e => deleteInfo()); -document.getElementById('test-reminder').addEventListener('click', e => testReminder()); -document.getElementById('info-form').setAttribute('action', 'javascript:submitInfo();'); +document.querySelector('#template-selection').onchange = e => applyTemplate(); +document.querySelector('#normal-button').onclick = e => toggleNormal(); +document.querySelector('#repeat-button').onclick = e => toggleRepeated(); +document.querySelector('#weekday-button').onclick = e => toggleWeekDay(); +document.querySelector('#close-info').onclick = e => showWindow("home"); +document.querySelector('#delete-info').onclick = e => deleteInfo(); +document.querySelector('#test-reminder').onclick = e => testReminder(); +document.querySelector('#info-form').action = 'javascript:submitInfo();'; diff --git a/frontend/templates/admin.html b/frontend/templates/admin.html index fd00ba5..537bbfd 100644 --- a/frontend/templates/admin.html +++ b/frontend/templates/admin.html @@ -39,7 +39,7 @@

Authentication

-
+
@@ -86,24 +86,34 @@ -
+
+
+ - - - - - - + + + + + diff --git a/frontend/templates/login.html b/frontend/templates/login.html index 5639ab8..796fbb0 100644 --- a/frontend/templates/login.html +++ b/frontend/templates/login.html @@ -19,18 +19,25 @@
+ +

Login

+ - - +
+ + +
- - +
+ + +
- +
@@ -44,7 +51,7 @@ - + diff --git a/frontend/templates/page_not_found.html b/frontend/templates/page_not_found.html index 5f47268..f64223a 100644 --- a/frontend/templates/page_not_found.html +++ b/frontend/templates/page_not_found.html @@ -7,8 +7,8 @@ - - + + Page Not Found - MIND diff --git a/frontend/templates/reminders.html b/frontend/templates/reminders.html index 13e85af..2339e28 100644 --- a/frontend/templates/reminders.html +++ b/frontend/templates/reminders.html @@ -24,9 +24,56 @@ Reminders - MIND + +
User Actions
+ + + + + + + +
+ + + + + + + +
+
+
- +
@@ -79,9 +126,12 @@
- - - + + + + + +
@@ -106,251 +156,286 @@ - + +
-
- -
- - -
- - - - - - -