Added alert dialog. Fixed image rotation EXIT data being discarded.

This commit is contained in:
DebaucheryLibrarian
2021-04-04 21:52:19 +02:00
parent 837fc98ad2
commit da0cbced15
43 changed files with 1134 additions and 38 deletions

View File

@@ -0,0 +1,436 @@
<template>
<Dialog
title="Add alert"
@close="$emit('close')"
>
<form
class="dialog-body"
@submit.prevent="addAlert"
>
<div class="dialog-section">
<h3 class="dialog-heading">
When<span class="dialog-description">All to appear in the same scene</span>
</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" />
<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 v-slot: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 v-slot: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-if="entity"
class="entity"
>
<Entity :entity="entity" />
<Icon
icon="cross3"
class="remove"
@click.native="removeEntity(entity)"
/>
</div>
<Tooltip v-if="!entity">
<div class="entity placeholder">
Any channel
<Icon
icon="plus3"
class="add"
/>
</div>
<template v-slot:tooltip>
<Search
label="Search channels"
content="entities"
@select="entity => addEntity(entity)"
/>
</template>
</Tooltip>
</div>
</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 v-slot:tooltip>
<Search
content="stashes"
@select="stash => addStash(stash)"
/>
</template>
</Tooltip>
</ul>
</div>
</div>
<div class="dialog-actions right">
<button
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 Search from './search.vue';
async function addAlert() {
await this.$store.dispatch('addAlert', {
actors: this.actors.map(actor => actor.id),
tags: this.tags.map(tag => tag.id),
entity: this.entity?.id,
notify: this.notify,
email: this.email,
stashes: this.stashes.map(stash => stash.id),
});
}
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.entity = 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() {
this.entity = null;
}
function removeTag(tag) {
this.tags = this.tags.filter(listedTag => listedTag.id !== tag.id);
}
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,
},
data() {
return {
actors: [],
tags: [],
entity: null,
notify: true,
email: false,
stashes: [],
availableStashes: this.$store.state.auth.user.stashes,
};
},
emits: ['close'],
methods: {
addActor,
addAlert,
addEntity,
addTag,
addStash,
removeActor,
removeEntity,
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 {
color: var(--shadow);
font-size: .9rem;
font-weight: normal;
}
.alert-heading {
margin: .75rem 0 .25rem 0;
}
.actors,
.entities,
.tags {
display: flex;
align-items: center;
flex-wrap: wrap;
font-size: 0;
}
.actors > .actor,
.entity,
.tag,
.stash {
position: relative;
font-size: 1rem;
margin: 0 .5rem .5rem 0;
}
.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);
}
.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>

View File

@@ -0,0 +1,126 @@
<template>
<div>
<input
ref="input"
v-model="query"
:placeholder="label || `Search ${content}`"
class="input"
@input="search"
>
<ul
v-if="results.length > 0"
class="nolist"
>
<li
v-for="actor in results"
:key="`actor-${actor.id}`"
class="result"
@click="selectResult(actor)"
><img
v-if="actor.avatar"
:src="getPath(actor.avatar)"
class="avatar"
>{{ actor.name }}</li>
</ul>
</div>
</template>
<script>
async function search() {
if (this.content === 'actors') {
this.results = await this.$store.dispatch('searchActors', {
query: this.query,
minLength: 1,
limit: 10,
});
}
if (this.content === 'entities') {
this.results = await this.$store.dispatch('searchEntities', {
query: this.query,
limit: 10,
});
}
if (this.content === 'tags') {
this.results = await this.$store.dispatch('searchTags', {
query: this.query,
minLength: 1,
limit: 10,
});
}
if (this.content === 'stashes') {
this.results = this.$store.state.auth.user.stashes.filter(stash => new RegExp(this.query).test(stash.name));
}
}
function selectResult(item) {
this.query = null;
this.results = [];
this.$emit('select', item);
}
async function mounted() {
this.$refs.input.focus();
if (this.defaults.length > 0 && this.content === 'tags') {
this.results = await this.$store.dispatch('fetchTags', {
slugs: this.defaults,
});
}
if (this.content === 'stashes') {
this.results = this.$store.state.auth.user.stashes;
}
}
export default {
props: {
content: {
type: String,
default: null,
},
defaults: {
type: Array,
default: () => [],
},
label: {
type: String,
default: null,
},
},
data() {
return {
query: null,
results: [],
};
},
emits: ['select'],
mounted,
methods: {
search,
selectResult,
},
};
</script>
<style lang="scss" scoped>
.result {
display: flex;
align-items: center;
padding: .5rem;
&:hover {
color: var(--primary);
cursor: pointer;
}
}
.avatar {
height: 2rem;
margin: 0 .5rem 0 0;
}
</style>

View File

@@ -6,7 +6,7 @@
>
<div
class="dialog"
@click.stop
@click.stop="events.emit('blur')"
>
<div
v-if="title || $slots.header"
@@ -98,14 +98,29 @@ export default {
}
.body {
display: flex;
flex-direction: column;
flex-grow: 1;
overflow: hidden;
::v-deep(.section) {
padding: 1rem;
}
}
::v-deep(.dialog-body) {
padding: 1rem;
flex-grow: 1;
}
::v-deep(.dialog-section:not(:last-child)) {
border-bottom: solid 1px var(--shadow-hint);
overflow: auto;
}
::v-deep(.dialog-actions) {
display: flex;
padding: 1rem 0 0 0;
margin: 1rem 0 0 0;
&.center {
justify-content: center;

View File

@@ -47,6 +47,7 @@ export default {
.tile {
width: 15rem;
height: 6rem;
}
&.expanded {

View File

@@ -56,7 +56,7 @@ export default {
@import 'theme';
.tile {
height: 6rem;
height: 100%;
background: var(--tile);
display: flex;
flex-shrink: 0;

View File

@@ -3,7 +3,7 @@
title="Filters"
@close="$emit('close')"
>
<div class="filters">
<div class="filters dialog-body">
<h3 class="form-heading">Show me</h3>
<ul class="tags nolist">
@@ -47,7 +47,7 @@ export default {
},
data() {
return {
tags: ['anal', 'gay', 'transsexual', 'bisexual', 'pissing'],
tags: ['anal', 'gay', 'transsexual', 'bisexual', 'pissing', 'anal prolapse'],
};
},
computed: {

View File

@@ -82,8 +82,25 @@
@click.stop="$emit('toggleSidebar')"
><Icon icon="menu" /></div>
<Tooltip v-if="me">
<div
class="header-button header-notifications"
@click="showAddAlert = true"
>
<Icon
icon="bell2"
/>
</div>
<template v-slot:tooltip>
<div
class="notifications"
>No notifications</div>
</template>
</Tooltip>
<Tooltip>
<div class="header-account">
<div class="header-button header-account">
<div class="account">
<Icon
icon="user3-long"
@@ -121,6 +138,11 @@
/>
</template>
</Tooltip>
<AddAlert
v-if="showAddAlert"
@close="showAddAlert = false"
>Alert</AddAlert>
</div>
</header>
</template>
@@ -128,11 +150,18 @@
<script>
import Menu from './menu.vue';
import Search from './search.vue';
import AddAlert from '../alerts/add-alert.vue';
import logo from '../../img/logo.svg';
function me() {
return this.$store.state.auth.user;
}
export default {
AddAlert,
components: {
AddAlert,
Menu,
Search,
},
@@ -142,8 +171,12 @@ export default {
logo,
searching: false,
showFilters: false,
showAddAlert: false,
};
},
computed: {
me,
},
};
</script>
@@ -257,26 +290,12 @@ export default {
}
}
.header-toggles {
margin: 0 .5rem 0 0;
.header-button {
padding: 1rem .75rem;
.icon {
padding: 1rem .75rem;
fill: var(--shadow);
&:hover {
fill: var(--shadow-strong);
cursor: pointer;
}
&.active {
fill: var(--primary);
}
}
}
.header-account {
padding: 1rem;
.icon {
fill: var(--shadow);
}
&:hover {
cursor: pointer;
@@ -285,12 +304,20 @@ export default {
border-color: var(--primary);
}
.avatar {
.icon {
fill: var(--primary);
}
}
}
.header-account {
padding: 1rem 1rem 1rem .75rem;
}
.header-notifications {
padding: 1rem .75rem 1rem 1rem;
}
.account {
width: 1.25rem;
height: 1.25rem;
@@ -333,6 +360,10 @@ export default {
}
}
.notifications {
padding: 1rem;
}
@media(max-width: $breakpoint-kilo) {
.search-full {
display: none;
@@ -364,7 +395,8 @@ export default {
display: none;
}
.header-account {
.header-account,
.header-notifications {
display: none;
}
}

View File

@@ -144,6 +144,10 @@ export default {
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
grid-gap: .5rem;
padding: 1rem 0;
.tile {
height: 6rem;
}
}
@media(max-width: $breakpoint2) {

View File

@@ -16,10 +16,17 @@
<div class="scene-footer">
<img
v-if="scene.entity.parent"
:src="`/img/logos/${scene.entity.parent.slug}/favicon_light.png`"
class="scene-favicon"
>
<img
v-else
:src="`/img/logos/${scene.entity.slug}/favicon_light.png`"
class="scene-favicon"
>
<span class="scene-title">{{ scene.title }}</span>
</div>
</a>

View File

@@ -117,7 +117,6 @@ function mounted() {
});
this.events.on('scroll', () => {
console.log('scroll!');
this.calculate();
});
}

View File

@@ -82,8 +82,8 @@
</template>
<script>
import ActorPreview from './actor-preview.vue';
import ScenePreview from './scene-preview.vue';
import ActorPreview from '../actors/preview.vue';
import ScenePreview from '../releases/scene-preview.vue';
import RemoveStash from '../stashes/remove-stash.vue';
import Toggle from '../form/toggle.vue';