Improved alerts overview.
This commit is contained in:
parent
cb5d931309
commit
2dbfd556e1
|
@ -2,6 +2,7 @@
|
|||
--primary-dark-10: #e54485;
|
||||
--primary: #f65596;
|
||||
--primary-light-10: #f075a6;
|
||||
--primary-light-20: #f2a6c4;
|
||||
--primary-light-30: #f7c9dc;
|
||||
|
||||
--grey-dark-50: #111;
|
||||
|
@ -29,6 +30,7 @@
|
|||
--background-level-30: #eee;
|
||||
--background-dim: var(--shadow-weak-10);
|
||||
|
||||
--shadow-weak-50: rgba(0, 0, 0, .02);
|
||||
--shadow-weak-40: rgba(0, 0, 0, .05);
|
||||
--shadow-weak-30: rgba(0, 0, 0, .1);
|
||||
--shadow-weak-20: rgba(0, 0, 0, .2);
|
||||
|
|
|
@ -19,7 +19,10 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<ul class="notifs nolist">
|
||||
<ul
|
||||
v-if="done"
|
||||
class="notifs nolist"
|
||||
>
|
||||
<li
|
||||
v-for="notif in notifications"
|
||||
:key="notif.id"
|
||||
|
@ -32,30 +35,34 @@
|
|||
target="_blank"
|
||||
class="notif-body notif-link nolink"
|
||||
>
|
||||
<span class="notif-details">
|
||||
New
|
||||
<span class="notif-trigger">
|
||||
<span class="notif-details">
|
||||
New
|
||||
|
||||
<template
|
||||
v-if="notif.matchedTags.length > 0"
|
||||
>{{ notif.matchedTags.map((tag) => tag.name).join(', ') }}</template>
|
||||
|
||||
scene
|
||||
|
||||
<template
|
||||
v-if="notif.matchedActors.length > 0"
|
||||
>with {{ notif.matchedActors.map((actor) => actor.name).join(', ') }} </template>
|
||||
|
||||
<template
|
||||
v-if="notif.matchedEntity"
|
||||
>for {{ notif.matchedEntity.name }} </template>
|
||||
|
||||
<template
|
||||
v-if="notif.matchedExpressions.length > 0"
|
||||
>matching {{ notif.matchedExpressions.map((match) => match.expression).join(', ') }}</template>
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="notif.matchedTags.length > 0"
|
||||
class="notif-tags"
|
||||
>{{ notif.matchedTags.map((tag) => tag.name).join(', ') }}</span>
|
||||
|
||||
scene
|
||||
|
||||
<span
|
||||
v-if="notif.matchedActors.length > 0"
|
||||
class="notif-actors"
|
||||
>with {{ notif.matchedActors.map((actor) => actor.name).join(', ') }} </span>
|
||||
|
||||
<span
|
||||
v-if="notif.matchedEntity"
|
||||
class="notif-entities"
|
||||
>for {{ notif.matchedEntity.name }} </span>
|
||||
|
||||
<span
|
||||
v-if="notif.matchedExpressions.length > 0"
|
||||
class="notif-entities"
|
||||
>matching {{ notif.matchedExpressions.map((match) => match.expression).join(', ') }}</span>
|
||||
v-if="notif.alertId"
|
||||
class="notif-id"
|
||||
title="Alert ID"
|
||||
>#{{ notif.alertId }}</span>
|
||||
</span>
|
||||
|
||||
<span class="notif-scene">
|
||||
|
@ -65,6 +72,11 @@
|
|||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<Ellipsis
|
||||
v-else
|
||||
class="notifs loading"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -79,6 +91,8 @@ import {
|
|||
import { get, patch } from '#/src/api.js';
|
||||
import { formatDate } from '#/utils/format.js';
|
||||
|
||||
import Ellipsis from '#/components/loading/ellipsis.vue';
|
||||
|
||||
const pageContext = inject('pageContext');
|
||||
const user = pageContext.user;
|
||||
|
||||
|
@ -88,10 +102,13 @@ const done = ref(true);
|
|||
const emit = defineEmits(['unseen', 'addAlert']);
|
||||
|
||||
async function fetchNotifications() {
|
||||
const res = await get(`/users/${user?.id}/notifications`);
|
||||
done.value = false;
|
||||
|
||||
const res = await get(`/users/${user?.id}/notifications`);
|
||||
notifications.value = res.notifications;
|
||||
|
||||
done.value = true;
|
||||
|
||||
emit('unseen', res.unseen);
|
||||
}
|
||||
|
||||
|
@ -131,13 +148,18 @@ onMounted(async () => {
|
|||
<style scoped>
|
||||
.notifs {
|
||||
width: 30rem;
|
||||
height: 20rem;
|
||||
max-height: 20rem;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.notifs-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -209,13 +231,19 @@ onMounted(async () => {
|
|||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.notif-details {
|
||||
padding: .5rem 0 .15rem 0;
|
||||
.notif-trigger {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: .5rem 0 .1rem 0;
|
||||
box-sizing: border-box;
|
||||
color: var(--shadow-strong);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.notif-details {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.notif-link {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
@ -242,4 +270,10 @@ onMounted(async () => {
|
|||
object-fit: cover;
|
||||
}
|
||||
|
||||
.notif-id {
|
||||
padding: 0 .5rem;
|
||||
color: var(--shadow-weak-10);
|
||||
font-size: .9rem;
|
||||
font-weight: normal;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -89,44 +89,129 @@
|
|||
:key="`alert-${alert.id}`"
|
||||
class="alert"
|
||||
>
|
||||
<div class="alert-details">
|
||||
<div class="alert-triggers">
|
||||
<Icon
|
||||
icon="bell2"
|
||||
:title="alert.notify ? 'Notify in traxxx' : undefined"
|
||||
:class="{ trigger: alert.notify }"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-if="alert.stashes.some((stash) => !stash.isPrimary)"
|
||||
icon="folder-heart"
|
||||
:title="`Add to ${alert.stashes.map((stash) => stash.name).join(', ')}`"
|
||||
class="trigger"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-else
|
||||
icon="heart7"
|
||||
:title="alert.stashes.length > 0 ? 'Add to Favorites' : undefined"
|
||||
:class="{ trigger: alert.stashes.length > 0 }"
|
||||
/>
|
||||
|
||||
<!--
|
||||
<Icon
|
||||
icon="envelop5"
|
||||
title="E-mail me"
|
||||
:class="{ trigger: alert.email }"
|
||||
/>
|
||||
-->
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="alert-details"
|
||||
:class="{ and: alert.and.fields, or: !alert.and.fields }"
|
||||
>
|
||||
<span
|
||||
v-if="alert.tags.length > 0"
|
||||
class="alert-tags"
|
||||
><a
|
||||
v-for="tag in alert.tags"
|
||||
:key="`tag-${alert.id}-${tag.id}`"
|
||||
:href="`/tag/${tag.slug}`"
|
||||
class="link"
|
||||
>{{ tag.name }}</a> </span>
|
||||
class="alert-detail alert-tags"
|
||||
:class="{ and: alert.and.tags, or: !alert.and.tags }"
|
||||
>
|
||||
<span class="alert-values">
|
||||
<span
|
||||
v-for="tag in alert.tags"
|
||||
:key="`tag-${alert.id}-${tag.id}`"
|
||||
class="alert-key"
|
||||
>
|
||||
<a
|
||||
:href="`/tag/${tag.slug}`"
|
||||
class="alert-value link"
|
||||
>{{ tag.name }}</a>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="alert.actors.length > 0"
|
||||
class="alert-actors"
|
||||
>with <a
|
||||
v-for="actor in alert.actors"
|
||||
:key="`actor-${alert.id}-${actor.id}`"
|
||||
:href="`/actor/${actor.id}/${actor.slug}`"
|
||||
class="link"
|
||||
>{{ actor.name }}</a> </span>
|
||||
class="alert-detail alert-actors"
|
||||
:class="{ and: alert.and.actors, or: !alert.and.actors }"
|
||||
>
|
||||
<span class="alert-values">with
|
||||
<span
|
||||
v-for="actor in alert.actors"
|
||||
:key="`actor-${alert.id}-${actor.id}`"
|
||||
class="alert-key"
|
||||
>
|
||||
<a
|
||||
:href="`/actor/${actor.id}/${actor.slug}`"
|
||||
class="alert-value link"
|
||||
>{{ actor.name }}</a>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="alert.entities.length > 0"
|
||||
class="alert-entities"
|
||||
>for <a
|
||||
v-for="entity in alert.entities"
|
||||
:key="`entity-${alert.id}-${entity.id}`"
|
||||
:href="`/${entity.type}/${entity.slug}`"
|
||||
class="link"
|
||||
>{{ entity.name }}</a> </span>
|
||||
class="alert-detail alert-entities or"
|
||||
>
|
||||
<span class="alert-values">for
|
||||
<span
|
||||
v-for="entity in alert.entities"
|
||||
:key="`entity-${alert.id}-${entity.id}`"
|
||||
class="alert-key"
|
||||
>
|
||||
<a
|
||||
:href="`/${entity.type}/${entity.slug}`"
|
||||
class="alert-value link"
|
||||
>
|
||||
<Icon
|
||||
v-if="entity.type === 'network'"
|
||||
icon="device_hub"
|
||||
/>{{ entity.name }}
|
||||
</a>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="alert.matches.length > 0"
|
||||
class="alert-entities"
|
||||
>matching {{ alert.matches.map((match) => match.expression).join(', ') }}</span>
|
||||
class="alert-detail alert-matches"
|
||||
:class="{ and: alert.and.matches, or: !alert.and.matches }"
|
||||
>
|
||||
<span class="alert-values">matching
|
||||
<span
|
||||
v-for="match in alert.matches"
|
||||
:key="`match-${alert.id}-${match.id}`"
|
||||
class="alert-key"
|
||||
>
|
||||
<span class="alert-value">{{ match.property }}:
|
||||
<span
|
||||
class="alert-regex"
|
||||
title="If your original expression was not a /regular expression/, it was converted, and new characters may have been added for syntactical purposes. These characters do not alter the function of the expression; they ensure it."
|
||||
>{{ match.expression }}</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="alert-actions">
|
||||
<span
|
||||
class="alert-id"
|
||||
title="Alert ID"
|
||||
>#{{ alert.id }}</span>
|
||||
|
||||
<Icon
|
||||
icon="bin"
|
||||
@click="removeAlert(alert)"
|
||||
|
@ -310,36 +395,90 @@ async function removeAlert(alert) {
|
|||
}
|
||||
|
||||
.alert {
|
||||
padding: 0 0 0 .5rem;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: solid 1px var(--shadow-weak-40);
|
||||
}
|
||||
border-bottom: solid 1px var(--shadow-weak-40);
|
||||
|
||||
&:hover {
|
||||
background: var(--shadow-weak-40);
|
||||
border-color: var(--shadow-weak-30);
|
||||
}
|
||||
}
|
||||
|
||||
.alert-triggers {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .5rem;
|
||||
margin-right: 1rem;
|
||||
|
||||
.icon {
|
||||
fill: var(--shadow-weak-40);
|
||||
}
|
||||
|
||||
.link:not(:last-child):after {
|
||||
content: ', ';
|
||||
.icon.trigger {
|
||||
fill: var(--shadow-weak-10);
|
||||
}
|
||||
}
|
||||
|
||||
.alert-details {
|
||||
padding: .5rem 1rem;
|
||||
padding: .75rem 0;
|
||||
flex-grow: 1;
|
||||
line-height: 1.5;
|
||||
color: var(--shadow);
|
||||
|
||||
.link {
|
||||
color: inherit;
|
||||
&.and .alert-detail:not(:last-child):after {
|
||||
content: ' and ';
|
||||
}
|
||||
|
||||
&.or .alert-detail:not(:last-child):after {
|
||||
content: ' or ';
|
||||
}
|
||||
}
|
||||
|
||||
.alert-value {
|
||||
color: var(--text);
|
||||
|
||||
.icon {
|
||||
margin-right: .25rem;
|
||||
fill: var(--shadow);
|
||||
transform: translateY(2px);
|
||||
}
|
||||
}
|
||||
|
||||
.alert-values {
|
||||
padding: .5rem .5rem;
|
||||
border-bottom: solid 1px var(--primary-light-20);
|
||||
border-radius: .3rem;
|
||||
}
|
||||
|
||||
.alert-detail {
|
||||
&.and .alert-key:not(:last-child):after {
|
||||
content: ' and ';
|
||||
}
|
||||
|
||||
&.or .alert-key:not(:last-child):after {
|
||||
content: ' or ';
|
||||
}
|
||||
}
|
||||
|
||||
.alert-regex {
|
||||
&:before,
|
||||
&:after {
|
||||
content: '╱';
|
||||
padding: 0 .1rem;
|
||||
color: var(--primary-light-20);
|
||||
}
|
||||
}
|
||||
|
||||
.alert-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: .9rem;
|
||||
color: var(--shadow-weak-10);
|
||||
|
||||
.icon {
|
||||
height: 100%;
|
||||
padding: 0 .5rem;
|
||||
padding: 0 .75rem;
|
||||
fill: var(--shadow);
|
||||
|
||||
&:hover {
|
||||
|
@ -369,6 +508,10 @@ async function removeAlert(alert) {
|
|||
.stashes {
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
}
|
||||
|
||||
.alert {
|
||||
padding: 0 .5rem 0 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media(--small-30) {
|
||||
|
|
|
@ -39,6 +39,12 @@ function curateAlert(alert, context = {}) {
|
|||
property: match.property,
|
||||
expression: match.expression,
|
||||
})) || [],
|
||||
stashes: context.stashes?.map((stash) => ({
|
||||
id: stash.stash_id,
|
||||
name: stash.stash_name,
|
||||
slug: stash.stash_slug,
|
||||
isPrimary: stash.stash_primary,
|
||||
})) || [],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -49,9 +55,11 @@ export async function fetchAlerts(user) {
|
|||
tags,
|
||||
entities,
|
||||
matches,
|
||||
stashes,
|
||||
} = await promiseProps({
|
||||
alerts: knex('alerts')
|
||||
.where('user_id', user.id),
|
||||
.where('user_id', user.id)
|
||||
.orderBy('created_at', 'desc'),
|
||||
actors: knex('alerts_actors')
|
||||
.select('alerts_actors.*', 'actors.name as actor_name', 'actors.slug as actor_slug')
|
||||
.leftJoin('alerts', 'alerts.id', 'alerts_actors.alert_id')
|
||||
|
@ -71,6 +79,11 @@ export async function fetchAlerts(user) {
|
|||
.select('alerts_matches.*')
|
||||
.leftJoin('alerts', 'alerts.id', 'alerts_matches.alert_id')
|
||||
.where('alerts.user_id', user.id),
|
||||
stashes: knex('alerts_stashes')
|
||||
.select('alerts_stashes.*', 'stashes.id as stash_id', 'stashes.name as stash_name', 'stashes.slug as stash_slug', 'stashes.primary as stash_primary')
|
||||
.leftJoin('alerts', 'alerts.id', 'alerts_stashes.alert_id')
|
||||
.leftJoin('stashes', 'stashes.id', 'alerts_stashes.stash_id')
|
||||
.where('alerts.user_id', user.id),
|
||||
});
|
||||
|
||||
const curatedAlerts = alerts.map((alert) => curateAlert(alert, {
|
||||
|
@ -78,6 +91,7 @@ export async function fetchAlerts(user) {
|
|||
tags: tags.filter((tag) => tag.alert_id === alert.id),
|
||||
entities: entities.filter((entity) => entity.alert_id === alert.id),
|
||||
matches: matches.filter((match) => match.alert_id === alert.id),
|
||||
stashes: stashes.filter((stash) => stash.alert_id === alert.id),
|
||||
}));
|
||||
|
||||
return curatedAlerts;
|
||||
|
|
Loading…
Reference in New Issue