2024-02-29 04:08:54 +00:00
|
|
|
<template>
|
2024-03-26 03:14:42 +00:00
|
|
|
<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>
|
2024-03-03 01:33:35 +00:00
|
|
|
|
2024-03-26 23:06:03 +00:00
|
|
|
<span class="age">{{ formatDistanceStrict(Date.now(), profile.createdAt) }}</span>
|
2024-03-26 03:14:42 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
<section class="profile-section">
|
|
|
|
<div class="section-header">
|
|
|
|
<h3 class="heading">Stashes</h3>
|
|
|
|
|
|
|
|
<button
|
2024-04-02 03:55:53 +00:00
|
|
|
v-if="profile.id === user?.id"
|
2024-03-26 03:14:42 +00:00
|
|
|
class="button"
|
|
|
|
@click="showStashDialog = true"
|
|
|
|
>
|
|
|
|
<Icon icon="plus3" />
|
|
|
|
<span class="button-label">New stash</span>
|
|
|
|
</button>
|
2024-03-21 01:59:55 +00:00
|
|
|
</div>
|
2024-03-26 03:14:42 +00:00
|
|
|
|
|
|
|
<Dialog
|
|
|
|
v-if="showStashDialog"
|
|
|
|
title="New stash"
|
|
|
|
@close="showStashDialog = false"
|
|
|
|
@open="stashNameInput?.focus()"
|
|
|
|
>
|
|
|
|
<form
|
|
|
|
class="dialog-body"
|
|
|
|
@submit.prevent="createStash"
|
|
|
|
>
|
|
|
|
<input
|
|
|
|
ref="stashNameInput"
|
|
|
|
v-model="stashName"
|
2024-03-26 23:06:03 +00:00
|
|
|
maxlength="24"
|
2024-03-26 03:14:42 +00:00
|
|
|
placeholder="Stash name"
|
2024-03-26 23:06:03 +00:00
|
|
|
class="input"
|
2024-03-26 03:14:42 +00:00
|
|
|
>
|
|
|
|
|
|
|
|
<button
|
|
|
|
class="button button-submit"
|
|
|
|
>Create stash</button>
|
|
|
|
</form>
|
|
|
|
</Dialog>
|
|
|
|
|
|
|
|
<ul class="stashes nolist">
|
|
|
|
<li
|
|
|
|
v-for="stash in profile.stashes"
|
|
|
|
:key="`stash-${stash.id}`"
|
|
|
|
>
|
2024-03-26 23:06:03 +00:00
|
|
|
<StashTile
|
|
|
|
:stash="stash"
|
|
|
|
:profile="profile"
|
|
|
|
@reload="reloadProfile"
|
|
|
|
/>
|
2024-03-26 03:14:42 +00:00
|
|
|
</li>
|
|
|
|
</ul>
|
|
|
|
</section>
|
2024-05-19 03:07:35 +00:00
|
|
|
|
|
|
|
<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>
|
|
|
|
|
|
|
|
<table class="alerts">
|
|
|
|
<tbody>
|
|
|
|
<tr class="alerts-headers">
|
|
|
|
<th class="alerts-header">Actors</th>
|
|
|
|
<th class="alerts-header">Tags</th>
|
|
|
|
<th class="alerts-header">Entities</th>
|
|
|
|
<th class="alerts-header">Matches</th>
|
|
|
|
</tr>
|
|
|
|
|
|
|
|
<tr
|
|
|
|
v-for="alert in alerts"
|
|
|
|
:key="`alert-${alert.id}`"
|
|
|
|
class="alert"
|
|
|
|
>
|
|
|
|
<td class="alert-field">{{ alert.actors.map((actor) => actor.name).join(', ') }}</td>
|
|
|
|
<td class="alert-field">{{ alert.tags.map((tag) => tag.name).join(alert.and.tags ? ' + ' : ' | ') }}</td>
|
|
|
|
<td class="alert-field">{{ alert.entities.map((entity) => entity.name).join(', ') }}</td>
|
|
|
|
<td class="alert-field">{{ alert.matches.map((match) => match.expression).join(', ') }}</td>
|
|
|
|
<td class="alert-actions noselect">
|
|
|
|
<Icon
|
|
|
|
icon="bin"
|
|
|
|
@click="removeAlert(alert)"
|
|
|
|
/>
|
|
|
|
</td>
|
|
|
|
</tr>
|
|
|
|
</tbody>
|
|
|
|
</table>
|
|
|
|
|
|
|
|
<AlertDialog
|
|
|
|
v-if="showAlertDialog"
|
|
|
|
@close="showAlertDialog = false; reloadAlerts();"
|
|
|
|
/>
|
|
|
|
</section>
|
2024-03-26 03:14:42 +00:00
|
|
|
</div>
|
2024-02-29 04:08:54 +00:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
<script setup>
|
2024-03-26 02:00:50 +00:00
|
|
|
import { ref, inject } from 'vue';
|
2024-03-26 03:14:42 +00:00
|
|
|
import { formatDistanceStrict } from 'date-fns';
|
2024-03-26 02:00:50 +00:00
|
|
|
|
2024-05-19 03:07:35 +00:00
|
|
|
import { get, post, del } from '#/src/api.js';
|
2024-02-29 04:08:54 +00:00
|
|
|
|
2024-03-26 23:06:03 +00:00
|
|
|
import StashTile from '#/components/stashes/tile.vue';
|
2024-03-26 03:14:42 +00:00
|
|
|
import Dialog from '#/components/dialog/dialog.vue';
|
2024-05-19 03:07:35 +00:00
|
|
|
import AlertDialog from '#/components/alerts/create.vue';
|
2024-03-26 03:14:42 +00:00
|
|
|
|
2024-02-29 04:08:54 +00:00
|
|
|
const pageContext = inject('pageContext');
|
2024-04-02 03:55:53 +00:00
|
|
|
const user = pageContext.user;
|
2024-03-26 02:00:50 +00:00
|
|
|
const profile = ref(pageContext.pageProps.profile);
|
2024-05-19 03:07:35 +00:00
|
|
|
const alerts = ref(pageContext.pageProps.alerts);
|
2024-03-26 03:14:42 +00:00
|
|
|
|
|
|
|
const stashName = ref(null);
|
|
|
|
const stashNameInput = ref(null);
|
|
|
|
|
2024-03-26 02:00:50 +00:00
|
|
|
const done = ref(true);
|
2024-03-26 03:14:42 +00:00
|
|
|
const showStashDialog = ref(false);
|
2024-05-19 03:07:35 +00:00
|
|
|
const showAlertDialog = ref(false);
|
2024-03-03 01:33:35 +00:00
|
|
|
|
2024-03-26 23:06:03 +00:00
|
|
|
async function reloadProfile() {
|
|
|
|
profile.value = await get(`/users/${profile.value.id}`);
|
2024-03-03 01:33:35 +00:00
|
|
|
}
|
2024-03-26 02:00:50 +00:00
|
|
|
|
2024-03-26 03:14:42 +00:00
|
|
|
async function createStash() {
|
|
|
|
if (done.value === false) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
done.value = false;
|
|
|
|
|
2024-05-19 03:07:35 +00:00
|
|
|
try {
|
|
|
|
await post('/stashes', {
|
|
|
|
name: stashName.value,
|
|
|
|
public: false,
|
|
|
|
}, {
|
|
|
|
successFeedback: `Created stash '${stashName.value}'`,
|
|
|
|
errorFeedback: `Failed to create stash '${stashName.value}'`,
|
|
|
|
appendErrorMessage: true,
|
|
|
|
});
|
|
|
|
} finally {
|
|
|
|
done.value = true;
|
|
|
|
}
|
2024-03-26 03:14:42 +00:00
|
|
|
|
2024-03-26 23:06:03 +00:00
|
|
|
showStashDialog.value = false;
|
|
|
|
stashName.value = null;
|
2024-03-26 03:14:42 +00:00
|
|
|
|
2024-03-26 23:06:03 +00:00
|
|
|
await reloadProfile();
|
2024-03-26 02:00:50 +00:00
|
|
|
}
|
2024-05-19 03:07:35 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2024-02-29 04:08:54 +00:00
|
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
2024-03-26 03:14:42 +00:00
|
|
|
.page {
|
|
|
|
display: flex;
|
|
|
|
flex-grow: 1;
|
|
|
|
justify-content: center;
|
|
|
|
background: var(--background-base-10);
|
|
|
|
}
|
|
|
|
|
|
|
|
.profile {
|
|
|
|
width: 1200px;
|
|
|
|
max-width: 100%;
|
|
|
|
}
|
|
|
|
|
2024-02-29 04:08:54 +00:00
|
|
|
.profile-header {
|
|
|
|
display: flex;
|
2024-03-26 03:14:42 +00:00
|
|
|
justify-content: space-between;
|
2024-02-29 04:08:54 +00:00
|
|
|
align-items: center;
|
|
|
|
padding: .5rem 1rem;
|
|
|
|
color: var(--highlight-strong-30);
|
|
|
|
background: var(--grey-dark-40);
|
2024-03-26 03:14:42 +00:00
|
|
|
border-radius: 0 0 .5rem .5rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.user {
|
|
|
|
display: flex;
|
|
|
|
overflow: hidden;
|
2024-02-29 04:08:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
.username {
|
|
|
|
margin: 0;
|
2024-03-26 02:00:50 +00:00
|
|
|
font-size: 1.25rem;
|
2024-02-29 04:08:54 +00:00
|
|
|
}
|
|
|
|
|
2024-03-26 03:14:42 +00:00
|
|
|
.age {
|
|
|
|
display: flex;
|
|
|
|
flex-shrink: 0;
|
2024-03-26 23:06:03 +00:00
|
|
|
font-size: .9rem;
|
2024-03-26 03:14:42 +00:00
|
|
|
|
|
|
|
.icon {
|
2024-03-26 23:06:03 +00:00
|
|
|
width: .9rem;
|
2024-03-26 03:14:42 +00:00
|
|
|
fill: var(--highlight-strong-20);
|
|
|
|
margin-right: .75rem;
|
|
|
|
transform: translateY(-1px);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-29 04:08:54 +00:00
|
|
|
.avatar {
|
2024-03-26 02:00:50 +00:00
|
|
|
width: 1.5rem;
|
|
|
|
height: 1.5rem;
|
2024-02-29 04:08:54 +00:00
|
|
|
border-radius: .25rem;
|
|
|
|
margin-right: 1rem;
|
|
|
|
}
|
2024-03-03 01:33:35 +00:00
|
|
|
|
2024-03-26 03:14:42 +00:00
|
|
|
.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);
|
|
|
|
}
|
|
|
|
|
2024-03-03 01:33:35 +00:00
|
|
|
.stashes {
|
|
|
|
display: grid;
|
|
|
|
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
2024-03-17 04:27:43 +00:00
|
|
|
gap: 1rem;
|
2024-03-26 03:14:42 +00:00
|
|
|
padding: 0 .5rem 1rem .5rem;
|
2024-03-03 01:33:35 +00:00
|
|
|
}
|
|
|
|
|
2024-05-19 03:07:35 +00:00
|
|
|
.alerts {
|
|
|
|
width: 100%;
|
|
|
|
height: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.alert {
|
|
|
|
&:nth-child(odd) {
|
|
|
|
background: var(--shadow-weak-40);
|
|
|
|
}
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
background: var(--shadow-weak-30);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
.alerts-header {
|
|
|
|
text-align: left;
|
|
|
|
}
|
|
|
|
|
|
|
|
.alerts-header,
|
|
|
|
.alert-field {
|
|
|
|
padding: .25rem .5rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.alert-actions {
|
|
|
|
height: 100%;
|
|
|
|
|
|
|
|
.icon {
|
|
|
|
height: 100%;
|
|
|
|
padding: 0 .25rem;
|
|
|
|
fill: var(--shadow);
|
|
|
|
|
|
|
|
&:hover {
|
|
|
|
cursor: pointer;
|
|
|
|
fill: var(--primary);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-03-26 03:14:42 +00:00
|
|
|
.dialog-body {
|
|
|
|
padding: 1rem;
|
|
|
|
|
|
|
|
.input {
|
|
|
|
margin-bottom: .5rem;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@media(--compact) {
|
|
|
|
.profile-header {
|
|
|
|
border-radius: 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
.section-header {
|
|
|
|
padding: .5rem 1rem .5rem 1rem;
|
|
|
|
}
|
|
|
|
|
|
|
|
.stashes {
|
|
|
|
padding: 0 1rem 1rem 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;
|
|
|
|
}
|
|
|
|
}
|
2024-02-29 04:08:54 +00:00
|
|
|
</style>
|