<template> <Dialog title="Add alert" @close="$emit('close')" > <div v-if="error" class="dialog-error" >{{ error }}</div> <form class="dialog-body" @submit.prevent="addAlert" > <div class="dialog-section"> <h3 class="dialog-heading"> When <label class="dialog-description noselect"> <template v-if="all">Scene must match <strong>all</strong> fields</template> <template v-else>Scene must match <strong>any</strong> field</template> <Toggle :checked="all" @change="(checked) => all = checked" /> </label> </h3> <div class="alert-section"> <h4 v-if="actors.length > 1" class="alert-heading" >Actors</h4> <h4 v-else class="alert-heading" >Actor</h4> <ul class="actors nolist"> <li v-for="actor in actors" :key="`actor-${actor.id}`" class="actor" > <ActorPreview :actor="actor" target="_blank" /> <Icon icon="cross3" class="remove" @click.native="removeActor(actor)" /> </li> <Tooltip> <li class="actor placeholder"><template v-if="actors.length === 0">Any actor</template> <Icon icon="plus3" class="add" /> </li> <template #tooltip> <Search content="actors" @select="actor => addActor(actor)" /> </template> </Tooltip> </ul> </div> <div class="alert-section"> <h4 v-if="actors.length > 1" class="alert-heading" >Do</h4> <h4 v-else class="alert-heading" >Does</h4> <ul class="tags nolist"> <li v-for="tag in tags" :key="`tag-${tag.id}`" class="tag" >{{ tag.name }} <Icon icon="cross3" class="remove" @click.native="removeTag(tag)" /> </li> <Tooltip> <li class="tag placeholder"><template v-if="tags.length === 0">Any type of scene</template> <Icon icon="plus3" class="add" /> </li> <template #tooltip> <Search content="tags" :defaults="['anal', 'blowbang', 'mfm', 'dp', 'gangbang', 'airtight']" @select="tag => addTag(tag)" /> </template> </Tooltip> </ul> </div> <div class="alert-section"> <h4 class="alert-heading">For</h4> <div class="entities"> <div v-for="(entity, index) in entities" :key="`entity-${entity.id}`" :class="{ invalid: all && index > 0 }" class="entity" > <Entity :entity="entity" target="_blank" /> <Icon icon="cross3" class="remove" @click.native="removeEntity(entity)" /> </div> <Tooltip v-if="entities.length < 1 || !all"> <div class="entity placeholder"> Any channel <Icon icon="plus3" class="add" /> </div> <template #tooltip> <Search label="Search channels" content="entities" @select="entity => addEntity(entity)" /> </template> </Tooltip> </div> </div> <div class="alert-section"> <h4 class="alert-heading">Matching</h4> <ul class="matches nolist"> <li v-for="(match, index) in matches" :key="`match-${index}`" class="match" > <span class="match-property">{{ match.property }}: </span> <span v-if="match.expression.slice(0, 1) === '/' && match.expression.slice(-1) === '/'" class="match-expression" ><span class="match-slash">/</span>{{ match.expression.slice(1, -1) }}<span class="match-slash">/</span></span> <span v-else class="match-expression" >{{ match.expression }}</span> <Icon icon="cross3" class="remove" @click.native="removeMatch(index)" /> </li> <Tooltip v-if="entities.length === 0" @open="$refs.expression?.focus()" > <li class="match placeholder"> Anything <Icon icon="plus3" class="add" /> </li> <template #tooltip> <form class="pattern-tooltip" @submit.prevent="addMatch" > <select v-model="matchProperty" class="input" > <option value="title">Title</option> <option value="description">Description</option> </select> <input ref="expression" v-model="matchExpression" class="input" placeholder="Expression, // for RegExp" > </form> </template> </Tooltip> </ul> </div> </div> <div class="dialog-section"> <h3 class="dialog-heading">Then</h3> <label class="alert-label"> <Checkbox :checked="notify" @change="checked => notify = checked" />Notify me in traxxx </label> <!-- <label class="alert-label"> <Checkbox :checked="email" @change="checked => email = checked" />Send me an e-mail </label> --> <div class="stashes-container"> <ul class="stashes nolist"> <li v-for="stash in stashes" :key="`stash-${stash.id}`" class="stash" >{{ stash.name }} <Icon icon="cross3" class="remove" @click.native="removeStash(stash)" /> </li> <Tooltip> <li class="stash placeholder"> Add to stash <Icon icon="plus3" class="add" /> </li> <template #tooltip> <Search content="stashes" @select="stash => addStash(stash)" /> </template> </Tooltip> </ul> </div> </div> <div class="dialog-actions right"> <button :disabled="actors.length === 0 && tags.length === 0 && entities.length === 0 && matches.length === 0" type="submit" class="button button-primary" >Add alert</button> </div> </form> </Dialog> </template> <script> import ActorPreview from '../actors/preview.vue'; import Entity from '../entities/tile.vue'; import Checkbox from '../form/checkbox.vue'; import Toggle from '../form/toggle.vue'; import Search from './search.vue'; async function addAlert() { this.error = null; try { await this.$store.dispatch('addAlert', { all: this.all, actors: this.actors.map((actor) => actor.id), tags: this.tags.map((tag) => tag.id), matches: this.matches, entities: this.entities.map((entity) => entity.id), notify: this.notify, email: this.email, stashes: this.stashes.map((stash) => stash.id), }); this.$emit('close', true); } catch (error) { this.error = error.message; } } function addActor(actor) { if (!this.actors.some((selectedActor) => selectedActor.id === actor.id)) { this.actors = this.actors.concat(actor); } this.events.emit('blur'); } function addEntity(entity) { this.entities = this.entities.concat(entity); this.events.emit('blur'); } function addTag(tag) { if (!this.tags.some((selectedTag) => selectedTag.id === tag.id)) { this.tags = this.tags.concat(tag); } this.events.emit('blur'); } function removeActor(actor) { this.actors = this.actors.filter((listedActor) => listedActor.id !== actor.id); } function removeEntity(entity) { this.entities = this.entities.filter((alertEntity) => alertEntity.id !== entity.id); } function removeTag(tag) { this.tags = this.tags.filter((listedTag) => listedTag.id !== tag.id); } function addMatch() { if (!this.matchExpression) { return; } this.matches = this.matches.concat({ property: this.matchProperty, expression: this.matchExpression, }); this.matchProperty = 'title'; this.matchExpression = null; this.events.emit('blur'); } function removeMatch(removeIndex) { this.matches = this.matches.filter((match, matchIndex) => matchIndex !== removeIndex); } function addStash(stash) { if (!this.stashes.some((selectedStash) => selectedStash.id === stash.id)) { this.stashes = this.stashes.concat(stash); } this.events.emit('blur'); } function removeStash(stash) { this.stashes = this.stashes.filter((listedStash) => listedStash.id !== stash.id); } export default { components: { ActorPreview, Checkbox, Entity, Search, Toggle, }, emits: ['close'], data() { return { error: null, actors: [], tags: [], all: true, entities: [], matches: [], matchProperty: 'title', matchExpression: null, notify: true, email: false, stashes: [], availableStashes: this.$store.state.auth.user.stashes, }; }, methods: { addActor, addAlert, addEntity, addMatch, addTag, addStash, removeActor, removeEntity, removeMatch, removeTag, removeStash, }, }; </script> <style lang="scss" scoped> .dialog-section { width: 30rem; max-width: 100%; &:first-child { border-bottom: solid 1px var(--shadow-hint); margin: 0 0 1rem 0; } } .dialog-heading { display: flex; justify-content: space-between; align-items: center; margin: 0 0 .25rem 0; color: var(--primary); } .dialog-description { display: flex; align-items: center; color: var(--shadow); font-size: .9rem; font-weight: normal; .toggle-container { margin-left: .5rem; } } .dialog-error { padding: 1rem; margin-bottom: 1rem; background: var(--error); color: var(--text-light); font-weight: bold; text-align: center; } .alert-heading { margin: .75rem 0 .25rem 0; } .actors, .entities, .tags { display: flex; align-items: center; flex-wrap: wrap; font-size: 0; } .match { display: flex; align-items: center; padding: .25rem 0; font-family: inherit; .remove { position: relative; top: -.1rem; right: 0; } } .match-property { text-transform: capitalize; color: var(--shadow); } .match-expression { flex-grow: 1; } .match-slash { padding: 0 .1rem; color: var(--primary); font-weight: bold; } .actors > .actor, .entity, .tag, .stash { position: relative; font-size: 1rem; margin: 0 .5rem .5rem 0; } .entity.invalid { opacity: .5; pointer-events: none; } .entity .tile { width: 10rem; height: 2.5rem; } .tag:not(.placeholder), .stash:not(.placeholder) { padding: .5rem .75rem; border: solid 1px var(--shadow-hint); margin: 0 .75rem 0 0; font-size: .9rem; font-weight: bold; } .stashes { margin: 0 0 0 .25rem; color: var(--text); } .pattern-tooltip { display: flex; gap: .5rem; position: relative; padding: .5rem; overflow: hidden; } .remove { width: 1rem; height: 1rem; position: absolute; top: -.35rem; right: -.35rem; z-index: 1; border: solid 1px var(--darken-hint); border-radius: 50%; background: var(--background); fill: var(--shadow-weak); box-shadow: 0 0 1px var(--shadow); &:hover { fill: var(--text-light); background: var(--primary); border: solid 1px var(--primary); cursor: pointer; } } .placeholder { display: inline-flex; align-items: center; color: var(--shadow-strong); padding: .75rem 0; margin: 0 0 .5rem 0; font-size: 1rem; .add { fill: var(--shadow); margin: 0 0 0 .5rem; } &:hover { color: var(--primary); cursor: pointer; .add { fill: var(--primary); } } } .alert-label { display: flex; align-items: center; padding: .5rem 0; margin: 0 0 .25rem 0; cursor: pointer; } .stashes-heading { display: flex; align-items: center; margin: 0 0 .5rem 0; font-size: 1rem; .alert-label { display: inline-block; margin: 0; } } .tooltip-container { display: inline-block; } .check-container { display: inline-block; margin: 0 .5rem 0 0; } </style>