From a56f84f65944cd14ed18c66c77999f324a38e5da Mon Sep 17 00:00:00 2001 From: jude Date: Sat, 19 Mar 2022 23:47:40 +0000 Subject: [PATCH] timezone help. moved javascript to separate file --- web/src/lib.rs | 1 + web/src/routes/mod.rs | 9 +- web/static/js/main.js | 588 ++++++++++++++++++++++++ web/templates/dashboard.html.tera | 626 +------------------------- web/templates/help_timezone.html.tera | 47 ++ 5 files changed, 652 insertions(+), 619 deletions(-) create mode 100644 web/static/js/main.js create mode 100644 web/templates/help_timezone.html.tera diff --git a/web/src/lib.rs b/web/src/lib.rs index 07510aa..28fa07d 100644 --- a/web/src/lib.rs +++ b/web/src/lib.rs @@ -82,6 +82,7 @@ pub async fn initialize( routes::privacy, routes::terms, routes::help, + routes::help_timezone, routes::return_to_same_site ], ) diff --git a/web/src/routes/mod.rs b/web/src/routes/mod.rs index c1560e8..33737d8 100644 --- a/web/src/routes/mod.rs +++ b/web/src/routes/mod.rs @@ -1,9 +1,10 @@ pub mod dashboard; pub mod login; +use std::collections::HashMap; + use rocket::request::FlashMessage; use rocket_dyn_templates::Template; -use std::collections::HashMap; #[get("/")] pub async fn index(flash: Option>) -> Template { @@ -49,3 +50,9 @@ pub async fn help() -> Template { let map: HashMap<&str, String> = HashMap::new(); Template::render("help", &map) } + +#[get("/help/timezone")] +pub async fn help_timezone() -> Template { + let map: HashMap<&str, String> = HashMap::new(); + Template::render("help_timezone", &map) +} diff --git a/web/static/js/main.js b/web/static/js/main.js new file mode 100644 index 0000000..98408d5 --- /dev/null +++ b/web/static/js/main.js @@ -0,0 +1,588 @@ +let colorPicker = new iro.ColorPicker('#colorpicker'); +let $discordFrame; +const $loader = document.querySelector('#loader'); +const $colorPickerModal = document.querySelector('div#pickColorModal'); +const $colorPickerInput = $colorPickerModal.querySelector('input'); + +let timezone = luxon.DateTime.now().zone.name; +const browserTimezone = luxon.DateTime.now().zone.name; +let botTimezone = 'UTC'; + +let channels; +let roles; + +function colorToInt(r, g, b) { + return (r << 16) + (g << 8) + (b); +} + +function resize_textareas() { + document.querySelectorAll('textarea.autoresize').forEach((element) => { + element.style.height = ""; + element.style.height = element.scrollHeight + 3 + "px"; + + element.addEventListener('input', () => { + element.style.height = ""; + element.style.height = element.scrollHeight + 3 + "px"; + }); + }); +} + +function switch_pane(selector) { + document.querySelectorAll('aside a').forEach((el) => { + el.classList.remove('is-active'); + }); + document.querySelectorAll('div.is-main-content > section').forEach((el) => { + el.classList.add('is-hidden'); + }); + + document.getElementById(selector).classList.remove('is-hidden'); + + resize_textareas(); +} + +function update_select(sel) { + if (sel.selectedOptions[0].dataset['webhookAvatar']) { + sel + .closest('div.reminderContent') + .querySelector('img.discord-avatar') + .src = sel.selectedOptions[0].dataset['webhookAvatar']; + } else { + sel + .closest('div.reminderContent') + .querySelector('img.discord-avatar') + .src = ''; + } + if (sel.selectedOptions[0].dataset['webhookName']) { + sel + .closest('div.reminderContent') + .querySelector('input.discord-username') + .value = sel.selectedOptions[0].dataset['webhookName']; + } else { + sel + .closest('div.reminderContent') + .querySelector('input.discord-username') + .value = ''; + } +} + +function reset_guild_pane() { + document.querySelectorAll('select.channel-selector option').forEach((opt) => opt.remove()); +} + +function fetch_roles(guild_id) { + fetch(`/dashboard/api/guild/${guild_id}/roles`) + .then(response => response.json()) + .then(data => { + if (data.error) { + show_error(data.error); + } else { + roles = data; + } + }) +} + +async function fetch_reminders(guild_id) { + // fetch dm reminders instead + if (guild_id === undefined) { + const $reminderBox = document.querySelector('div#personalReminders'); + + // reset div contents + $reminderBox.innerHTML = ''; + + // fetch reminders + await fetch('dashboard/api/user/reminders') + .then(response => response.json()) + .then(data => { + if (data.error) { + show_error(data.error); + } else { + const $template = document.querySelector('template#personalReminder'); + + for (let reminder of data) { + let newFrame = $template.content.cloneNode(true); + + for (let prop in reminder) { + if (reminder.hasOwnProperty(prop) && reminder[prop] !== null) { + let $input = newFrame.querySelector(`*[name="${prop}"]`); + let $image = newFrame.querySelector(`img.${prop}`); + + if ($input !== null) { + $input.value = reminder[prop]; + } else if ($image !== null) { + $image.src = reminder[prop]; + } + } + } + + $reminderBox.append(newFrame); + } + } + }); + } else { + const $reminderBox = document.querySelector('div#guildReminders'); + + // reset div contents + $reminderBox.innerHTML = ''; + + // fetch reminders + await fetch(`dashboard/api/guild/${guild_id}/reminders`) + .then(response => response.json()) + .then(data => { + if (data.error) { + show_error(data.error); + } else { + console.log(data); + + const $template = document.querySelector('template#guildReminder'); + + for (let reminder of data) { + let newFrame = $template.content.cloneNode(true); + + // populate channels + set_channels(newFrame.querySelector('select.channel-selector')) + + // populate majority of items + for (let prop in reminder) { + if (reminder.hasOwnProperty(prop) && reminder[prop] !== null) { + let $input = newFrame.querySelector(`*[name="${prop}"]`); + let $image = newFrame.querySelector(`img.${prop}`); + + if ($input !== null) { + $input.value = reminder[prop]; + } else if ($image !== null) { + $image.src = reminder[prop]; + } + } + } + + let timeInput = newFrame.querySelector('input[name="time"]'); + let localTime = luxon.DateTime.fromISO(reminder["utc_time"]).setZone(timezone); + timeInput.value = localTime.toFormat("yyyy-LL-dd'T'HH:mm:ss"); + + $reminderBox.appendChild(newFrame); + } + } + }); + } + + register_interval_hide(); +} + +function show_error(error) { + document.getElementById('errors').querySelector('span.error-message').textContent = error; + document.getElementById('errors').classList.add('is-active'); + + window.setTimeout(() => { + document.getElementById('errors').classList.remove('is-active'); + }, 5000); +} + +function update_times() { + document.querySelectorAll('span.set-timezone').forEach((element) => { + element.textContent = timezone; + }); + document.querySelectorAll('span.set-time').forEach((element) => { + element.textContent = luxon.DateTime.now().setZone(timezone).toFormat('HH:mm'); + }); + document.querySelectorAll('span.browser-timezone').forEach((element) => { + element.textContent = browserTimezone; + }); + document.querySelectorAll('span.browser-time').forEach((element) => { + element.textContent = luxon.DateTime.now().toFormat('HH:mm'); + }); + document.querySelectorAll('span.bot-timezone').forEach((element) => { + element.textContent = botTimezone; + }); + document.querySelectorAll('span.bot-time').forEach((element) => { + element.textContent = luxon.DateTime.now().setZone(botTimezone).toFormat('HH:mm'); + }); +} + +window.setInterval(() => { + update_times(); +}, 30000) + +document.getElementById('set-bot-timezone').addEventListener('click', () => { + timezone = botTimezone; + update_times(); +}); +document.getElementById('set-browser-timezone').addEventListener('click', () => { + timezone = browserTimezone; + update_times(); +}); +document.getElementById('update-bot-timezone').addEventListener('click', () => { + timezone = browserTimezone; + fetch('/dashboard/api/user', { + method: 'PATCH', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({timezone: timezone}) + }) + .then(response => response.json()) + .then(data => { + if (data.error) { + show_error(data.error) + } else { + botTimezone = browserTimezone; + update_times(); + } + }); +}); + +$colorPickerInput.value = colorPicker.color.hexString; + +$colorPickerInput.addEventListener('input', () => { + if (/^#[0-9a-fA-F]{6}$/.test($colorPickerInput.value) === true) { + colorPicker.color.hexString = $colorPickerInput.value; + } +}); + +colorPicker.on('color:change', function (color) { + $colorPickerInput.value = color.hexString; +}); + +document.querySelectorAll('.discord-embed').forEach((element) => { + element.addEventListener('click', (e) => { + if (e.offsetX < parseInt(window.getComputedStyle(element).borderLeftWidth)) { + $discordFrame = element; + $colorPickerModal.classList.toggle('is-active'); + colorPicker.color.rgbString = window.getComputedStyle($discordFrame).borderLeftColor; + } + }) +}); + +document.querySelectorAll('.set-color').forEach((element) => { + element.addEventListener('click', (e) => { + e.preventDefault(); + + $discordFrame = element.closest('div.reminderContent').querySelector('div.discord-embed'); + $colorPickerModal.classList.toggle('is-active'); + colorPicker.color.rgbString = window.getComputedStyle($discordFrame).borderLeftColor; + }) +}); + +$colorPickerModal.querySelector('button.is-success').addEventListener('click', () => { + $discordFrame.style.borderLeftColor = colorPicker.color.rgbString; + + $colorPickerModal.classList.remove('is-active') +}); + +document.querySelectorAll('a.show-modal').forEach((element) => { + element.addEventListener('click', (e) => { + e.preventDefault(); + document.getElementById(element.dataset['modal']).classList.toggle('is-active'); + }) +}) + +document.addEventListener('DOMContentLoaded', () => { + $loader.classList.remove('is-hidden'); + + document.querySelectorAll('.navbar-burger').forEach(el => { + el.addEventListener('click', () => { + const target = el.dataset.target; + const $target = document.getElementById(target); + + el.classList.toggle('is-active'); + $target.classList.toggle('is-active'); + }); + }); + + fetch('/dashboard/api/user') + .then(response => response.json()) + .then(data => { + if (data.error) { + show_error(data.error); + } else { + document.querySelectorAll('a.switch-pane').forEach((element) => { + element.innerHTML = element.innerHTML.replace('%username%', data.name); + + element.addEventListener('click', (e) => { + e.preventDefault(); + + switch_pane(element.dataset['pane']); + + element.classList.add('is-active'); + + document.querySelectorAll('p.pageTitle').forEach((el) => { + el.textContent = 'Your Reminders'; + }); + }); + }); + + if (data.timezone !== null) { + botTimezone = data.timezone; + } + + update_times(); + } + }); + + fetch('/dashboard/api/user/guilds') + .then(response => response.json()) + .then(data => { + if (data.error) { + show_error(data.error); + } else { + const $template = document.getElementById('guildListEntry'); + + for (let guild of data) { + document.querySelectorAll('.guildList').forEach((element) => { + const $clone = $template.content.cloneNode(true); + const $anchor = $clone.querySelector('a'); + + $anchor.innerHTML = $clone.querySelector('a').innerHTML.replace('%guildname%', guild.name); + $anchor.dataset['guild'] = guild.id; + $anchor.dataset['name'] = guild.name; + + $anchor.addEventListener('click', async (e) => { + e.preventDefault(); + + $loader.classList.remove('is-hidden'); + + switch_pane($anchor.dataset['pane']); + + reset_guild_pane(); + + fetch_roles($anchor.dataset['guild']); + + await fetch(`/dashboard/api/guild/${$anchor.dataset['guild']}/channels`) + .then(response => response.json()) + .then(data => { + if (data.error) { + if (data.error === "Bot not in guild") { + switch_pane('guild-error'); + } else { + show_error(data.error); + } + } else { + channels = data; + + document.querySelectorAll('select.channel-selector').forEach(set_channels); + } + }); + + fetch_reminders($anchor.dataset['guild']); + + document.querySelectorAll('p.pageTitle').forEach((el) => { + el.textContent = $anchor.dataset['name'] + ' Reminders'; + }); + document.querySelectorAll('select.channel-selector').forEach((el) => { + el.addEventListener('change', (e) => { + update_select(e.target); + }) + }); + $anchor.classList.add('is-active'); + resize_textareas(); + + $loader.classList.add('is-hidden'); + }); + + element.append($clone); + }); + } + } + }); + + $loader.classList.add('is-hidden'); +}); + +function set_channels(element) { + for (let channel of channels) { + let newOption = document.createElement('option'); + + newOption.value = channel.id; + newOption.textContent = channel.name; + if (channel.webhook_avatar !== null) { + newOption.dataset['webhookAvatar'] = channel.webhook_avatar; + } + if (channel.webhook_name !== null) { + newOption.dataset['webhookName'] = channel.webhook_name; + } + + element.appendChild(newOption); + } + + update_select(element); +} + +let $createReminder = document.querySelector('#reminderCreator'); + +$createReminder.querySelector('button#createReminder').addEventListener('click', () => { + // create reminder object + let seconds = parseInt($createReminder.querySelector('input[name="interval_seconds"]').value) || null; + let months = parseInt($createReminder.querySelector('input[name="interval_months"]').value) || null; + + let rgb_color = window.getComputedStyle($createReminder.querySelector('div.discord-embed')).borderLeftColor; + let rgb = rgb_color.match(/\d+/g); + let color = colorToInt(parseInt(rgb[0]), parseInt(rgb[1]), parseInt(rgb[2])); + + let utc_time = luxon.DateTime.fromISO($createReminder.querySelector('input[name="time"]').value).setZone('UTC'); + + let reminder = { + avatar: $createReminder.querySelector('img.discord-avatar').src, + channel: $createReminder.querySelector('select.channel-selector').value, + content: $createReminder.querySelector('textarea#messageContent').value, + embed_author_url: $createReminder.querySelector('img.embed_author_url').src, + embed_author: $createReminder.querySelector('textarea#embedAuthor').value, + embed_color: color, + embed_description: $createReminder.querySelector('textarea#embedDescription').value, + embed_footer: $createReminder.querySelector('textarea#embedFooter').value, + embed_footer_url: $createReminder.querySelector('img.embed_footer_url').src, + embed_image_url: $createReminder.querySelector('img.embed_image_url').src, + embed_thumbnail_url: $createReminder.querySelector('img.embed_thumbnail_url').src, + embed_title: $createReminder.querySelector('textarea#embedTitle').value, + enabled: true, + expires: null, + interval_seconds: seconds, + interval_months: months, + name: $createReminder.querySelector('input[name="name"]').value, + pin: $createReminder.querySelector('input[name="pin"]').checked, + restartable: false, + tts: $createReminder.querySelector('input[name="tts"]').checked, + username: $createReminder.querySelector('input#reminderUsername').value, + utc_time: utc_time.toFormat("yyyy-LL-dd'T'HH:mm:ss") + } + + // send to server + let guild = document.querySelector('.guildList a.is-active').dataset['guild']; + + fetch(`/dashboard/api/guild/${guild}/reminders`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(reminder) + }).then(response => response.json()).then(data => console.log(data)) + + // process response + + // reset inputs +}); + +document.querySelectorAll('textarea.autoresize').forEach((element) => { + element.addEventListener('input', () => { + element.style.height = ""; + element.style.height = element.scrollHeight + 3 + "px"; + }); +}); + +let $img; +const $urlModal = document.querySelector('div#addImageModal'); +const $urlInput = $urlModal.querySelector('input'); + +$urlModal.querySelector('button.is-success').addEventListener('click', () => { + $img.src = $urlInput.value; + + $urlInput.value = ''; + $urlModal.classList.remove('is-active') +}); + +document.querySelectorAll('button.close-modal').forEach((element) => { + element.addEventListener('click', () => { + let $modal = element.closest('div.modal'); + + $urlInput.value = ''; + + $modal.classList.remove('is-active') + }); +}); + +document.querySelectorAll('.customizable').forEach((element) => { + element.querySelector('a').addEventListener('click', (e) => { + e.preventDefault(); + + $img = element.querySelector('img'); + + $urlModal.classList.toggle('is-active') + }); +}); + +document.querySelectorAll('a.icon-toggle').forEach((element) => { + element.addEventListener('click', (e) => { + e.preventDefault(); + + element.classList.toggle('is-active'); + }) +}); + +let $showButton = document.querySelector('button#showReminderCreator'); + +$showButton.addEventListener('click', () => { + $showButton.querySelector('span.icon i').classList.toggle('fa-chevron-right'); + $showButton.querySelector('span.icon i').classList.toggle('fa-chevron-down'); + document.querySelector('div#reminderCreator').classList.toggle('is-hidden'); +}); + +function register_interval_hide() { + let $showInterval = document.querySelectorAll('a.intervalLabel'); + + $showInterval.forEach((element) => { + element.addEventListener('click', () => { + element.querySelector('i').classList.toggle('fa-chevron-right'); + element.querySelector('i').classList.toggle('fa-chevron-down'); + element.nextElementSibling.classList.toggle('is-hidden'); + }); + }); +} + +const fileInput = document.querySelectorAll('input[type=file]'); + +fileInput.forEach((element) => { + element.addEventListener('change', () => { + if (element.files.length > 0) { + const fileName = element.parentElement.querySelector('.file-label'); + fileName.textContent = element.files[0].name; + } + }) +}); + +function check_embed_fields() { + document.querySelectorAll('.discord-field-title').forEach((element) => { + const $template = document.querySelector('template#embedFieldTemplate'); + const $complement = element.parentElement.querySelector('.discord-field-value'); + + // when the user clicks out of the field title and if the field title/value are empty, remove the field + element.addEventListener('blur', () => { + if (element.value === '' && $complement.value === '' && element.parentElement.nextElementSibling !== null) { + element.parentElement.remove(); + } + }); + + $complement.addEventListener('blur', () => { + if (element.value === '' && $complement.value === '' && element.parentElement.nextElementSibling !== null) { + element.parentElement.remove(); + } + }); + + // when the user inputs into the end field, create a new field after it + element.addEventListener('input', () => { + if (element.value !== '' && $complement.value !== '' && element.parentElement.nextElementSibling === null) { + const $clone = $template.content.cloneNode(true); + element.parentElement.parentElement.append($clone); + } + }); + + $complement.addEventListener('input', () => { + if (element.value !== '' && $complement.value !== '' && element.parentElement.nextElementSibling === null) { + const $clone = $template.content.cloneNode(true); + element.parentElement.parentElement.append($clone); + } + }); + }); +} + +document.addEventListener('DOMNodeInserted', () => { + document.querySelectorAll('div.mobile-sidebar a').forEach((element) => { + element.addEventListener('click', (e) => { + document.getElementById('mobileSidebar').classList.remove('is-active'); + document.querySelectorAll('.navbar-burger').forEach((el) => { + el.classList.remove('is-active'); + }); + }); + }); + + check_embed_fields(); + resize_textareas(); +}); diff --git a/web/templates/dashboard.html.tera b/web/templates/dashboard.html.tera index 3bef65f..73a6ccc 100644 --- a/web/templates/dashboard.html.tera +++ b/web/templates/dashboard.html.tera @@ -117,7 +117,7 @@