traxxx-web/pages/users/@username/+Page.vue

484 lines
9.5 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="page">
<div class="profile">
<div class="profile-header">
<div class="user">
<img
v-if="profile.avatar"
:src="profile.avatar"
class="avatar"
>
<h2 class="username ellipsis">{{ profile.username }}</h2>
</div>
<span class="age">{{ formatDistanceStrict(Date.now(), profile.createdAt) }}</span>
</div>
<section class="profile-section">
<div class="section-header">
<h3 class="heading">Stashes</h3>
<button
v-if="profile.id === user?.id"
class="button"
@click="showStashDialog = true"
>
<Icon icon="plus3" />
<span class="button-label">New stash</span>
</button>
</div>
<StashDialog
v-if="showStashDialog"
@created="showStashDialog = false; reloadStashes();"
@close="showStashDialog = false"
/>
<ul class="stashes nolist">
<li
v-for="stash in stashes"
:key="`stash-${stash.id}`"
>
<StashTile
:stash="stash"
:profile="profile"
@reload="reloadStashes"
/>
</li>
</ul>
</section>
<section
v-if="profile.id === user?.id"
class="profile-section"
>
<div class="section-header">
<h3 class="heading">Alerts</h3>
<button
class="button"
@click="showAlertDialog = true"
>
<Icon icon="plus3" />
<span class="button-label">New alert</span>
</button>
</div>
<ul class="alerts nolist">
<li
v-for="alert in alerts"
:key="`alert-${alert.id}`"
class="alert"
>
<div class="alert-triggers">
<Icon
v-tooltip="alert.notify ? 'Notify in traxxx' : undefined"
icon="bell2"
:class="{ trigger: alert.notify }"
/>
<Icon
v-if="alert.stashes.some((stash) => !stash.isPrimary)"
v-tooltip="`Add to ${alert.stashes.map((stash) => stash.name).join(', ')}`"
icon="folder-heart"
class="trigger"
/>
<Icon
v-else
v-tooltip="alert.stashes.length > 0 ? 'Add to Favorites' : undefined"
icon="heart7"
: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-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-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-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-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)"
/>
</div>
</li>
</ul>
<AlertDialog
v-if="showAlertDialog"
@close="showAlertDialog = false; reloadAlerts();"
/>
</section>
</div>
</div>
</template>
<script setup>
import { ref, inject } from 'vue';
import { formatDistanceStrict } from 'date-fns';
import { get, del } from '#/src/api.js';
import StashTile from '#/components/stashes/tile.vue';
import StashDialog from '#/components/stashes/create.vue';
import AlertDialog from '#/components/alerts/create.vue';
const pageContext = inject('pageContext');
const user = pageContext.user;
const profile = ref(pageContext.pageProps.profile);
const stashes = ref(pageContext.pageProps.stashes);
const alerts = ref(pageContext.pageProps.alerts);
const done = ref(true);
const showStashDialog = ref(false);
const showAlertDialog = ref(false);
async function reloadStashes() {
// profile.value = await get(`/users/${profile.value.id}`);
stashes.value = await get(`/users/${profile.value.id}/stashes`);
}
async function reloadAlerts() {
alerts.value = await get('/alerts');
}
async function removeAlert(alert) {
if (done.value === false) {
return;
}
done.value = false;
const alertLabel = [
...alert.actors.map((actor) => actor.name),
...alert.tags.map((tag) => tag.name),
...alert.entities.map((entity) => entity.name),
...alert.matches.map((match) => match.expression),
].filter(Boolean).join(', ');
try {
await del(`/alerts/${alert.id}`, {
undoFeedback: `Removed alert for '${alertLabel}'`,
errorFeedback: `Failed to remove alert for '${alertLabel}'`,
appendErrorMessage: true,
});
await reloadAlerts();
} finally {
done.value = true;
}
}
</script>
<style scoped>
.page {
display: flex;
flex-grow: 1;
justify-content: center;
background: var(--background-base-10);
}
.profile {
width: 1200px;
max-width: 100%;
}
.profile-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: .5rem 1rem;
color: var(--highlight-strong-30);
background: var(--shadow-strong-30);
border-radius: 0 0 .5rem .5rem;
}
.user {
display: flex;
overflow: hidden;
}
.username {
margin: 0;
font-size: 1.25rem;
}
.age {
display: flex;
flex-shrink: 0;
font-size: .9rem;
.icon {
width: .9rem;
fill: var(--highlight-strong-20);
margin-right: .75rem;
transform: translateY(-1px);
}
}
.avatar {
width: 1.5rem;
height: 1.5rem;
border-radius: .25rem;
margin-right: 1rem;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: .5rem .5rem .5rem .5rem;
.button {
margin-left: 1rem;
}
}
.heading {
margin: 0;
font-size: 1.1rem;
color: var(--primary);
}
.stashes {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
gap: 1rem;
padding: 0 .5rem 1rem .5rem;
}
.alerts {
width: 100%;
}
.alert {
padding: 0 0 0 .5rem;
display: flex;
align-items: stretch;
border-bottom: solid 1px var(--glass-weak-40);
&:hover {
border-color: var(--glass-weak-30);
}
}
.alert-triggers {
display: flex;
align-items: center;
gap: .5rem;
margin-right: .75rem;
.icon {
fill: var(--glass-weak-40);
}
.icon.trigger {
fill: var(--glass-weak-10);
}
}
.alert-details {
padding: .25rem 0;
flex-grow: 1;
line-height: 2.5;
color: var(--glass);
&.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(--glass);
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(--glass-weak-10);
.icon {
height: 100%;
padding: 0 .75rem;
fill: var(--glass);
&:hover {
cursor: pointer;
fill: var(--primary);
}
}
}
@media(--compact) {
.profile-header {
border-radius: 0;
}
.section-header {
padding: .5rem 1rem .5rem 1rem;
}
.stashes {
padding: 0 1rem 1rem 1rem;
}
.alert {
padding: 0 .5rem 0 1rem;
}
}
@media(--small-30) {
.section-header {
padding: .5rem .5rem .5rem .5rem;
}
.stashes {
padding: 0 .5rem 1rem .5rem;
}
.age .icon {
display: none;
}
}
@media(--small-50) {
.age {
display: none;
}
}
</style>