From 26ebd23bfd5d06455ebb3d55155691cde5124e28 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Thu, 21 Apr 2022 16:33:49 -0400 Subject: [PATCH] Add user actions to web app --- web/public/static/langs/bg.json | 2 +- web/public/static/langs/de.json | 2 +- web/public/static/langs/en.json | 4 +- web/public/static/langs/es.json | 2 +- web/public/static/langs/fr.json | 2 +- web/public/static/langs/id.json | 2 +- web/public/static/langs/ja.json | 2 +- web/public/static/langs/nb_NO.json | 2 +- web/public/static/langs/ru.json | 2 +- web/public/static/langs/tr.json | 2 +- web/src/app/SubscriptionManager.js | 13 ++++ web/src/app/utils.js | 12 ++++ web/src/components/Notifications.js | 97 ++++++++++++++++++++++++++--- 13 files changed, 126 insertions(+), 18 deletions(-) diff --git a/web/public/static/langs/bg.json b/web/public/static/langs/bg.json index 8df2053..a4ecbf6 100644 --- a/web/public/static/langs/bg.json +++ b/web/public/static/langs/bg.json @@ -56,7 +56,7 @@ "notifications_attachment_copy_url_button": "Копиране на адреса", "notifications_attachment_open_button": "Отваряне на прикачения файл", "notifications_attachment_link_expires": "препратката изтича на {{date}}", - "notifications_click_open_title": "Към {{url}}", + "notifications_actions_open_url_title": "Към {{url}}", "notifications_click_copy_url_button": "Копиране на препратка", "notifications_click_open_button": "Отваряне", "notifications_click_copy_url_title": "Копира препратката в междинната памет", diff --git a/web/public/static/langs/de.json b/web/public/static/langs/de.json index 3beca7f..2be4707 100644 --- a/web/public/static/langs/de.json +++ b/web/public/static/langs/de.json @@ -48,7 +48,7 @@ "notifications_attachment_open_button": "Anhang öffnen", "notifications_attachment_link_expired": "Download-Link ist abgelaufen", "notifications_click_copy_url_button": "Link kopieren", - "notifications_click_open_title": "Gehe zu {{url}}", + "notifications_actions_open_url_title": "Gehe zu {{url}}", "publish_dialog_other_features": "Andere Optionen:", "notifications_none_for_topic_description": "Um Benachrichtigungen an dieses Thema zu senden, PUTe/POSTe an die Themen-URL.", "notifications_no_subscriptions_title": "Anscheinend hast Du noch keine Themen abonniert.", diff --git a/web/public/static/langs/en.json b/web/public/static/langs/en.json index a27ff86..d668ce2 100644 --- a/web/public/static/langs/en.json +++ b/web/public/static/langs/en.json @@ -26,8 +26,10 @@ "notifications_attachment_link_expired": "download link expired", "notifications_click_copy_url_title": "Copy link URL to clipboard", "notifications_click_copy_url_button": "Copy link", - "notifications_click_open_title": "Go to {{url}}", "notifications_click_open_button": "Open link", + "notifications_actions_open_url_title": "Go to {{url}}", + "notifications_actions_not_supported": "Action not supported in web app", + "notifications_actions_http_request_title": "Send HTTP {{method}} to {{url}}", "notifications_none_for_topic_title": "You haven't received any notifications for this topic yet.", "notifications_none_for_topic_description": "To send notifications to this topic, simply PUT or POST to the topic URL.", "notifications_none_for_any_title": "You haven't received any notifications.", diff --git a/web/public/static/langs/es.json b/web/public/static/langs/es.json index c4baa83..1d8c62c 100644 --- a/web/public/static/langs/es.json +++ b/web/public/static/langs/es.json @@ -26,7 +26,7 @@ "notifications_attachment_link_expired": "el enlace de descarga ha expirado", "notifications_click_copy_url_title": "Copiar la URL del enlace en el portapapeles", "notifications_click_copy_url_button": "Copiar enlace", - "notifications_click_open_title": "Ir a {{url}}", + "notifications_actions_open_url_title": "Ir a {{url}}", "notifications_click_open_button": "Abrir enlace", "notifications_none_for_topic_title": "Aún no has recibido ninguna notificación en este tópico.", "notifications_none_for_topic_description": "Para enviar notificaciones a este tópico, simplemente realice un PUT o POST a la URL del tópico.", diff --git a/web/public/static/langs/fr.json b/web/public/static/langs/fr.json index b83cf86..bb559af 100644 --- a/web/public/static/langs/fr.json +++ b/web/public/static/langs/fr.json @@ -24,7 +24,7 @@ "notifications_click_copy_url_button": "Copier le lien", "notifications_click_open_button": "Ouvrir le lien", "notifications_none_for_topic_title": "Vous n'avez pas encore reçu de notifications pour ce sujet.", - "notifications_click_open_title": "Aller à {{url}}", + "notifications_actions_open_url_title": "Aller à {{url}}", "notifications_example": "Exemple", "notifications_loading": "Chargement des notifications…", "publish_dialog_progress_uploading": "Téléversement…", diff --git a/web/public/static/langs/id.json b/web/public/static/langs/id.json index 2b4c119..b9dfde1 100644 --- a/web/public/static/langs/id.json +++ b/web/public/static/langs/id.json @@ -29,7 +29,7 @@ "notifications_attachment_open_button": "Buka lampiran", "notifications_attachment_link_expires": "tautan kadaluwarsa {{date}}", "notifications_attachment_link_expired": "tautan unduhan kadaluwarsa", - "notifications_click_open_title": "Pergi ke {{url}}", + "notifications_actions_open_url_title": "Pergi ke {{url}}", "notifications_click_open_button": "Buka tautan", "publish_dialog_topic_placeholder": "Nama topik, mis. pemberitahuan_andi", "nav_button_publish_message": "Publikasikan notifikasi", diff --git a/web/public/static/langs/ja.json b/web/public/static/langs/ja.json index e456a65..e9d51cb 100644 --- a/web/public/static/langs/ja.json +++ b/web/public/static/langs/ja.json @@ -40,7 +40,7 @@ "notifications_attachment_copy_url_button": "URLをコピー", "notifications_attachment_open_title": "{{url}} に移動", "notifications_attachment_link_expired": "ダウンロードリンクは失効しました", - "notifications_click_open_title": "{{url}} に移動", + "notifications_actions_open_url_title": "{{url}} に移動", "notifications_attachment_copy_url_title": "添付URLをクリップボードにコピー", "notifications_attachment_open_button": "添付ファイルを開く", "notifications_click_copy_url_title": "リンクURLをクリップボードにコピー", diff --git a/web/public/static/langs/nb_NO.json b/web/public/static/langs/nb_NO.json index 84526e6..1dab51b 100644 --- a/web/public/static/langs/nb_NO.json +++ b/web/public/static/langs/nb_NO.json @@ -18,7 +18,7 @@ "notifications_attachment_open_title": "Gå til {{url}}", "notifications_attachment_link_expires": "lenken utløper {{date}}", "notifications_click_copy_url_title": "Kopier lenke-nettadresse til utklippstavlen", - "notifications_click_open_title": "Gå til {{url}}", + "notifications_actions_open_url_title": "Gå til {{url}}", "notifications_tags": "Etiketter", "notifications_attachment_link_expired": "nedlastingslenken har utløpt", "notifications_none_for_any_title": "Du har ikke mottatt noen merknader.", diff --git a/web/public/static/langs/ru.json b/web/public/static/langs/ru.json index 5f4c4e6..ed97047 100644 --- a/web/public/static/langs/ru.json +++ b/web/public/static/langs/ru.json @@ -59,7 +59,7 @@ "notifications_none_for_any_title": "Вы ещё не получали никаких уведомлений.", "alert_grant_title": "Уведомления отключены", "notifications_attachment_copy_url_title": "Скопировать URL-адрес вложения", - "notifications_click_open_title": "Перейти на {{url}}", + "notifications_actions_open_url_title": "Перейти на {{url}}", "notifications_tags": "Тэги", "notifications_attachment_link_expires": "срок действия ссылки истекает {{date}}", "notifications_click_copy_url_title": "Скопировать URL-адрес ссылки", diff --git a/web/public/static/langs/tr.json b/web/public/static/langs/tr.json index 2425005..254213e 100644 --- a/web/public/static/langs/tr.json +++ b/web/public/static/langs/tr.json @@ -71,7 +71,7 @@ "notifications_none_for_any_title": "Herhangi bir bildirim almadınız.", "notifications_attachment_link_expired": "indirme bağlantısının süresi doldu", "notifications_click_copy_url_button": "Bağlantıyı kopyala", - "notifications_click_open_title": "{{url}} adresine git", + "notifications_actions_open_url_title": "{{url}} adresine git", "notifications_click_open_button": "Bağlantıyı aç", "notifications_no_subscriptions_description": "Bir konu oluşturmak veya bir konuya abone olmak için \"{{linktext}}\" bağlantısına tıklayın. Bundan sonra PUT veya POST yoluyla mesaj gönderebilirsiniz ve buradan bildirimler alırsınız.", "notifications_example": "Örnek", diff --git a/web/src/app/SubscriptionManager.js b/web/src/app/SubscriptionManager.js index b1e4449..b485383 100644 --- a/web/src/app/SubscriptionManager.js +++ b/web/src/app/SubscriptionManager.js @@ -92,6 +92,19 @@ class SubscriptionManager { }); } + async updateNotification(notification) { + const exists = await db.notifications.get(notification.id); + if (!exists) { + return false; + } + try { + await db.notifications.put({ ...notification }); + } catch (e) { + console.error(`[SubscriptionManager] Error updating notification`, e); + } + return true; + } + async deleteNotification(notificationId) { await db.notifications.delete(notificationId); } diff --git a/web/src/app/utils.js b/web/src/app/utils.js index adba2e9..aaf8911 100644 --- a/web/src/app/utils.js +++ b/web/src/app/utils.js @@ -105,6 +105,18 @@ export const encodeBase64Url = (s) => { return Base64.encodeURI(s); } +export const maybeAppendActionErrors = (message, notification) => { + const actionErrors = (notification.actions ?? []) + .map(action => action.error) + .filter(action => !!action) + .join("\n") + if (actionErrors.length === 0) { + return message; + } else { + return `${message}\n\n${actionErrors}`; + } +} + export const shuffle = (arr) => { let j, x; for (let index = arr.length - 1; index > 0; index--) { diff --git a/web/src/components/Notifications.js b/web/src/components/Notifications.js index a412e29..3a95219 100644 --- a/web/src/components/Notifications.js +++ b/web/src/components/Notifications.js @@ -19,7 +19,7 @@ import { formatBytes, formatMessage, formatShortDateTime, - formatTitle, + formatTitle, maybeAppendActionErrors, openUrl, shortUrl, topicShortUrl, @@ -138,9 +138,10 @@ const NotificationItem = (props) => { props.onShowSnack(); }; const expired = attachment && attachment.expires && attachment.expires < Date.now()/1000; - const showAttachmentActions = attachment && !expired; - const showClickAction = notification.click; - const showActions = showAttachmentActions || showClickAction; + const hasAttachmentActions = attachment && !expired; + const hasClickAction = notification.click; + const hasUserActions = notification.actions && notification.actions.length > 0; + const showActions = hasAttachmentActions || hasClickAction || hasUserActions; return ( @@ -161,13 +162,15 @@ const NotificationItem = (props) => { } {notification.title && {formatTitle(notification)}} - {autolink(formatMessage(notification))} + + {autolink(maybeAppendActionErrors(formatMessage(notification), notification))} + {attachment && } {tags && {t("notifications_tags")}: {tags}} {showActions && - {showAttachmentActions && <> + {hasAttachmentActions && <> @@ -175,14 +178,15 @@ const NotificationItem = (props) => { } - {showClickAction && <> + {hasClickAction && <> - + } + {hasUserActions && } } ); @@ -329,6 +333,83 @@ const Image = (props) => { ); } +const UserActions = (props) => { + return ( + <>{props.notification.actions.map(action => + )} + ); +}; + +const UserAction = (props) => { + const { t } = useTranslation(); + const notification = props.notification; + const action = props.action; + if (action.action === "broadcast") { + return ( + + + + ); + } else if (action.action === "view") { + return ( + + + + ); + } else if (action.action === "http") { + const method = action.method ?? "POST"; + const label = action.label + (ACTION_LABEL_SUFFIX[action.progress ?? 0] ?? ""); + return ( + + + + ); + } + return null; // Others +}; + +const performHttpAction = async (notification, action) => { + console.log(`[Notifications] Performing HTTP user action`, action); + try { + updateActionStatus(notification, action, ACTION_PROGRESS_ONGOING, null); + const response = await fetch(action.url, { + method: action.method ?? "POST", + headers: action.headers ?? {}, + body: action.body ?? "" + }); + console.log(`[Notifications] HTTP user action response`, response); + const success = response.status >= 200 && response.status <= 299; + if (success) { + updateActionStatus(notification, action, ACTION_PROGRESS_SUCCESS, null); + } else { + updateActionStatus(notification, action, ACTION_PROGRESS_FAILED, `${action.label}: Unexpected response HTTP ${response.status}`); + } + } catch (e) { + console.log(`[Notifications] HTTP action failed`, e); + updateActionStatus(notification, action, ACTION_PROGRESS_FAILED, `${action.label}: ${e} Check developer console for details.`); + } +}; + +const updateActionStatus = (notification, action, progress, error) => { + notification.actions = notification.actions.map(a => { + if (a.id !== action.id) { + return a; + } + return { ...a, progress: progress, error: error }; + }); + subscriptionManager.updateNotification(notification); +} + +const ACTION_PROGRESS_ONGOING = 1; +const ACTION_PROGRESS_SUCCESS = 2; +const ACTION_PROGRESS_FAILED = 3; + +const ACTION_LABEL_SUFFIX = { + [ACTION_PROGRESS_ONGOING]: " …", + [ACTION_PROGRESS_SUCCESS]: " ✔", + [ACTION_PROGRESS_FAILED]: " ❌" +}; + const NoNotifications = (props) => { const { t } = useTranslation(); const shortUrl = topicShortUrl(props.subscription.baseUrl, props.subscription.topic);