Add user actions to web app
parent
12d347976c
commit
26ebd23bfd
|
@ -56,7 +56,7 @@
|
||||||
"notifications_attachment_copy_url_button": "Копиране на адреса",
|
"notifications_attachment_copy_url_button": "Копиране на адреса",
|
||||||
"notifications_attachment_open_button": "Отваряне на прикачения файл",
|
"notifications_attachment_open_button": "Отваряне на прикачения файл",
|
||||||
"notifications_attachment_link_expires": "препратката изтича на {{date}}",
|
"notifications_attachment_link_expires": "препратката изтича на {{date}}",
|
||||||
"notifications_click_open_title": "Към {{url}}",
|
"notifications_actions_open_url_title": "Към {{url}}",
|
||||||
"notifications_click_copy_url_button": "Копиране на препратка",
|
"notifications_click_copy_url_button": "Копиране на препратка",
|
||||||
"notifications_click_open_button": "Отваряне",
|
"notifications_click_open_button": "Отваряне",
|
||||||
"notifications_click_copy_url_title": "Копира препратката в междинната памет",
|
"notifications_click_copy_url_title": "Копира препратката в междинната памет",
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
"notifications_attachment_open_button": "Anhang öffnen",
|
"notifications_attachment_open_button": "Anhang öffnen",
|
||||||
"notifications_attachment_link_expired": "Download-Link ist abgelaufen",
|
"notifications_attachment_link_expired": "Download-Link ist abgelaufen",
|
||||||
"notifications_click_copy_url_button": "Link kopieren",
|
"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:",
|
"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_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.",
|
"notifications_no_subscriptions_title": "Anscheinend hast Du noch keine Themen abonniert.",
|
||||||
|
|
|
@ -26,8 +26,10 @@
|
||||||
"notifications_attachment_link_expired": "download link expired",
|
"notifications_attachment_link_expired": "download link expired",
|
||||||
"notifications_click_copy_url_title": "Copy link URL to clipboard",
|
"notifications_click_copy_url_title": "Copy link URL to clipboard",
|
||||||
"notifications_click_copy_url_button": "Copy link",
|
"notifications_click_copy_url_button": "Copy link",
|
||||||
"notifications_click_open_title": "Go to {{url}}",
|
|
||||||
"notifications_click_open_button": "Open link",
|
"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_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_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.",
|
"notifications_none_for_any_title": "You haven't received any notifications.",
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
"notifications_attachment_link_expired": "el enlace de descarga ha expirado",
|
"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_title": "Copiar la URL del enlace en el portapapeles",
|
||||||
"notifications_click_copy_url_button": "Copiar enlace",
|
"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_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_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.",
|
"notifications_none_for_topic_description": "Para enviar notificaciones a este tópico, simplemente realice un PUT o POST a la URL del tópico.",
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"notifications_click_copy_url_button": "Copier le lien",
|
"notifications_click_copy_url_button": "Copier le lien",
|
||||||
"notifications_click_open_button": "Ouvrir 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_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_example": "Exemple",
|
||||||
"notifications_loading": "Chargement des notifications…",
|
"notifications_loading": "Chargement des notifications…",
|
||||||
"publish_dialog_progress_uploading": "Téléversement…",
|
"publish_dialog_progress_uploading": "Téléversement…",
|
||||||
|
|
|
@ -29,7 +29,7 @@
|
||||||
"notifications_attachment_open_button": "Buka lampiran",
|
"notifications_attachment_open_button": "Buka lampiran",
|
||||||
"notifications_attachment_link_expires": "tautan kadaluwarsa {{date}}",
|
"notifications_attachment_link_expires": "tautan kadaluwarsa {{date}}",
|
||||||
"notifications_attachment_link_expired": "tautan unduhan kadaluwarsa",
|
"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",
|
"notifications_click_open_button": "Buka tautan",
|
||||||
"publish_dialog_topic_placeholder": "Nama topik, mis. pemberitahuan_andi",
|
"publish_dialog_topic_placeholder": "Nama topik, mis. pemberitahuan_andi",
|
||||||
"nav_button_publish_message": "Publikasikan notifikasi",
|
"nav_button_publish_message": "Publikasikan notifikasi",
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
"notifications_attachment_copy_url_button": "URLをコピー",
|
"notifications_attachment_copy_url_button": "URLをコピー",
|
||||||
"notifications_attachment_open_title": "{{url}} に移動",
|
"notifications_attachment_open_title": "{{url}} に移動",
|
||||||
"notifications_attachment_link_expired": "ダウンロードリンクは失効しました",
|
"notifications_attachment_link_expired": "ダウンロードリンクは失効しました",
|
||||||
"notifications_click_open_title": "{{url}} に移動",
|
"notifications_actions_open_url_title": "{{url}} に移動",
|
||||||
"notifications_attachment_copy_url_title": "添付URLをクリップボードにコピー",
|
"notifications_attachment_copy_url_title": "添付URLをクリップボードにコピー",
|
||||||
"notifications_attachment_open_button": "添付ファイルを開く",
|
"notifications_attachment_open_button": "添付ファイルを開く",
|
||||||
"notifications_click_copy_url_title": "リンクURLをクリップボードにコピー",
|
"notifications_click_copy_url_title": "リンクURLをクリップボードにコピー",
|
||||||
|
|
|
@ -18,7 +18,7 @@
|
||||||
"notifications_attachment_open_title": "Gå til {{url}}",
|
"notifications_attachment_open_title": "Gå til {{url}}",
|
||||||
"notifications_attachment_link_expires": "lenken utløper {{date}}",
|
"notifications_attachment_link_expires": "lenken utløper {{date}}",
|
||||||
"notifications_click_copy_url_title": "Kopier lenke-nettadresse til utklippstavlen",
|
"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_tags": "Etiketter",
|
||||||
"notifications_attachment_link_expired": "nedlastingslenken har utløpt",
|
"notifications_attachment_link_expired": "nedlastingslenken har utløpt",
|
||||||
"notifications_none_for_any_title": "Du har ikke mottatt noen merknader.",
|
"notifications_none_for_any_title": "Du har ikke mottatt noen merknader.",
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
"notifications_none_for_any_title": "Вы ещё не получали никаких уведомлений.",
|
"notifications_none_for_any_title": "Вы ещё не получали никаких уведомлений.",
|
||||||
"alert_grant_title": "Уведомления отключены",
|
"alert_grant_title": "Уведомления отключены",
|
||||||
"notifications_attachment_copy_url_title": "Скопировать URL-адрес вложения",
|
"notifications_attachment_copy_url_title": "Скопировать URL-адрес вложения",
|
||||||
"notifications_click_open_title": "Перейти на {{url}}",
|
"notifications_actions_open_url_title": "Перейти на {{url}}",
|
||||||
"notifications_tags": "Тэги",
|
"notifications_tags": "Тэги",
|
||||||
"notifications_attachment_link_expires": "срок действия ссылки истекает {{date}}",
|
"notifications_attachment_link_expires": "срок действия ссылки истекает {{date}}",
|
||||||
"notifications_click_copy_url_title": "Скопировать URL-адрес ссылки",
|
"notifications_click_copy_url_title": "Скопировать URL-адрес ссылки",
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
"notifications_none_for_any_title": "Herhangi bir bildirim almadınız.",
|
"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_attachment_link_expired": "indirme bağlantısının süresi doldu",
|
||||||
"notifications_click_copy_url_button": "Bağlantıyı kopyala",
|
"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_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_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",
|
"notifications_example": "Örnek",
|
||||||
|
|
|
@ -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) {
|
async deleteNotification(notificationId) {
|
||||||
await db.notifications.delete(notificationId);
|
await db.notifications.delete(notificationId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,6 +105,18 @@ export const encodeBase64Url = (s) => {
|
||||||
return Base64.encodeURI(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) => {
|
export const shuffle = (arr) => {
|
||||||
let j, x;
|
let j, x;
|
||||||
for (let index = arr.length - 1; index > 0; index--) {
|
for (let index = arr.length - 1; index > 0; index--) {
|
||||||
|
|
|
@ -19,7 +19,7 @@ import {
|
||||||
formatBytes,
|
formatBytes,
|
||||||
formatMessage,
|
formatMessage,
|
||||||
formatShortDateTime,
|
formatShortDateTime,
|
||||||
formatTitle,
|
formatTitle, maybeAppendActionErrors,
|
||||||
openUrl,
|
openUrl,
|
||||||
shortUrl,
|
shortUrl,
|
||||||
topicShortUrl,
|
topicShortUrl,
|
||||||
|
@ -138,9 +138,10 @@ const NotificationItem = (props) => {
|
||||||
props.onShowSnack();
|
props.onShowSnack();
|
||||||
};
|
};
|
||||||
const expired = attachment && attachment.expires && attachment.expires < Date.now()/1000;
|
const expired = attachment && attachment.expires && attachment.expires < Date.now()/1000;
|
||||||
const showAttachmentActions = attachment && !expired;
|
const hasAttachmentActions = attachment && !expired;
|
||||||
const showClickAction = notification.click;
|
const hasClickAction = notification.click;
|
||||||
const showActions = showAttachmentActions || showClickAction;
|
const hasUserActions = notification.actions && notification.actions.length > 0;
|
||||||
|
const showActions = hasAttachmentActions || hasClickAction || hasUserActions;
|
||||||
return (
|
return (
|
||||||
<Card sx={{ minWidth: 275, padding: 1 }}>
|
<Card sx={{ minWidth: 275, padding: 1 }}>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
@ -161,13 +162,15 @@ const NotificationItem = (props) => {
|
||||||
</svg>}
|
</svg>}
|
||||||
</Typography>
|
</Typography>
|
||||||
{notification.title && <Typography variant="h5" component="div">{formatTitle(notification)}</Typography>}
|
{notification.title && <Typography variant="h5" component="div">{formatTitle(notification)}</Typography>}
|
||||||
<Typography variant="body1" sx={{ whiteSpace: 'pre-line' }}>{autolink(formatMessage(notification))}</Typography>
|
<Typography variant="body1" sx={{ whiteSpace: 'pre-line' }}>
|
||||||
|
{autolink(maybeAppendActionErrors(formatMessage(notification), notification))}
|
||||||
|
</Typography>
|
||||||
{attachment && <Attachment attachment={attachment}/>}
|
{attachment && <Attachment attachment={attachment}/>}
|
||||||
{tags && <Typography sx={{ fontSize: 14 }} color="text.secondary">{t("notifications_tags")}: {tags}</Typography>}
|
{tags && <Typography sx={{ fontSize: 14 }} color="text.secondary">{t("notifications_tags")}: {tags}</Typography>}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
{showActions &&
|
{showActions &&
|
||||||
<CardActions sx={{paddingTop: 0}}>
|
<CardActions sx={{paddingTop: 0}}>
|
||||||
{showAttachmentActions && <>
|
{hasAttachmentActions && <>
|
||||||
<Tooltip title={t("notifications_attachment_copy_url_title")}>
|
<Tooltip title={t("notifications_attachment_copy_url_title")}>
|
||||||
<Button onClick={() => handleCopy(attachment.url)}>{t("notifications_attachment_copy_url_button")}</Button>
|
<Button onClick={() => handleCopy(attachment.url)}>{t("notifications_attachment_copy_url_button")}</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -175,14 +178,15 @@ const NotificationItem = (props) => {
|
||||||
<Button onClick={() => openUrl(attachment.url)}>{t("notifications_attachment_open_button")}</Button>
|
<Button onClick={() => openUrl(attachment.url)}>{t("notifications_attachment_open_button")}</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>}
|
</>}
|
||||||
{showClickAction && <>
|
{hasClickAction && <>
|
||||||
<Tooltip title={t("notifications_click_copy_url_title")}>
|
<Tooltip title={t("notifications_click_copy_url_title")}>
|
||||||
<Button onClick={() => handleCopy(notification.click)}>{t("notifications_click_copy_url_button")}</Button>
|
<Button onClick={() => handleCopy(notification.click)}>{t("notifications_click_copy_url_button")}</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<Tooltip title={t("notifications_click_open_title", { url: notification.click })}>
|
<Tooltip title={t("notifications_actions_open_url_title", { url: notification.click })}>
|
||||||
<Button onClick={() => openUrl(notification.click)}>{t("notifications_click_open_button")}</Button>
|
<Button onClick={() => openUrl(notification.click)}>{t("notifications_click_open_button")}</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</>}
|
</>}
|
||||||
|
{hasUserActions && <UserActions notification={notification}/>}
|
||||||
</CardActions>}
|
</CardActions>}
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
|
@ -329,6 +333,83 @@ const Image = (props) => {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const UserActions = (props) => {
|
||||||
|
return (
|
||||||
|
<>{props.notification.actions.map(action =>
|
||||||
|
<UserAction key={action.id} notification={props.notification} action={action}/>)}</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserAction = (props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const notification = props.notification;
|
||||||
|
const action = props.action;
|
||||||
|
if (action.action === "broadcast") {
|
||||||
|
return (
|
||||||
|
<Tooltip title={t("notifications_actions_not_supported")}>
|
||||||
|
<span><Button disabled>{action.label}</Button></span>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
} else if (action.action === "view") {
|
||||||
|
return (
|
||||||
|
<Tooltip title={t("notifications_actions_open_url_title", { url: action.url })}>
|
||||||
|
<Button onClick={() => openUrl(action.url)}>{action.label}</Button>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
} else if (action.action === "http") {
|
||||||
|
const method = action.method ?? "POST";
|
||||||
|
const label = action.label + (ACTION_LABEL_SUFFIX[action.progress ?? 0] ?? "");
|
||||||
|
return (
|
||||||
|
<Tooltip title={t("notifications_actions_http_request_title", { method: method, url: action.url })}>
|
||||||
|
<Button onClick={() => performHttpAction(notification, action)}>{label}</Button>
|
||||||
|
</Tooltip>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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 NoNotifications = (props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const shortUrl = topicShortUrl(props.subscription.baseUrl, props.subscription.topic);
|
const shortUrl = topicShortUrl(props.subscription.baseUrl, props.subscription.topic);
|
||||||
|
|
Loading…
Reference in New Issue