Compare commits
410 Commits
d4919016b6
...
experiment
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
33a327a04b | ||
|
|
a46061e247 | ||
|
|
94e07ff23d | ||
|
|
4811befcf6 | ||
|
|
c455f02c66 | ||
|
|
efc5620a28 | ||
|
|
61123fdb6a | ||
|
|
3ec6911d46 | ||
|
|
2021093645 | ||
|
|
1c72dc202f | ||
|
|
1ef946fa77 | ||
|
|
3b6bbc39ff | ||
|
|
481c9feada | ||
|
|
953b3e9568 | ||
|
|
bdd2e68f49 | ||
|
|
e4cc349302 | ||
|
|
6547b93e55 | ||
|
|
bb9649d23b | ||
|
|
9e2eaef9d1 | ||
|
|
1c3ee75d3b | ||
|
|
15c9af8057 | ||
|
|
295573c1ef | ||
|
|
e93e8ace5c | ||
|
|
43af7ba777 | ||
|
|
0dad5b0d68 | ||
|
|
ae9b793318 | ||
|
|
fd8170f223 | ||
|
|
661b8b716b | ||
|
|
5ff076cac3 | ||
|
|
41c100ac4e | ||
|
|
c6e977f842 | ||
|
|
50b7f521b5 | ||
|
|
f0d0ee3acc | ||
|
|
7b3bdadd44 | ||
|
|
5deba6b90f | ||
|
|
a5afffc968 | ||
|
|
a239a5c593 | ||
|
|
e56e7333e3 | ||
|
|
d55e3c37cd | ||
|
|
97b78ea016 | ||
|
|
3e290b74dc | ||
|
|
65141207ae | ||
|
|
17dfeac1af | ||
|
|
4a9c428d69 | ||
|
|
333f252099 | ||
|
|
38232f258a | ||
|
|
582269cfaa | ||
|
|
0b646429fd | ||
|
|
fecef6c1cf | ||
|
|
73e5404c44 | ||
|
|
690d2bb3ed | ||
|
|
1dd935e1e9 | ||
|
|
8386230f33 | ||
|
|
5a68b06137 | ||
|
|
5918364cf5 | ||
|
|
df4d860d35 | ||
|
|
d9f0db6e3c | ||
|
|
7372b560b2 | ||
|
|
9d638c685c | ||
|
|
5da1acc38d | ||
|
|
c7c62e220d | ||
|
|
9edd652a2c | ||
|
|
cde760c1ea | ||
|
|
19c7f958e1 | ||
|
|
c7710afdbf | ||
|
|
ba18a3dadb | ||
|
|
496c29e569 | ||
|
|
98a72a4929 | ||
|
|
c4f0b48932 | ||
|
|
93abbab873 | ||
|
|
f7bbab08bd | ||
|
|
1e4ddf2bbf | ||
|
|
9b7879bff7 | ||
|
|
03d96d4dec | ||
|
|
1dfa034332 | ||
|
|
196a1d33e3 | ||
|
|
3d349c413f | ||
|
|
bd5babd37e | ||
|
|
fffe9080f3 | ||
|
|
5630b16e99 | ||
|
|
4bcf7ef45b | ||
|
|
9abc1d48ac | ||
|
|
ceaf5a3217 | ||
|
|
36d84fb98b | ||
|
|
3d80e9d77b | ||
|
|
1dc38ffacc | ||
|
|
ee210b5c68 | ||
|
|
3aae3cd3a1 | ||
|
|
f1a7f2f905 | ||
|
|
0b7e0319f1 | ||
|
|
10b8abf706 | ||
|
|
b163223ff1 | ||
|
|
b9f3eb85f9 | ||
|
|
285a65f018 | ||
|
|
815d56d334 | ||
|
|
27a9a233e2 | ||
|
|
22864105ac | ||
|
|
7cf47cbf8d | ||
|
|
502c588958 | ||
|
|
81792a7c2f | ||
|
|
784d326584 | ||
|
|
83dc7aa578 | ||
|
|
5a3b27fbf7 | ||
|
|
5ff83a9497 | ||
|
|
07cb39c756 | ||
|
|
fffd54995d | ||
|
|
5302176f0b | ||
|
|
efd7cf8acc | ||
|
|
52218c30f8 | ||
|
|
04c176fa8b | ||
|
|
af320684b4 | ||
|
|
b2ba14ce45 | ||
|
|
d776cc2f71 | ||
|
|
b4bed2f623 | ||
|
|
5c5a11fdca | ||
|
|
a22c62d893 | ||
|
|
90bc2f3724 | ||
|
|
8cdc794a3a | ||
|
|
250618df8b | ||
|
|
b62ae00c54 | ||
|
|
67d0a9e0e0 | ||
| b4425bc3bb | |||
| d77c145de3 | |||
|
|
6753f923d9 | ||
|
|
452f725332 | ||
|
|
7c1f45bcfb | ||
|
|
11ea54f6e8 | ||
|
|
5b623ee46f | ||
|
|
7ceec1c739 | ||
|
|
fc318536aa | ||
|
|
cd8f690ac6 | ||
|
|
240a8e86fd | ||
|
|
c557f0f1f0 | ||
|
|
86f56695e2 | ||
|
|
20da2d1cf6 | ||
|
|
372db86927 | ||
|
|
4c503a3177 | ||
|
|
57d46f5842 | ||
|
|
7f11d0ca91 | ||
|
|
bbd788699e | ||
|
|
e24add98ea | ||
|
|
506971b44b | ||
|
|
43a0bc8a2c | ||
|
|
baebdbc0bb | ||
|
|
1c24cae3b1 | ||
|
|
b9146aee5f | ||
|
|
09bfcb01f5 | ||
|
|
5d80b6dc21 | ||
|
|
140a38c349 | ||
|
|
6760c11881 | ||
|
|
2dcdbf9c37 | ||
|
|
3b958a02ef | ||
|
|
605242b399 | ||
|
|
299f257a01 | ||
|
|
0ad64ef5f4 | ||
|
|
3bfe1e8fdb | ||
|
|
7d992f4129 | ||
|
|
280032937f | ||
|
|
d93670842b | ||
|
|
dc187a9a3a | ||
|
|
5f89c6e14c | ||
|
|
9f10420eb9 | ||
|
|
9568370132 | ||
|
|
1c0c30be11 | ||
|
|
cdb7dcd6e9 | ||
|
|
8e4be06675 | ||
|
|
de7a8c756a | ||
|
|
65e2b72c6a | ||
|
|
523c36ecd4 | ||
|
|
5b5688438c | ||
|
|
4ca07631c7 | ||
|
|
ec4e7bc12a | ||
|
|
26d15c0a6f | ||
|
|
a484396db4 | ||
|
|
681db02784 | ||
|
|
e88554666c | ||
|
|
9f37ec4cff | ||
|
|
abd063a578 | ||
|
|
883e57ce1f | ||
|
|
ad04502a8c | ||
|
|
f65ed16842 | ||
|
|
1918dd4ad5 | ||
|
|
3983d3464f | ||
|
|
61daf5f037 | ||
|
|
eafa144104 | ||
|
|
60c151ff6a | ||
|
|
c97d18fcf9 | ||
|
|
e41f9fa937 | ||
|
|
56a7fb0ad9 | ||
|
|
e29cbc9fea | ||
|
|
08f725a0b6 | ||
|
|
b93a5715cb | ||
|
|
2018d5c298 | ||
|
|
87094a9498 | ||
|
|
53a1c697d0 | ||
|
|
f04eb72891 | ||
|
|
dfeb0c08cf | ||
|
|
9d7183ac69 | ||
|
|
2539e88f47 | ||
|
|
20d0d860d3 | ||
|
|
6b4aa64d74 | ||
|
|
c7b9ec7a4b | ||
|
|
e81864ebde | ||
|
|
0305a22a24 | ||
|
|
a406eb584d | ||
|
|
2be3ea9bbb | ||
|
|
0a8a7ff9a5 | ||
|
|
98c103c625 | ||
|
|
e0908a5f5e | ||
|
|
f0b7678444 | ||
|
|
64e9efe095 | ||
|
|
ab29ab053e | ||
|
|
a5081bc7c9 | ||
|
|
da9c628f9b | ||
|
|
26539b74a5 | ||
|
|
ccb99e278c | ||
|
|
a867817dc1 | ||
|
|
d1e05915b5 | ||
|
|
b764fdec85 | ||
|
|
8322d43b09 | ||
|
|
29b8c5e38e | ||
|
|
83dd233991 | ||
|
|
0864154a0e | ||
|
|
53357d4bd2 | ||
|
|
69bf98edf1 | ||
|
|
a22c4d5679 | ||
|
|
e5ad1648eb | ||
|
|
100a35b4e8 | ||
|
|
6c5d4389fe | ||
|
|
92f9ff4104 | ||
|
|
0e4fd12d70 | ||
|
|
9040285ce5 | ||
|
|
972b15e72d | ||
|
|
c1aea78496 | ||
|
|
49f891ba44 | ||
|
|
239057f1b6 | ||
|
|
167df35d37 | ||
|
|
b1b2ad2111 | ||
|
|
f1ff662be2 | ||
|
|
2418fec9c9 | ||
|
|
aaaa0a6afb | ||
|
|
afe22003e1 | ||
|
|
b6ad2903f3 | ||
|
|
6c298cd639 | ||
|
|
dbff3e9539 | ||
|
|
fcc6b33d07 | ||
|
|
0224c26ca0 | ||
|
|
294f15e694 | ||
|
|
522584711b | ||
|
|
ae40f33283 | ||
|
|
172e01c80e | ||
|
|
901acf1390 | ||
|
|
a316da3083 | ||
|
|
0b46e27497 | ||
|
|
b164c5dad7 | ||
|
|
2d6b285817 | ||
|
|
65c79567d2 | ||
|
|
d3633f31ac | ||
|
|
73a9a11aa6 | ||
|
|
c21bd8b01e | ||
|
|
14f4d24872 | ||
|
|
731abc79ee | ||
|
|
d542889827 | ||
|
|
8c5ef21459 | ||
|
|
7bfca9596d | ||
|
|
7bd858f96b | ||
|
|
b8657cb6e6 | ||
|
|
69a7e8f13d | ||
|
|
23537e0243 | ||
|
|
546f778b1b | ||
|
|
245f69a122 | ||
|
|
c6f3f7a239 | ||
|
|
f0a6e80e5a | ||
|
|
0cbb985945 | ||
|
|
bb384029ac | ||
|
|
85c6b581cb | ||
|
|
dd18644796 | ||
|
|
1628e41d09 | ||
|
|
a77d8f4cea | ||
|
|
0a4152b4b7 | ||
|
|
3fc4dc15df | ||
|
|
4ee0dcef9b | ||
|
|
011482ac9d | ||
|
|
6a8c9d89cb | ||
|
|
e0905ab8fc | ||
|
|
eb1f8f86fd | ||
|
|
959b5d9d0e | ||
|
|
0c19a026ef | ||
|
|
b24973eb19 | ||
|
|
4b18867883 | ||
|
|
5e292a0880 | ||
|
|
f00e37490c | ||
|
|
715e44cf21 | ||
|
|
25d1c1b229 | ||
|
|
094226eeb5 | ||
|
|
b08cb46ae5 | ||
|
|
6b88cf1040 | ||
|
|
aacfd1b29d | ||
|
|
0427e1e276 | ||
|
|
ee33bd8e63 | ||
|
|
25b8bd689e | ||
|
|
b6468b03a7 | ||
|
|
a848d6991b | ||
|
|
65c3053b49 | ||
|
|
1d686d7e40 | ||
|
|
488d1082e4 | ||
|
|
8967907893 | ||
|
|
e527a67dc1 | ||
|
|
6847ef690c | ||
|
|
96a2125248 | ||
|
|
c5e4310a6b | ||
|
|
23b41fc4f3 | ||
|
|
4fb41a4c35 | ||
|
|
12c0e8e828 | ||
|
|
dce27e985e | ||
|
|
0a343dfa98 | ||
|
|
729ca0f968 | ||
|
|
385dfb9f75 | ||
|
|
afbae24f43 | ||
|
|
eb7009832a | ||
|
|
d1480da076 | ||
|
|
10a2731caf | ||
|
|
ab1329dd67 | ||
|
|
e9a0700742 | ||
|
|
3f473589ad | ||
|
|
7a44c7aaaa | ||
|
|
bed329cd8c | ||
|
|
011bb4efa3 | ||
|
|
c979173422 | ||
|
|
42791c528e | ||
|
|
c76c8054b9 | ||
|
|
6107c7d0ef | ||
|
|
d0d045a2ab | ||
|
|
102e053021 | ||
|
|
ca0660c1cc | ||
|
|
a4a05232db | ||
|
|
0f8d5d4456 | ||
|
|
478a2c4b48 | ||
|
|
55e240e68d | ||
|
|
71b25774d0 | ||
|
|
1697728b2a | ||
|
|
4242efbd4c | ||
|
|
0b825a61bb | ||
|
|
7d974e6b89 | ||
|
|
ae1b9c0d73 | ||
|
|
91ebcace0a | ||
|
|
846b860c06 | ||
|
|
83ed793e39 | ||
|
|
3f55b90ab8 | ||
|
|
4806b0aa41 | ||
|
|
3b91493995 | ||
|
|
8bf9fff7dc | ||
|
|
eed563e06f | ||
|
|
fc1c2fc2f3 | ||
|
|
f8a3bf6a64 | ||
|
|
c5e74c33b7 | ||
|
|
95f3b1c03a | ||
|
|
0773a8019c | ||
|
|
1116e09af5 | ||
|
|
52e215d3bc | ||
|
|
7f25846d55 | ||
|
|
d36e52d5d1 | ||
|
|
da0cbced15 | ||
|
|
837fc98ad2 | ||
|
|
a0f41da80a | ||
|
|
4a2d2ad996 | ||
|
|
010da8954b | ||
|
|
e643e0a924 | ||
|
|
c386a9098f | ||
|
|
d17dbf1b36 | ||
|
|
3961f83ef6 | ||
|
|
d0648b5006 | ||
|
|
337b6c70f4 | ||
|
|
2cd007dae3 | ||
|
|
43e55446f7 | ||
|
|
f47be86df3 | ||
|
|
d5bf253011 | ||
|
|
093d447328 | ||
|
|
e12de5ec00 | ||
|
|
a64b25eb51 | ||
|
|
bbf058480f | ||
|
|
b2105c8fb0 | ||
|
|
8ff5a8c5e1 | ||
|
|
98624c9954 | ||
|
|
3b9e8e3cc3 | ||
|
|
193af9bab5 | ||
|
|
b2ad031c54 | ||
|
|
0419cc633b | ||
|
|
63143fb185 | ||
|
|
3f6ecfd92c | ||
|
|
a208c922f7 | ||
|
|
757d554e7b | ||
|
|
74afc55dc6 | ||
|
|
29b0451608 | ||
|
|
cfa1ed7a61 | ||
|
|
c70f500acc | ||
|
|
76a5ccf3f6 | ||
|
|
35c28dede2 | ||
|
|
11e043ca2e | ||
|
|
7ac64c57ae | ||
|
|
9ff70e5578 | ||
|
|
348aa91832 | ||
|
|
de5d104e1e | ||
|
|
565cf551f0 | ||
|
|
819d53fc2b | ||
|
|
67f22a6e08 | ||
|
|
eee47111a6 | ||
|
|
07643870cd | ||
|
|
bb949e0a3b | ||
|
|
a7cf3f689e |
@@ -2,7 +2,7 @@
|
||||
"root": true,
|
||||
"extends": ["airbnb-base", "plugin:vue/recommended"],
|
||||
"parserOptions": {
|
||||
"parser": "babel-eslint",
|
||||
"parser": "@babel/eslint-parser",
|
||||
"ecmaVersion": 2019,
|
||||
"sourceType": "module"
|
||||
},
|
||||
@@ -17,10 +17,11 @@
|
||||
"vue/html-indent": ["error", "tab"],
|
||||
"vue/multiline-html-element-content-newline": 0,
|
||||
"vue/singleline-html-element-content-newline": 0,
|
||||
"vue/multi-word-component-names": 0,
|
||||
"no-param-reassign": ["error", {
|
||||
"props": true,
|
||||
"ignorePropertyModificationsFor": ["state", "acc"]
|
||||
}],
|
||||
}]
|
||||
},
|
||||
"globals": {
|
||||
"CONFIG": true
|
||||
|
||||
3
.gitignore
vendored
@@ -2,9 +2,12 @@ node_modules/
|
||||
dist/
|
||||
log/
|
||||
media/
|
||||
html/
|
||||
public/js/*
|
||||
public/css/*
|
||||
config/*
|
||||
!config/default.js
|
||||
assets/js/config/
|
||||
!assets/js/config/default.js
|
||||
*.heapprofile
|
||||
*.heapsnapshot
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="actor-header">
|
||||
<h2 class="header-name">
|
||||
<span v-if="actor.entity">{{ actor.name }} ({{ actor.entity.name }})</span>
|
||||
<span v-else="">{{ actor.name }}</span>
|
||||
<span v-else>{{ actor.name }}</span>
|
||||
|
||||
<Gender
|
||||
:gender="actor.gender"
|
||||
@@ -34,22 +34,18 @@
|
||||
class="header-social"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-show="me && isStashed"
|
||||
icon="heart7"
|
||||
class="stash stashed noselect"
|
||||
@click="unstashActor"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-show="me && !isStashed"
|
||||
icon="heart8"
|
||||
class="stash unstashed noselect"
|
||||
@click="stashActor"
|
||||
<StashButton
|
||||
:stashed-by="stashedBy"
|
||||
class="actor-stash light"
|
||||
@stash="(stash) => stashActor(stash)"
|
||||
@unstash="(stash) => unstashActor(stash)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="content-inner actor-inner">
|
||||
<div
|
||||
class="content-inner actor-inner"
|
||||
@scroll="events.emit('scroll', $event)"
|
||||
>
|
||||
<div
|
||||
class="profile"
|
||||
:class="{ expanded: bioExpanded, 'with-avatar': !!actor.avatar }"
|
||||
@@ -365,7 +361,10 @@
|
||||
:available-actors="actor.actors"
|
||||
/>
|
||||
|
||||
<Releases :releases="releases" />
|
||||
<Releases
|
||||
:releases="releases"
|
||||
:done="done"
|
||||
/>
|
||||
|
||||
<Pagination
|
||||
:items-total="totalCount"
|
||||
@@ -390,8 +389,11 @@ import Expand from '../expand/expand.vue';
|
||||
import Scroll from '../scroll/scroll.vue';
|
||||
import Gender from './gender.vue';
|
||||
import Social from './social.vue';
|
||||
import StashButton from '../stashes/button.vue';
|
||||
|
||||
async function fetchActor(scroll = true) {
|
||||
this.done = false;
|
||||
|
||||
const { actor, releases, totalCount } = await this.$store.dispatch('fetchActorById', {
|
||||
actorId: Number(this.$route.params.actorId),
|
||||
limit: this.limit,
|
||||
@@ -402,38 +404,32 @@ async function fetchActor(scroll = true) {
|
||||
this.actor = actor;
|
||||
this.releases = releases;
|
||||
this.totalCount = totalCount;
|
||||
this.stashedBy = actor.stashes;
|
||||
this.done = true;
|
||||
|
||||
if (this.$refs.filter && scroll) {
|
||||
this.$refs.filter.$el.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
async function stashActor() {
|
||||
this.$store.dispatch('stashActor', {
|
||||
async function stashActor(stashId) {
|
||||
this.stashedBy = await this.$store.dispatch('stashActor', {
|
||||
actorId: this.actor.id,
|
||||
stashId: this.$store.getters.favorites.id,
|
||||
stashId,
|
||||
});
|
||||
|
||||
this.fetchActor(false);
|
||||
}
|
||||
|
||||
async function unstashActor() {
|
||||
this.$store.dispatch('unstashActor', {
|
||||
async function unstashActor(stashId) {
|
||||
this.stashedBy = await this.$store.dispatch('unstashActor', {
|
||||
actorId: this.actor.id,
|
||||
stashId: this.$store.getters.favorites.id,
|
||||
stashId,
|
||||
});
|
||||
|
||||
this.fetchActor(false);
|
||||
}
|
||||
|
||||
function me() {
|
||||
return this.$store.state.auth.user;
|
||||
}
|
||||
|
||||
function isStashed() {
|
||||
return this.actor.stashes?.length > 0;
|
||||
}
|
||||
|
||||
function sfw() {
|
||||
return this.$store.state.ui.sfw;
|
||||
}
|
||||
@@ -467,20 +463,23 @@ export default {
|
||||
Releases,
|
||||
Gender,
|
||||
Social,
|
||||
StashButton,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actor: null,
|
||||
releases: null,
|
||||
done: false,
|
||||
totalCount: 0,
|
||||
limit: 20,
|
||||
pageTitle: null,
|
||||
bioExpanded: false,
|
||||
photosExpanded: false,
|
||||
stashed: false,
|
||||
stashedBy: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isStashed,
|
||||
me,
|
||||
sfw,
|
||||
showAlbum,
|
||||
@@ -516,6 +515,10 @@ export default {
|
||||
background: var(--profile);
|
||||
}
|
||||
|
||||
.actor-stash {
|
||||
margin: 0 1rem 0 0;
|
||||
}
|
||||
|
||||
.header-name {
|
||||
padding: .5rem 1rem;
|
||||
margin: 0;
|
||||
@@ -834,6 +837,10 @@ export default {
|
||||
.expand {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.actor-stash {
|
||||
margin: 0 .5rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint0) {
|
||||
|
||||
@@ -1,216 +1,246 @@
|
||||
<template>
|
||||
<div class="actors">
|
||||
<div
|
||||
ref="content"
|
||||
class="actors"
|
||||
>
|
||||
<nav
|
||||
ref="filter"
|
||||
class="filter"
|
||||
ref="filters"
|
||||
class="filters"
|
||||
>
|
||||
<ul class="genders nolist">
|
||||
<li class="gender">
|
||||
<router-link
|
||||
:to="{ name: 'actors', params: { gender: 'all', letter, pageNumber: 1 } }"
|
||||
:class="{ selected: gender === 'all' }"
|
||||
class="gender-link all"
|
||||
>all</router-link>
|
||||
</li>
|
||||
<li class="gender">
|
||||
<router-link
|
||||
:to="{ name: 'actors', params: { gender: 'female', letter, pageNumber: 1 } }"
|
||||
:class="{ selected: gender === 'female' }"
|
||||
class="gender-link female"
|
||||
><Gender gender="female" /></router-link>
|
||||
</li>
|
||||
<li class="gender">
|
||||
<router-link
|
||||
:to="{ name: 'actors', params: { gender: 'male', letter, pageNumber: 1 } }"
|
||||
:class="{ selected: gender === 'male' }"
|
||||
class="gender-link male"
|
||||
replace
|
||||
><Gender gender="male" /></router-link>
|
||||
</li>
|
||||
<li class="gender">
|
||||
<router-link
|
||||
:to="{ name: 'actors', params: { gender: 'trans', letter, pageNumber: 1 } }"
|
||||
:class="{ selected: gender === 'trans' }"
|
||||
class="gender-link transsexual"
|
||||
replace
|
||||
><Gender gender="transsexual" /></router-link>
|
||||
</li>
|
||||
<li class="gender">
|
||||
<router-link
|
||||
:to="{ name: 'actors', params: { gender: 'other', letter, pageNumber: 1 } }"
|
||||
:class="{ selected: gender === 'other' }"
|
||||
class="gender-link other"
|
||||
replace
|
||||
><Icon icon="question5" /></router-link>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="filters-row">
|
||||
<ul class="genders nolist">
|
||||
<li class="gender">
|
||||
<router-link
|
||||
:to="{ name: 'actors', params: { gender: 'all', pageNumber: 1 }, query: $route.query }"
|
||||
:class="{ selected: gender === 'all' }"
|
||||
class="gender-link all"
|
||||
>all</router-link>
|
||||
</li>
|
||||
<li class="gender">
|
||||
<router-link
|
||||
:to="{ name: 'actors', params: { gender: 'female', pageNumber: 1 }, query: $route.query }"
|
||||
:class="{ selected: gender === 'female' }"
|
||||
class="gender-link female"
|
||||
><Gender gender="female" /></router-link>
|
||||
</li>
|
||||
<li class="gender">
|
||||
<router-link
|
||||
:to="{ name: 'actors', params: { gender: 'male', pageNumber: 1 }, query: $route.query }"
|
||||
:class="{ selected: gender === 'male' }"
|
||||
class="gender-link male"
|
||||
replace
|
||||
><Gender gender="male" /></router-link>
|
||||
</li>
|
||||
<li class="gender">
|
||||
<router-link
|
||||
:to="{ name: 'actors', params: { gender: 'trans', pageNumber: 1 }, query: $route.query }"
|
||||
:class="{ selected: gender === 'trans' }"
|
||||
class="gender-link transsexual"
|
||||
replace
|
||||
><Gender gender="transsexual" /></router-link>
|
||||
</li>
|
||||
<li class="gender">
|
||||
<router-link
|
||||
:to="{ name: 'actors', params: { gender: 'other', pageNumber: 1 }, query: $route.query }"
|
||||
:class="{ selected: gender === 'other' }"
|
||||
class="gender-link other"
|
||||
replace
|
||||
><Icon icon="question5" /></router-link>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="letters nolist">
|
||||
<li
|
||||
v-for="letterX in letters"
|
||||
:key="letterX"
|
||||
class="letter"
|
||||
>
|
||||
<router-link
|
||||
:to="{ name: 'actors', params: { gender, letter: letterX, pageNumber: 1 } }"
|
||||
:class="{ selected: letterX === letter }"
|
||||
class="letter-link"
|
||||
replace
|
||||
>{{ letterX || 'All' }}</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<ul class="filters-attributes nolist">
|
||||
<li>
|
||||
<Tooltip class="filter boobs">
|
||||
<span
|
||||
class="filter-trigger"
|
||||
:class="{ enabled: ageRequired }"
|
||||
><Icon icon="vcard" />Age</span>
|
||||
|
||||
<nav class="filter">
|
||||
<ul class="nolist">
|
||||
<li>
|
||||
<Tooltip class="filter boobs">
|
||||
<span
|
||||
class="filter-trigger"
|
||||
:class="{ enabled: ageRequired }"
|
||||
><Icon icon="vcard" />Age</span>
|
||||
|
||||
<template v-slot:tooltip>
|
||||
<RangeFilter
|
||||
label="age"
|
||||
:min="18"
|
||||
:max="100"
|
||||
:value="age"
|
||||
:disabled="!ageRequired"
|
||||
@enable="(checked) => updateValue('ageRequired', checked, true)"
|
||||
@input="(range) => updateValue('age', range, false)"
|
||||
@change="(range) => updateValue('age', range, true)"
|
||||
>
|
||||
<template v-slot:start><Icon icon="leaf" /></template>
|
||||
<template v-slot:end><Icon icon="tree3" /></template>
|
||||
</RangeFilter>
|
||||
|
||||
<div class="filter-section">
|
||||
<label class="filter-label">
|
||||
<span class="label">
|
||||
<Checkbox
|
||||
:checked="dobRequired"
|
||||
class="checkbox"
|
||||
@change="(checked) => updateValue('dobRequired', checked, true)"
|
||||
/>Date of birth
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div
|
||||
class="input-container"
|
||||
@click="() => updateValue('dobRequired', true, true)"
|
||||
<template v-slot:tooltip>
|
||||
<RangeFilter
|
||||
label="age"
|
||||
:min="18"
|
||||
:max="100"
|
||||
:value="age"
|
||||
:disabled="!ageRequired"
|
||||
@enable="(checked) => updateValue('ageRequired', checked, true)"
|
||||
@input="(range) => updateValue('age', range, false)"
|
||||
@change="(range) => updateValue('age', range, true)"
|
||||
>
|
||||
<input
|
||||
v-model="dob"
|
||||
:disabled="!dobRequired"
|
||||
type="date"
|
||||
class="input"
|
||||
@change="updateFilters"
|
||||
<template v-slot:start><Icon icon="leaf" /></template>
|
||||
<template v-slot:end><Icon icon="tree3" /></template>
|
||||
</RangeFilter>
|
||||
|
||||
<div class="filter-section">
|
||||
<label class="filter-label">
|
||||
<span class="label">
|
||||
<Checkbox
|
||||
:checked="dobRequired"
|
||||
class="checkbox"
|
||||
@change="(checked) => updateValue('dobRequired', checked, true)"
|
||||
/>Date of birth
|
||||
</span>
|
||||
</label>
|
||||
|
||||
<div
|
||||
class="input-container"
|
||||
@click="() => updateValue('dobRequired', true, true)"
|
||||
>
|
||||
<input
|
||||
v-model="dob"
|
||||
:disabled="!dobRequired"
|
||||
type="date"
|
||||
class="input"
|
||||
@change="updateFilters"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Tooltip>
|
||||
</li>
|
||||
</template>
|
||||
</Tooltip>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Tooltip class="filter">
|
||||
<span
|
||||
class="filter-trigger boobs"
|
||||
:class="{ enabled: boobSizeRequired || naturalBoobs !== 1 }"
|
||||
><Icon icon="boobs" />Boobs</span>
|
||||
<li>
|
||||
<Tooltip class="filter">
|
||||
<span
|
||||
class="filter-trigger boobs"
|
||||
:class="{ enabled: boobSizeRequired || naturalBoobs !== 1 }"
|
||||
><Icon icon="boobs" />Boobs</span>
|
||||
|
||||
<template v-slot:tooltip>
|
||||
<RangeFilter
|
||||
label="size"
|
||||
:min="0"
|
||||
:max="boobSizes.length - 1"
|
||||
:value="boobSize"
|
||||
:values="boobSizes"
|
||||
:disabled="!boobSizeRequired"
|
||||
@enable="(checked) => updateValue('boobSizeRequired', checked, true)"
|
||||
@input="(range) => updateValue('boobSize', range, false)"
|
||||
@change="(range) => updateValue('boobSize', range, true)"
|
||||
>
|
||||
<template v-slot:start><Icon icon="boobs-small" /></template>
|
||||
<template v-slot:end><Icon icon="boobs-big" /></template>
|
||||
</RangeFilter>
|
||||
|
||||
<div class="filter-section">
|
||||
<span class="filter-label">Enhanced</span>
|
||||
|
||||
<span
|
||||
:class="{ [['off', 'default', 'on'][naturalBoobs]]: true }"
|
||||
class="toggle-container noclick"
|
||||
<template v-slot:tooltip>
|
||||
<RangeFilter
|
||||
label="size"
|
||||
:min="0"
|
||||
:max="boobSizes.length - 1"
|
||||
:value="boobSize"
|
||||
:values="boobSizes"
|
||||
:disabled="!boobSizeRequired"
|
||||
@enable="(checked) => updateValue('boobSizeRequired', checked, true)"
|
||||
@input="(range) => updateValue('boobSize', range, false)"
|
||||
@change="(range) => updateValue('boobSize', range, true)"
|
||||
>
|
||||
<span
|
||||
class="toggle-label off"
|
||||
@click="updateValue('naturalBoobs', 0)"
|
||||
><Icon icon="leaf" /></span>
|
||||
<template v-slot:start><Icon icon="boobs-small" /></template>
|
||||
<template v-slot:end><Icon icon="boobs-big" /></template>
|
||||
</RangeFilter>
|
||||
|
||||
<input
|
||||
v-model.number="naturalBoobs"
|
||||
class="toggle"
|
||||
type="range"
|
||||
min="0"
|
||||
max="2"
|
||||
@change="updateFilters"
|
||||
<div class="filter-section">
|
||||
<span class="filter-label">Enhanced</span>
|
||||
|
||||
<span
|
||||
:class="{ [['off', 'default', 'on'][naturalBoobs]]: true }"
|
||||
class="toggle-container noclick"
|
||||
>
|
||||
<span
|
||||
class="toggle-label off"
|
||||
@click="updateValue('naturalBoobs', 0)"
|
||||
><Icon icon="leaf" /></span>
|
||||
|
||||
<span
|
||||
class="toggle-label on"
|
||||
@click="updateValue('naturalBoobs', 2)"
|
||||
><Icon icon="magic-wand2" /></span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</Tooltip>
|
||||
</li>
|
||||
<input
|
||||
v-model.number="naturalBoobs"
|
||||
class="toggle"
|
||||
type="range"
|
||||
min="0"
|
||||
max="2"
|
||||
@change="updateFilters"
|
||||
>
|
||||
|
||||
<li>
|
||||
<Tooltip class="filter boobs">
|
||||
<span
|
||||
class="filter-trigger"
|
||||
:class="{ enabled: heightRequired || weightRequired }"
|
||||
><Icon icon="rulers" />Physique</span>
|
||||
<span
|
||||
class="toggle-label on"
|
||||
@click="updateValue('naturalBoobs', 2)"
|
||||
><Icon icon="magic-wand2" /></span>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
</Tooltip>
|
||||
</li>
|
||||
|
||||
<template v-slot:tooltip>
|
||||
<RangeFilter
|
||||
label="height"
|
||||
:min="50"
|
||||
:max="220"
|
||||
:value="height"
|
||||
:disabled="!heightRequired"
|
||||
unit="cm"
|
||||
@enable="(checked) => updateValue('heightRequired', checked, true)"
|
||||
@input="(range) => updateValue('height', range, false)"
|
||||
@change="(range) => updateValue('height', range, true)"
|
||||
>
|
||||
<template v-slot:start><Icon icon="height-short" /></template>
|
||||
<template v-slot:end><Icon icon="height" /></template>
|
||||
</RangeFilter>
|
||||
<li>
|
||||
<Tooltip class="filter boobs">
|
||||
<span
|
||||
class="filter-trigger"
|
||||
:class="{ enabled: heightRequired || weightRequired }"
|
||||
><Icon icon="rulers" />Physique</span>
|
||||
|
||||
<RangeFilter
|
||||
label="weight"
|
||||
:min="30"
|
||||
:max="200"
|
||||
:value="weight"
|
||||
:disabled="!weightRequired"
|
||||
unit="kg"
|
||||
@enable="(checked) => updateValue('weightRequired', checked, true)"
|
||||
@input="(range) => updateValue('weight', range, false)"
|
||||
@change="(range) => updateValue('weight', range, true)"
|
||||
>
|
||||
<template v-slot:start><Icon icon="meter-slow" /></template>
|
||||
<template v-slot:end><Icon icon="meter-fast" /></template>
|
||||
</RangeFilter>
|
||||
</template>
|
||||
</Tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
<template v-slot:tooltip>
|
||||
<RangeFilter
|
||||
label="height"
|
||||
:min="50"
|
||||
:max="220"
|
||||
:value="height"
|
||||
:disabled="!heightRequired"
|
||||
unit="cm"
|
||||
@enable="(checked) => updateValue('heightRequired', checked, true)"
|
||||
@input="(range) => updateValue('height', range, false)"
|
||||
@change="(range) => updateValue('height', range, true)"
|
||||
>
|
||||
<template v-slot:start><Icon icon="height-short" /></template>
|
||||
<template v-slot:end><Icon icon="height" /></template>
|
||||
</RangeFilter>
|
||||
|
||||
<RangeFilter
|
||||
label="weight"
|
||||
:min="30"
|
||||
:max="200"
|
||||
:value="weight"
|
||||
:disabled="!weightRequired"
|
||||
unit="kg"
|
||||
@enable="(checked) => updateValue('weightRequired', checked, true)"
|
||||
@input="(range) => updateValue('weight', range, false)"
|
||||
@change="(range) => updateValue('weight', range, true)"
|
||||
>
|
||||
<template v-slot:start><Icon icon="meter-slow" /></template>
|
||||
<template v-slot:end><Icon icon="meter-fast" /></template>
|
||||
</RangeFilter>
|
||||
</template>
|
||||
</Tooltip>
|
||||
</li>
|
||||
|
||||
<li>
|
||||
<Tooltip class="filter">
|
||||
<span
|
||||
:class="{ enabled: country }"
|
||||
class="filter-trigger"
|
||||
><img
|
||||
v-if="$route.query.c"
|
||||
:src="`/img/flags/${$route.query.c.toLowerCase()}.svg`"
|
||||
class="flag"
|
||||
><Icon
|
||||
v-else
|
||||
icon="earth2"
|
||||
/>Country</span>
|
||||
|
||||
<template v-slot:tooltip>
|
||||
<input
|
||||
v-model="countryQuery"
|
||||
placeholder="Search"
|
||||
class="input"
|
||||
>
|
||||
|
||||
<Countries
|
||||
v-if="!countryQuery"
|
||||
:countries="topCountries"
|
||||
:selected-country="country"
|
||||
:update-value="updateValue"
|
||||
/>
|
||||
|
||||
<Countries
|
||||
:countries="filteredCountries"
|
||||
:selected-country="country"
|
||||
:update-value="updateValue"
|
||||
/>
|
||||
</template>
|
||||
</Tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<SearchBar :placeholder="`Search ${totalCount} actors`" />
|
||||
</nav>
|
||||
|
||||
<div class="tiles">
|
||||
<div
|
||||
ref="tiles"
|
||||
class="tiles"
|
||||
>
|
||||
<Actor
|
||||
v-for="actor in actors"
|
||||
:key="`actor-${actor.id}`"
|
||||
@@ -235,11 +265,14 @@ import dayjs from 'dayjs';
|
||||
import Actor from './tile.vue';
|
||||
import Gender from './gender.vue';
|
||||
import Checkbox from '../form/checkbox.vue';
|
||||
import Countries from './countries.vue';
|
||||
import RangeFilter from './filter-range.vue';
|
||||
import SearchBar from '../search/bar.vue';
|
||||
import Pagination from '../pagination/pagination.vue';
|
||||
|
||||
const toggleValues = [true, null, false];
|
||||
const boobSizes = 'ABCDEFGHZ'.split('');
|
||||
const boobSizes = 'ABCDEFGHIJKZ'.split('');
|
||||
const topCountries = ['AU', 'BR', 'CZ', 'DE', 'JP', 'RU', 'GB', 'US'];
|
||||
|
||||
function updateFilters() {
|
||||
this.$router.push({
|
||||
@@ -247,15 +280,16 @@ function updateFilters() {
|
||||
params: {
|
||||
pageNumber: 1,
|
||||
gender: this.gender,
|
||||
letter: this.letter,
|
||||
},
|
||||
query: {
|
||||
nb: this.naturalBoobs !== 1 ? this.naturalBoobs : undefined,
|
||||
bs: this.boobSizeRequired ? this.boobSize.join(',') : undefined,
|
||||
h: this.heightRequired ? this.height.join(',') : undefined,
|
||||
w: this.weightRequired ? this.weight.join(',') : undefined,
|
||||
c: this.country ? this.country : undefined,
|
||||
age: this.ageRequired ? this.age.join(',') : undefined,
|
||||
dob: this.dobRequired ? this.dob : undefined,
|
||||
query: this.$route.query.query,
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -271,29 +305,39 @@ function updateValue(prop, value, load = true) {
|
||||
async function fetchActors(scroll) {
|
||||
const curatedGender = this.gender.replace('trans', 'transsexual');
|
||||
|
||||
const { actors, totalCount } = await this.$store.dispatch('fetchActors', {
|
||||
const { actors, countries, totalCount } = await this.$store.dispatch('fetchActors', {
|
||||
limit: this.limit,
|
||||
pageNumber: Number(this.$route.params.pageNumber) || 1,
|
||||
letter: this.letter.replace('all', ''),
|
||||
query: this.$route.query.query,
|
||||
gender: curatedGender === 'other' ? null : curatedGender,
|
||||
age: this.ageRequired && this.age,
|
||||
dob: this.dobRequired && this.dob,
|
||||
boobSize: this.boobSizeRequired && this.boobSize,
|
||||
country: this.country,
|
||||
naturalBoobs: toggleValues[this.naturalBoobs] ?? null,
|
||||
height: this.heightRequired && this.height,
|
||||
weight: this.weightRequired && this.weight,
|
||||
});
|
||||
|
||||
const countriesByAlpha2 = countries.reduce((acc, country) => ({ ...acc, [country.alpha2]: country }), {});
|
||||
|
||||
this.actors = actors;
|
||||
this.totalCount = totalCount;
|
||||
|
||||
this.countries = countries;
|
||||
this.topCountries = [...(this.country && !topCountries.includes(this.country) ? [this.country] : []), ...topCountries].map(alpha2 => countriesByAlpha2[alpha2]);
|
||||
|
||||
if (scroll) {
|
||||
this.$refs.filter?.scrollIntoView();
|
||||
this.$refs.tiles?.scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
function letter() {
|
||||
return this.$route.params.letter || 'all';
|
||||
function filteredCountries() {
|
||||
const countryQueryExpression = new RegExp(this.countryQuery, 'i');
|
||||
|
||||
return this.countryQuery?.length > 0
|
||||
? this.countries.filter(country => countryQueryExpression.test(country.name) || countryQueryExpression.test(country.alpha2))
|
||||
: this.countries;
|
||||
}
|
||||
|
||||
function gender() {
|
||||
@@ -302,8 +346,7 @@ function gender() {
|
||||
|
||||
async function route(to, from) {
|
||||
const scroll = to.params.pageNumber !== from.params.pageNumber
|
||||
|| to.params.gender !== from.params.gender
|
||||
|| to.params.letter !== from.params.letter;
|
||||
|| to.params.gender !== from.params.gender;
|
||||
|
||||
await this.fetchActors(scroll);
|
||||
}
|
||||
@@ -318,17 +361,21 @@ export default {
|
||||
components: {
|
||||
Actor,
|
||||
Checkbox,
|
||||
Countries,
|
||||
Gender,
|
||||
RangeFilter,
|
||||
SearchBar,
|
||||
Pagination,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
actors: [],
|
||||
countries: [],
|
||||
topCountries: [],
|
||||
countryQuery: null,
|
||||
pageTitle: null,
|
||||
totalCount: 0,
|
||||
limit: 50,
|
||||
letters: ['all'].concat(Array.from({ length: 26 }, (value, index) => String.fromCharCode(index + 97).toUpperCase())),
|
||||
age: this.$route.query.age?.split(',') || [18, 100],
|
||||
ageRequired: !!this.$route.query.age,
|
||||
dob: this.$route.query.dob || dayjs().subtract(21, 'years').format('YYYY-MM-DD'),
|
||||
@@ -336,6 +383,7 @@ export default {
|
||||
boobSizes,
|
||||
boobSize: this.$route.query.bs?.split(',') || ['A', 'Z'],
|
||||
boobSizeRequired: !!this.$route.query.bs,
|
||||
country: this.$route.query.c || null,
|
||||
naturalBoobs: Number(this.$route.query.nb) || 1,
|
||||
height: this.$route.query.h?.split(',').map(Number) || [50, 220],
|
||||
heightRequired: !!this.$route.query.h,
|
||||
@@ -344,8 +392,8 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
letter,
|
||||
gender,
|
||||
filteredCountries,
|
||||
},
|
||||
watch: {
|
||||
$route: route,
|
||||
@@ -393,42 +441,53 @@ export default {
|
||||
.actors {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tiles {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
|
||||
grid-template-rows: min-content;
|
||||
grid-gap: .5rem;
|
||||
padding: 1rem;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.filter {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0 1rem;
|
||||
margin: 0 0 1rem 0;
|
||||
.search {
|
||||
width: 0;
|
||||
justify-content: flex-end;
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
.filters,
|
||||
.filters-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.filters {
|
||||
margin: 1rem 0 .5rem 0;
|
||||
}
|
||||
|
||||
.filters-row,
|
||||
.filter {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.genders {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
padding: 0 .5rem 0 0;
|
||||
border-right: solid 1px var(--shadow-hint);
|
||||
margin: 0 1rem 0 0;
|
||||
}
|
||||
|
||||
.letter,
|
||||
.gender {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.letter-link,
|
||||
.gender-link {
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
@@ -478,7 +537,8 @@ export default {
|
||||
color: var(--shadow);
|
||||
font-weight: bold;
|
||||
|
||||
.icon {
|
||||
.icon,
|
||||
.flag {
|
||||
fill: var(--shadow);
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
@@ -516,7 +576,7 @@ export default {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: .75rem .5rem .5rem .5rem;
|
||||
color: var(--darken);
|
||||
color: var(--shadow);
|
||||
font-weight: bold;
|
||||
font-size: .9rem;
|
||||
|
||||
@@ -598,7 +658,7 @@ export default {
|
||||
min-width: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
padding: 0 .5rem;
|
||||
color: var(--darken);
|
||||
color: var(--shadow);
|
||||
font-weight: bold;
|
||||
font-size: .9rem;
|
||||
|
||||
@@ -607,7 +667,7 @@ export default {
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: var(--darken);
|
||||
fill: var(--shadow);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -637,7 +697,7 @@ export default {
|
||||
height: 1.25rem;
|
||||
appearance: none;
|
||||
border-radius: 1rem;
|
||||
background-color: var(--darken-hint);
|
||||
background-color: var(--shadow-hint);
|
||||
background-image: radial-gradient(circle, var(--shadow-weak) .3rem, transparent calc(.3rem + 1px));
|
||||
cursor: pointer;
|
||||
|
||||
@@ -661,10 +721,45 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint) {
|
||||
.genders {
|
||||
flex-direction: column;
|
||||
@media(max-width: $breakpoint-mega) {
|
||||
.filters {
|
||||
flex-direction: column-reverse;
|
||||
}
|
||||
|
||||
::v-deep(.search) {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-kilo) {
|
||||
.filters {
|
||||
margin: 1rem 0 0 0;
|
||||
}
|
||||
|
||||
.filters-row {
|
||||
flex-direction: column;
|
||||
|
||||
.filter {
|
||||
padding: 0 1rem 1rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.filters-attributes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.genders {
|
||||
padding: 0;
|
||||
margin: 0 0 1.5rem 0;
|
||||
}
|
||||
|
||||
.tiles {
|
||||
padding: .5rem 1rem 1rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-micro) {
|
||||
|
||||
88
assets/components/actors/countries.vue
Normal file
@@ -0,0 +1,88 @@
|
||||
<template>
|
||||
<ul class="countries nolist">
|
||||
<li
|
||||
v-for="country in countries"
|
||||
:key="country.alpha2"
|
||||
:title="country.name"
|
||||
:class="{ selected: selectedCountry === country.alpha2 }"
|
||||
class="country"
|
||||
@click="updateValue('country', country.alpha2, true)"
|
||||
>
|
||||
<img
|
||||
:src="`/img/flags/${country.alpha2.toLowerCase()}.svg`"
|
||||
class="flag"
|
||||
>
|
||||
|
||||
<span class="country-name">{{ country.alias || country.name }}</span>
|
||||
|
||||
<Icon
|
||||
v-if="selectedCountry === country.alpha2"
|
||||
icon="cross2"
|
||||
@click.native.stop="updateValue('country', null, true)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
countries: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
selectedCountry: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
updateValue: {
|
||||
type: Function,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.countries:not(:last-child) {
|
||||
border-bottom: solid 1px var(--shadow-hint);
|
||||
}
|
||||
|
||||
.country {
|
||||
width: 15rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
|
||||
.flag {
|
||||
width: 1.25rem;
|
||||
padding: .25rem .5rem;
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: .25rem .5rem;;
|
||||
fill: var(--shadow);
|
||||
|
||||
&:hover {
|
||||
fill: var(--shadow-strong);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--shadow-hint);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.country-name {
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: .25rem .5rem;
|
||||
}
|
||||
</style>
|
||||
@@ -12,7 +12,7 @@
|
||||
<span
|
||||
v-if="!disabled"
|
||||
class="label-values"
|
||||
>({{ value[0] }} - {{ value[1] }}<template v-if="unit"> {{ unit }}</template>)</span>
|
||||
>{{ value[0] }} - {{ value[1] }}<template v-if="unit"> {{ unit }}</template></span>
|
||||
</label>
|
||||
|
||||
<span class="filter-split">
|
||||
@@ -87,14 +87,14 @@ export default {
|
||||
.filter-section {
|
||||
width: 15rem;
|
||||
max-width: 100%;
|
||||
border-bottom: solid 1px var(--darken-hint);
|
||||
border-bottom: solid 1px var(--shadow-hint);
|
||||
}
|
||||
|
||||
.filter-label {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: .75rem .5rem .5rem .5rem;
|
||||
color: var(--darken);
|
||||
color: var(--shadow);
|
||||
font-weight: bold;
|
||||
font-size: .9rem;
|
||||
|
||||
@@ -180,7 +180,7 @@ export default {
|
||||
min-width: 1.5rem;
|
||||
flex-shrink: 0;
|
||||
padding: 0 .5rem;
|
||||
color: var(--darken);
|
||||
color: var(--shadow);
|
||||
font-weight: bold;
|
||||
font-size: .9rem;
|
||||
|
||||
@@ -189,7 +189,7 @@ export default {
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: var(--darken);
|
||||
fill: var(--shadow);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -23,19 +23,19 @@ export default {
|
||||
.gender {
|
||||
&.female .icon {
|
||||
fill: var(--female);
|
||||
filter: drop-shadow(0 0 1px var(--shadow));
|
||||
filter: drop-shadow(0 0 1px var(--darken));
|
||||
}
|
||||
|
||||
&.male .icon {
|
||||
fill: var(--male);
|
||||
filter: drop-shadow(0 0 1px var(--shadow));
|
||||
filter: drop-shadow(0 0 1px var(--darken));
|
||||
}
|
||||
|
||||
&.transsexual .icon {
|
||||
fill: var(--text-contrast);
|
||||
fill: var(--text-light);
|
||||
filter: drop-shadow(1px 0 0 var(--female)) drop-shadow(-1px 0 0 var(--female)) drop-shadow(0 1px 0 var(--female)) drop-shadow(0 -1px 0 var(--female))
|
||||
drop-shadow(1px 0 0 var(--male)) drop-shadow(-1px 0 0 var(--male)) drop-shadow(0 1px 0 var(--male)) drop-shadow(0 -1px 0 var(--male))
|
||||
drop-shadow(0 0 1px var(--shadow))
|
||||
drop-shadow(0 0 1px var(--darken))
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -113,6 +113,7 @@ export default {
|
||||
|
||||
.photo {
|
||||
height: 15rem;
|
||||
width: auto;
|
||||
box-shadow: 0 0 3px var(--darken-weak);
|
||||
object-fit: cover;
|
||||
background-position: center;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<div class="avatar">
|
||||
<img
|
||||
v-if="actor.avatar"
|
||||
:src="getPath(actor.avatar)"
|
||||
:src="getPath(actor.avatar, 'lazy')"
|
||||
class="avatar-image"
|
||||
>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
v-if="actor"
|
||||
class="actor"
|
||||
>
|
||||
<router-link
|
||||
<RouterLink
|
||||
:to="{ name: 'actor', params: { actorId: actor.id, actorSlug: actor.slug } }"
|
||||
class="link"
|
||||
>
|
||||
@@ -15,7 +15,7 @@
|
||||
class="name"
|
||||
>{{ actor.name }}</span>
|
||||
|
||||
<router-link
|
||||
<RouterLink
|
||||
v-if="actor.entity"
|
||||
v-tooltip="actor.entity.name"
|
||||
:to="{ name: actor.entity.type, params: { entitySlug: actor.entity.slug, range: 'new', pageNumber: 1 } }"
|
||||
@@ -25,7 +25,7 @@
|
||||
:src="`/img/logos/${actor.entity.slug}/favicon_dark.png`"
|
||||
class="favicon-icon"
|
||||
>
|
||||
</router-link>
|
||||
</RouterLink>
|
||||
|
||||
<Icon
|
||||
v-if="alias"
|
||||
@@ -59,6 +59,27 @@
|
||||
class="avatar-fallback"
|
||||
></span>
|
||||
|
||||
<Icon
|
||||
v-show="(!stash || stash.primary) && favorited"
|
||||
icon="heart7"
|
||||
class="stash stashed"
|
||||
@click.prevent.native="unstashActor"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-show="(!stash || stash.primary) && favorited === false"
|
||||
icon="heart8"
|
||||
class="stash unstashed"
|
||||
@click.prevent.native="stashActor"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-show="stash && !stash.primary"
|
||||
icon="cross2"
|
||||
class="stash unstash"
|
||||
@click.prevent.native="unstashActor"
|
||||
/>
|
||||
|
||||
<span
|
||||
class="details"
|
||||
>
|
||||
@@ -108,13 +129,45 @@
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</router-link>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Gender from './gender.vue';
|
||||
|
||||
async function stashActor() {
|
||||
this.favorited = true;
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('stashActor', {
|
||||
actorId: this.actor.id,
|
||||
stashId: this.$store.getters.favorites.id,
|
||||
});
|
||||
|
||||
this.$emit('stash', true);
|
||||
} catch (error) {
|
||||
this.favorited = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function unstashActor() {
|
||||
if (!this.stash || this.stash.primary) {
|
||||
this.favorited = false;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('unstashActor', {
|
||||
actorId: this.actor.id,
|
||||
stashId: this.stash?.id || this.$store.getters.favorites.id,
|
||||
});
|
||||
|
||||
this.$emit('stash', false);
|
||||
} catch (error) {
|
||||
this.favorited = true;
|
||||
}
|
||||
}
|
||||
|
||||
function sfw() {
|
||||
return this.$store.state.ui.sfw;
|
||||
}
|
||||
@@ -132,10 +185,24 @@ export default {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
stash: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['stash'],
|
||||
data() {
|
||||
return {
|
||||
favorited: this.actor.isFavorited,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
sfw,
|
||||
},
|
||||
methods: {
|
||||
stashActor,
|
||||
unstashActor,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -147,7 +214,7 @@ export default {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
box-shadow: 0 0 3px var(--darken-weak);
|
||||
background: var(--background);
|
||||
background: var(--info);
|
||||
overflow: hidden;
|
||||
|
||||
&::before {
|
||||
@@ -155,6 +222,11 @@ export default {
|
||||
display: inline-block;
|
||||
padding-bottom: 150%;
|
||||
}
|
||||
|
||||
&:hover .unstashed,
|
||||
&:hover .unstash {
|
||||
fill: var(--lighten);
|
||||
}
|
||||
}
|
||||
|
||||
.link {
|
||||
@@ -165,12 +237,8 @@ export default {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
color: var(--text);
|
||||
color: var(--text-light);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.handle {
|
||||
@@ -181,7 +249,8 @@ export default {
|
||||
box-shadow: 0 0 3px var(--shadow);
|
||||
|
||||
.name {
|
||||
padding: .5rem;
|
||||
padding: .25rem .5rem;
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.alias {
|
||||
@@ -232,9 +301,9 @@ export default {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
object-fit: cover;
|
||||
object-position: 50% 0;
|
||||
object-position: center 0;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
background-position: center 0;
|
||||
}
|
||||
|
||||
.avatar-fallback {
|
||||
@@ -243,6 +312,27 @@ export default {
|
||||
opacity: .1;
|
||||
}
|
||||
|
||||
.stash {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: .25rem .25rem .5rem .5rem;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
fill: var(--lighten-weak);
|
||||
filter: drop-shadow(0 0 2px var(--darken));
|
||||
|
||||
&:hover.unstashed,
|
||||
&.stashed {
|
||||
fill: var(--primary);
|
||||
}
|
||||
|
||||
&:hover.unstash {
|
||||
fill: var(--text-light);
|
||||
filter: drop-shadow(0 0 2px var(--darken-weak));
|
||||
}
|
||||
}
|
||||
|
||||
.details {
|
||||
background: var(--darken);
|
||||
color: var(--text-light);
|
||||
|
||||
@@ -11,43 +11,56 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="album-items"
|
||||
:class="{ portrait }"
|
||||
>
|
||||
<div class="album-body">
|
||||
<div
|
||||
v-for="item in albumItems"
|
||||
:key="item.id"
|
||||
class="item-container"
|
||||
v-if="entity || tag"
|
||||
class="campaign-container"
|
||||
>
|
||||
<a
|
||||
:href="getPath(item, null, { local })"
|
||||
class="item-link"
|
||||
target="_blank"
|
||||
<Campaign
|
||||
:entity="entity"
|
||||
:tag="tag"
|
||||
:min-ratio="3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="album-items"
|
||||
:class="{ portrait }"
|
||||
>
|
||||
<div
|
||||
v-for="item in albumItems"
|
||||
:key="item.id"
|
||||
class="item-container"
|
||||
>
|
||||
<img
|
||||
:src="getPath(item, 'thumbnail', { local })"
|
||||
:style="{ 'background-image': getBgPath(item, 'lazy', { local }) }"
|
||||
:width="item.thumbnailWidth"
|
||||
:height="item.thumbnailHeight"
|
||||
:title="item.title"
|
||||
loading="lazy"
|
||||
class="item image"
|
||||
<a
|
||||
:href="getPath(item, null, { local })"
|
||||
class="item-link"
|
||||
target="_blank"
|
||||
>
|
||||
<img
|
||||
:src="getPath(item, 'thumbnail', { local })"
|
||||
:style="{ 'background-image': getBgPath(item, 'lazy', { local }) }"
|
||||
:width="item.thumbnailWidth"
|
||||
:height="item.thumbnailHeight"
|
||||
:title="item.title"
|
||||
loading="lazy"
|
||||
class="item image"
|
||||
>
|
||||
|
||||
<Logo :photo="item" />
|
||||
<Logo :photo="item" />
|
||||
|
||||
<router-link
|
||||
v-if="comments && item.title && item.entity"
|
||||
:to="`/${item.entity.type}/${item.entity.slug}`"
|
||||
class="item-comment"
|
||||
>{{ item.title }} for {{ item.entity.name }}</router-link>
|
||||
<router-link
|
||||
v-if="comments && item.title && item.entity"
|
||||
:to="`/${item.entity.type}/${item.entity.slug}`"
|
||||
class="item-comment"
|
||||
>{{ item.title }} for {{ item.entity.name }}</router-link>
|
||||
|
||||
<span
|
||||
v-else-if="comments && item.title"
|
||||
class="item-comment"
|
||||
>{{ item.title }}</span>
|
||||
</a>
|
||||
<span
|
||||
v-else-if="comments && item.title"
|
||||
class="item-comment"
|
||||
>{{ item.title }}</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -56,6 +69,7 @@
|
||||
|
||||
<script>
|
||||
import Logo from './logo.vue';
|
||||
import Campaign from '../campaigns/campaign.vue';
|
||||
|
||||
function albumItems() {
|
||||
return this.items
|
||||
@@ -69,6 +83,7 @@ function albumItems() {
|
||||
export default {
|
||||
components: {
|
||||
Logo,
|
||||
Campaign,
|
||||
},
|
||||
props: {
|
||||
items: {
|
||||
@@ -91,6 +106,14 @@ export default {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
entity: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
tag: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
albumItems,
|
||||
@@ -146,15 +169,30 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.album-body {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.campaign-container {
|
||||
flex-shrink: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.album-items {
|
||||
height: 0;
|
||||
display: grid;
|
||||
flex-grow: 1;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
grid-template-columns: repeat(auto-fit, minmax(25rem, 1fr));
|
||||
grid-gap: 0 1rem;
|
||||
padding: 1rem;
|
||||
margin: auto 0;
|
||||
overflow-y: auto;
|
||||
|
||||
&.portrait {
|
||||
grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
|
||||
|
||||
441
assets/components/alerts/add.vue
Normal file
@@ -0,0 +1,441 @@
|
||||
<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 #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-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 #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 #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 && !entity"
|
||||
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),
|
||||
});
|
||||
|
||||
this.$emit('close', true);
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
emits: ['close'],
|
||||
data() {
|
||||
return {
|
||||
actors: [],
|
||||
tags: [],
|
||||
entity: null,
|
||||
notify: true,
|
||||
email: false,
|
||||
stashes: [],
|
||||
availableStashes: this.$store.state.auth.user.stashes,
|
||||
};
|
||||
},
|
||||
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>
|
||||
41
assets/components/alerts/remove.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<Dialog
|
||||
title="Remove alert"
|
||||
@close="$emit('close', false)"
|
||||
>
|
||||
<form
|
||||
class="dialog-body"
|
||||
@submit.prevent="removeAlert"
|
||||
>
|
||||
Are you sure you want to remove alert?
|
||||
|
||||
<div class="dialog-actions right">
|
||||
<button
|
||||
type="submit"
|
||||
class="button button-primary"
|
||||
>Remove</button>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
async function removeAlert() {
|
||||
await this.$store.dispatch('removeAlert', this.alert.id);
|
||||
|
||||
this.$emit('close', true);
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
alert: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['close'],
|
||||
methods: {
|
||||
removeAlert,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
147
assets/components/alerts/search.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<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="result in results"
|
||||
:key="`result-${result.id}`"
|
||||
class="result"
|
||||
@click="selectResult(result)"
|
||||
>
|
||||
<Icon
|
||||
v-if="result.type === 'network'"
|
||||
v-tooltip="'Network'"
|
||||
icon="device_hub"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-if="result.type === 'channel'"
|
||||
v-tooltip="'Channel'"
|
||||
icon="tv"
|
||||
/>
|
||||
|
||||
<img
|
||||
v-if="result.avatar"
|
||||
:src="getPath(result.avatar)"
|
||||
class="avatar"
|
||||
>{{ result.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
async function search() {
|
||||
if (this.content === 'actors') {
|
||||
const { actors } = await this.$store.dispatch('fetchActors', {
|
||||
query: this.query,
|
||||
limit: 10,
|
||||
});
|
||||
|
||||
this.results = actors;
|
||||
}
|
||||
|
||||
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 {
|
||||
color: var(--text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .5rem;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
height: 2rem;
|
||||
margin: 0 .5rem 0 0;
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: var(--shadow);
|
||||
margin: -.1rem .75rem 0 0;
|
||||
}
|
||||
</style>
|
||||
@@ -35,10 +35,11 @@
|
||||
class="button button-primary"
|
||||
>Log in</button>
|
||||
|
||||
<router-link
|
||||
<RouterLink
|
||||
v-if="$store.state.auth.signup"
|
||||
:to="{ name: 'signup', query: { ref: $route.query.ref } }"
|
||||
class="link link-external signup"
|
||||
>Sign up</router-link>
|
||||
>Sign up</RouterLink>
|
||||
</template>
|
||||
</form>
|
||||
</template>
|
||||
@@ -65,7 +66,7 @@ async function login() {
|
||||
}
|
||||
|
||||
function mounted() {
|
||||
if (!this.$store.state.auth.enabled) {
|
||||
if (!this.$store.state.auth.login) {
|
||||
this.$router.replace({ name: 'not-found' });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,10 +43,11 @@
|
||||
class="button button-primary"
|
||||
>Sign up</button>
|
||||
|
||||
<router-link
|
||||
<RouterLink
|
||||
v-if="$store.state.auth.login"
|
||||
:to="{ name: 'login', query: { ref: $route.query.ref } }"
|
||||
class="link link-external login"
|
||||
>Log in</router-link>
|
||||
>Log in</RouterLink>
|
||||
</template>
|
||||
</form>
|
||||
</template>
|
||||
@@ -74,7 +75,7 @@ async function signup() {
|
||||
}
|
||||
|
||||
function mounted() {
|
||||
if (!this.$store.state.auth.enabled) {
|
||||
if (!this.$store.state.auth.signup) {
|
||||
this.$router.replace({ name: 'not-found' });
|
||||
}
|
||||
}
|
||||
|
||||
152
assets/components/campaigns/campaign.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<a
|
||||
v-if="campaign"
|
||||
:href="campaign.url || campaign.affiliate?.url"
|
||||
target="_blank"
|
||||
class="campaign"
|
||||
>
|
||||
<img
|
||||
v-if="campaign.banner.entity.type === 'network' || !campaign.banner.entity.parent"
|
||||
:src="`/img/banners/${campaign.banner.entity.slug}/${campaign.banner.id}.${campaign.banner.type || 'jpg'}`"
|
||||
:width="campaign.banner.width"
|
||||
:height="campaign.banner.height"
|
||||
class="campaign-banner"
|
||||
>
|
||||
|
||||
<img
|
||||
v-if="campaign.banner.entity.type === 'channel' && campaign.banner.entity.parent?.type === 'network'"
|
||||
:src="`/img/banners/${campaign.banner.entity.parent.slug}/${campaign.banner.entity.slug}/${campaign.banner.id}.${campaign.banner.type || 'jpg'}`"
|
||||
:width="campaign.banner.width"
|
||||
:height="campaign.banner.height"
|
||||
class="campaign-banner"
|
||||
>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
function ratioFilter(banner) {
|
||||
if (!banner) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.minHeight && banner.height < this.minHeight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.maxHeight && banner.height > this.minHeight) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.minRatio && banner.ratio < this.minRatio) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.maxRatio && banner.ratio > this.maxRatio) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function entityCampaign() {
|
||||
const bannerCampaigns = this.entity.campaigns
|
||||
.concat(this.entity.children?.flatMap(child => child.campaigns))
|
||||
.concat(this.entity.parent?.campaigns)
|
||||
.filter(campaignX => campaignX && this.ratioFilter(campaignX.banner));
|
||||
|
||||
if (bannerCampaigns.length > 0) {
|
||||
const randomCampaign = bannerCampaigns[Math.floor(Math.random() * bannerCampaigns.length)];
|
||||
|
||||
this.$emit('campaign', randomCampaign);
|
||||
return randomCampaign;
|
||||
}
|
||||
|
||||
this.$emit('campaign', null);
|
||||
return null;
|
||||
}
|
||||
|
||||
function tagCampaign() {
|
||||
const campaignBanners = this.tag.banners.filter(banner => banner.campaigns.length > 0 && this.ratioFilter(banner));
|
||||
const banner = campaignBanners[Math.floor(Math.random() * campaignBanners.length)];
|
||||
|
||||
if (banner?.campaigns.length > 0) {
|
||||
const randomCampaign = {
|
||||
...banner.campaigns[Math.floor(Math.random() * banner.campaigns.length)],
|
||||
banner,
|
||||
};
|
||||
|
||||
this.$emit('campaign', randomCampaign);
|
||||
return randomCampaign;
|
||||
}
|
||||
|
||||
this.$emit('campaign', null);
|
||||
return null;
|
||||
}
|
||||
|
||||
function campaign() {
|
||||
if (this.entity) {
|
||||
return this.entityCampaign();
|
||||
}
|
||||
|
||||
if (this.tag) {
|
||||
return this.tagCampaign();
|
||||
}
|
||||
|
||||
this.$emit('campaign', null); // allow parent to toggle campaigns depending on availability
|
||||
return null;
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
entity: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
tag: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
minHeight: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
maxHeight: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
minRatio: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
maxRatio: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['campaign'],
|
||||
computed: {
|
||||
campaign,
|
||||
},
|
||||
methods: {
|
||||
entityCampaign,
|
||||
ratioFilter,
|
||||
tagCampaign,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.campaign {
|
||||
height: 100%;
|
||||
display: inline-flex;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.campaign-banner {
|
||||
height: auto;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -44,7 +44,7 @@
|
||||
import Warning from './warning.vue';
|
||||
import Header from '../header/header.vue';
|
||||
import Sidebar from '../sidebar/sidebar.vue';
|
||||
import Filters from './filters.vue';
|
||||
import Filters from '../filters/filters.vue';
|
||||
|
||||
function toggleSidebar(state) {
|
||||
this.showSidebar = typeof state === 'boolean' ? state : !this.showSidebar;
|
||||
@@ -62,7 +62,7 @@ async function setConsent(consent, includeQueer) {
|
||||
}
|
||||
|
||||
if (includeQueer) {
|
||||
this.$store.dispatch('setTagFilter', this.$store.state.ui.tagFilter.filter(tag => !['gay', 'bisexual', 'transsexual'].includes(tag)));
|
||||
this.$store.dispatch('setTagFilter', this.$store.state.ui.tagFilter.filter((tag) => !['gay', 'bisexual', 'transsexual'].includes(tag)));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -124,8 +124,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import 'theme';
|
||||
|
||||
.container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
|
||||
@@ -67,12 +67,13 @@ export default {
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
background: var(--darken-censor);
|
||||
background: var(--background-censor);
|
||||
backdrop-filter: blur(.25rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: var(--text-light);
|
||||
color: var(--text);
|
||||
width: 50rem;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
@@ -87,8 +88,8 @@ export default {
|
||||
.title {
|
||||
display: block;
|
||||
font-size: 2rem;
|
||||
margin: 1rem 0 0 0;
|
||||
color: var(--text-light);
|
||||
margin: 1rem 0;
|
||||
color: var(--text);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@@ -113,7 +114,7 @@ export default {
|
||||
.rules {
|
||||
margin: 0 0 0 1rem;
|
||||
text-align: left;
|
||||
text-shadow: 0 0 3px var(--darken-extreme);
|
||||
text-shadow: 0 0 3px var(--highlight-strong);
|
||||
}
|
||||
|
||||
.rule {
|
||||
@@ -129,20 +130,21 @@ export default {
|
||||
|
||||
.button {
|
||||
display: inline-flex;
|
||||
flex-basis: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
border-radius: .25rem;
|
||||
padding: 0;
|
||||
border: solid 2px transparent;
|
||||
color: var(--lighten-strong);
|
||||
background: none;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
font-size: 1.5rem;
|
||||
text-decoration: none;
|
||||
flex-basis: 0;
|
||||
transition: border-radius .2s ease;
|
||||
|
||||
&.leave {
|
||||
color: var(--shadow-strong);
|
||||
flex-direction: row;
|
||||
padding: 1rem;
|
||||
|
||||
@@ -150,7 +152,11 @@ export default {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
margin: 0 1rem 0 0;
|
||||
fill: var(--lighten);
|
||||
fill: var(--shadow);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--text);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,12 +164,37 @@ export default {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
&.straight {
|
||||
border-color: var(--primary);
|
||||
&.straight,
|
||||
&.queer {
|
||||
color: var(--lighten-strong);
|
||||
background: var(--darken-censor);
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
width: calc(100% + .3rem);
|
||||
height: calc(100% + .25rem);
|
||||
position: absolute;
|
||||
z-index: -1;
|
||||
filter: blur(.25rem);
|
||||
transition: filter .2s ease;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--text-light);
|
||||
border-radius: 0;
|
||||
|
||||
.button-sub {
|
||||
color: var(--text-light);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.queer {
|
||||
border-image: linear-gradient(90deg, #f00, #f80, #ff0, #0f0, #00f, #a0f, #fff, #f8f, #0ff) 1;
|
||||
&.straight:before {
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
&.queer:before {
|
||||
background: linear-gradient(90deg, #f00, #f80, #ff0, #0f0, #00f, #a0f, #fff, #f8f, #0ff);
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
@@ -171,14 +202,13 @@ export default {
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--text-light);
|
||||
|
||||
.button-sub {
|
||||
color: var(--lighten-strong);
|
||||
.icon {
|
||||
fill: var(--text);
|
||||
}
|
||||
|
||||
.icon {
|
||||
fill: var(--text-light);
|
||||
&.straight:before,
|
||||
&.queer:before {
|
||||
filter: blur(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,7 +233,7 @@ export default {
|
||||
}
|
||||
|
||||
.preferences {
|
||||
color: var(--lighten);
|
||||
color: var(--shadow);
|
||||
display: block;
|
||||
padding: .5rem 0 1rem 0;
|
||||
text-align: center;
|
||||
@@ -212,11 +242,11 @@ export default {
|
||||
|
||||
@media(max-width: $breakpoint) {
|
||||
.title {
|
||||
font-size: 1.5rem;
|
||||
font-size: 1.75rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 5rem;
|
||||
width: 5.75rem;
|
||||
margin: 0 .5rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
@click.stop
|
||||
@click.stop="events.emit('blur')"
|
||||
>
|
||||
<div
|
||||
v-if="title || $slots.header"
|
||||
@@ -32,6 +32,10 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
function mounted() {
|
||||
this.events.emit('blur');
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
@@ -40,6 +44,7 @@ export default {
|
||||
},
|
||||
},
|
||||
emits: ['close'],
|
||||
mounted,
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -76,7 +81,6 @@ export default {
|
||||
color: var(--text-light);
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
@@ -99,14 +103,34 @@ 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;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
::v-deep(.dialog-section:not(:last-child)) {
|
||||
border-bottom: solid 1px var(--shadow-hint);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
::v-deep(.dialog-actions) {
|
||||
display: flex;
|
||||
padding: .5rem 0;
|
||||
margin: 1rem 0 0 0;
|
||||
|
||||
&.center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&.right {
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -47,6 +47,7 @@ export default {
|
||||
|
||||
.tile {
|
||||
width: 15rem;
|
||||
height: 6rem;
|
||||
}
|
||||
|
||||
&.expanded {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
>
|
||||
<div class="info">
|
||||
<a
|
||||
:href="entity.url"
|
||||
:href="entityUrl"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
class="link link-child"
|
||||
@@ -51,7 +51,7 @@
|
||||
>{{ tag.name }}</li>
|
||||
</ul>
|
||||
|
||||
<router-link
|
||||
<RouterLink
|
||||
v-if="entity.parent"
|
||||
:to="`/${entity.parent.type}/${entity.parent.slug}`"
|
||||
class="link link-parent"
|
||||
@@ -72,10 +72,15 @@
|
||||
v-else
|
||||
class="name parent-name"
|
||||
>{{ entity.parent.name }}</h3>
|
||||
</router-link>
|
||||
|
||||
<Icon icon="device_hub" />
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<div class="content-inner">
|
||||
<div
|
||||
class="content-inner"
|
||||
@scroll="events.emit('scroll', $event)"
|
||||
>
|
||||
<Scroll
|
||||
v-if="entity.children.length > 0"
|
||||
v-slot="scroll"
|
||||
@@ -91,15 +96,26 @@
|
||||
/>
|
||||
</Scroll>
|
||||
|
||||
<div class="campaign-container">
|
||||
<Campaign
|
||||
:entity="entity"
|
||||
:min-ratio="3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FilterBar
|
||||
ref="filter"
|
||||
:fetch-releases="fetchEntity"
|
||||
:items-total="totalCount"
|
||||
:items-per-page="limit"
|
||||
:available-tags="entity.tags"
|
||||
/>
|
||||
|
||||
<div class="releases">
|
||||
<Releases :releases="entity.releases" />
|
||||
<Releases
|
||||
:releases="entity.releases"
|
||||
:done="done"
|
||||
/>
|
||||
|
||||
<Pagination
|
||||
:items-total="totalCount"
|
||||
@@ -119,8 +135,12 @@ import Pagination from '../pagination/pagination.vue';
|
||||
import Releases from '../releases/releases.vue';
|
||||
import Children from './children.vue';
|
||||
import Scroll from '../scroll/scroll.vue';
|
||||
import Campaign from '../campaigns/campaign.vue';
|
||||
|
||||
async function fetchEntity(scroll = true) {
|
||||
this.done = false;
|
||||
this.entityUrl = null;
|
||||
|
||||
const { entity, totalCount } = await this.$store.dispatch('fetchEntityBySlugAndType', {
|
||||
entitySlug: this.$route.params.entitySlug,
|
||||
entityType: this.$route.name,
|
||||
@@ -134,7 +154,18 @@ async function fetchEntity(scroll = true) {
|
||||
|
||||
this.pageTitle = entity.name;
|
||||
|
||||
if (scroll) {
|
||||
const campaign = entity.campaigns.find((campaignX) => !campaignX.banner)
|
||||
|| entity.parent?.campaigns.find((campaignX) => !campaignX.banner);
|
||||
|
||||
const affiliateParams = new URLSearchParams({
|
||||
...(entity.url && Object.fromEntries(new URL(entity.url).searchParams)), // preserve any query in entity URL, e.g. ?siteId=5
|
||||
...(campaign?.affiliate?.parameters && Object.fromEntries(new URLSearchParams(campaign.affiliate.parameters))), // append affiliate parameters
|
||||
}).toString();
|
||||
|
||||
this.entityUrl = campaign?.url || campaign?.affiliate?.url || `${entity.url}${campaign?.affiliate?.parameters ? `?${affiliateParams}` : ''}`;
|
||||
this.done = true;
|
||||
|
||||
if (scroll && this.$refs.filter?.$el) {
|
||||
this.$refs.filter.$el.scrollIntoView();
|
||||
}
|
||||
}
|
||||
@@ -143,9 +174,11 @@ async function mounted() {
|
||||
await this.fetchEntity();
|
||||
}
|
||||
|
||||
async function route() {
|
||||
await this.fetchEntity();
|
||||
this.expanded = false;
|
||||
async function route(to) {
|
||||
if (to.name === 'channel' || to.name === 'network' || to.name === 'studio') {
|
||||
await this.fetchEntity();
|
||||
this.expanded = false;
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
@@ -155,14 +188,17 @@ export default {
|
||||
Children,
|
||||
Releases,
|
||||
Scroll,
|
||||
Campaign,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
entity: null,
|
||||
pageTitle: null,
|
||||
totalCount: null,
|
||||
done: false,
|
||||
limit: Number(this.$route.query.limit) || 20,
|
||||
expanded: false,
|
||||
entityUrl: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -187,7 +223,7 @@ export default {
|
||||
}
|
||||
|
||||
.link {
|
||||
max-width: 20rem;
|
||||
max-width: 15rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
@@ -196,6 +232,8 @@ export default {
|
||||
}
|
||||
|
||||
.link-child {
|
||||
flex-shrink: 0;
|
||||
|
||||
.icon {
|
||||
fill: var(--lighten);
|
||||
margin: 0 0 0 1rem;
|
||||
@@ -208,7 +246,15 @@ export default {
|
||||
|
||||
.link-parent {
|
||||
flex-direction: row-reverse;
|
||||
flex-shrink: 0;
|
||||
margin: 0 0 0 3rem;
|
||||
|
||||
.icon {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
fill: var(--lighten);
|
||||
margin: 0 .5rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
@@ -218,7 +264,7 @@ export default {
|
||||
}
|
||||
|
||||
.logo-child {
|
||||
height: 2rem;
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.logo-parent {
|
||||
@@ -227,6 +273,7 @@ export default {
|
||||
|
||||
.favicon {
|
||||
height: 1rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.content-inner {
|
||||
@@ -238,6 +285,12 @@ export default {
|
||||
background: var(--profile);
|
||||
}
|
||||
|
||||
.campaign-container {
|
||||
background: var(--background-dim);
|
||||
text-align: center;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.releases {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@@ -4,39 +4,49 @@
|
||||
:title="entity.name"
|
||||
class="tile"
|
||||
>
|
||||
<template v-if="entity.hasLogo">
|
||||
<img
|
||||
v-if="entity.type === 'network' || entity.independent"
|
||||
:src="`/img/logos/${entity.slug}/thumbs/network.png`"
|
||||
:alt="entity.name"
|
||||
loading="lazy"
|
||||
class="logo"
|
||||
@load="$emit('load', $event)"
|
||||
>
|
||||
<div class="tile-logo">
|
||||
<template v-if="entity.hasLogo">
|
||||
<img
|
||||
v-if="entity.type === 'network' || entity.independent"
|
||||
:src="`/img/logos/${entity.slug}/thumbs/network.png`"
|
||||
:alt="entity.name"
|
||||
loading="lazy"
|
||||
class="logo"
|
||||
@load="$emit('load', $event)"
|
||||
>
|
||||
|
||||
<img
|
||||
v-else-if="entity.parent"
|
||||
:src="`/img/logos/${entity.parent.slug}/thumbs/${entity.slug}.png`"
|
||||
:alt="entity.name"
|
||||
loading="lazy"
|
||||
class="logo"
|
||||
@load="$emit('load', $event)"
|
||||
>
|
||||
<img
|
||||
v-else-if="entity.parent"
|
||||
:src="`/img/logos/${entity.parent.slug}/thumbs/${entity.slug}.png`"
|
||||
:alt="entity.name"
|
||||
loading="lazy"
|
||||
class="logo"
|
||||
@load="$emit('load', $event)"
|
||||
>
|
||||
|
||||
<img
|
||||
<img
|
||||
v-else
|
||||
:src="`/img/logos/${entity.slug}/thumbs/${entity.slug}.png`"
|
||||
:alt="entity.name"
|
||||
loading="lazy"
|
||||
class="logo"
|
||||
@load="$emit('load', $event)"
|
||||
>
|
||||
</template>
|
||||
|
||||
<span
|
||||
v-else
|
||||
:src="`/img/logos/${entity.slug}/thumbs/${entity.slug}.png`"
|
||||
:alt="entity.name"
|
||||
loading="lazy"
|
||||
class="logo"
|
||||
@load="$emit('load', $event)"
|
||||
>
|
||||
</template>
|
||||
class="name"
|
||||
>{{ entity.name }}</span>
|
||||
</div>
|
||||
|
||||
<span
|
||||
v-else
|
||||
class="name"
|
||||
>{{ entity.name }}</span>
|
||||
v-if="entity.childrenTotal > 0 || typeof entity.sceneTotal !== 'undefined'"
|
||||
class="count"
|
||||
>
|
||||
<span v-if="typeof entity.sceneTotal !== 'undefined'">{{ entity.sceneTotal }} scenes</span>
|
||||
<span v-if="entity.type === 'network'">{{ entity.childrenTotal }} channels</span>
|
||||
</span>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
@@ -56,18 +66,31 @@ export default {
|
||||
@import 'theme';
|
||||
|
||||
.tile {
|
||||
height: 6rem;
|
||||
height: 100%;
|
||||
background: var(--tile);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: .5rem 1rem;
|
||||
border-radius: .25rem;
|
||||
position: relative;
|
||||
box-shadow: 0 0 3px rgba(0, 0, 0, .25);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover .count {
|
||||
bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.tile-logo {
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
padding: .5rem 1rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.logo {
|
||||
@@ -75,11 +98,30 @@ export default {
|
||||
max-height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-light);
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.count {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: -1.75rem;
|
||||
box-sizing: border-box;
|
||||
padding: .25rem .5rem;
|
||||
background: var(--darken-strong);
|
||||
box-shadow: 0 0 3px var(--darken);
|
||||
color: var(--text-light);
|
||||
text-align: center;
|
||||
text-shadow: 1px 1px var(--darken);
|
||||
transition: bottom .1s ease;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,41 +1,33 @@
|
||||
<template>
|
||||
<div class="filter-bar noselect">
|
||||
<div class="sort">
|
||||
<router-link
|
||||
:to="{ params: { range: 'latest', pageNumber: 1 }, query: $route.query }"
|
||||
:class="{ active: $route.name === 'latest' || range === 'latest' }"
|
||||
<RouterLink
|
||||
v-for="section in ranges"
|
||||
:key="section"
|
||||
:to="{ params: { range: section, pageNumber: 1 }, query: $route.query }"
|
||||
:class="{ active: $route.name === section || range === section }"
|
||||
class="range range-button"
|
||||
>Latest</router-link>
|
||||
|
||||
<router-link
|
||||
:to="{ params: { range: 'upcoming', pageNumber: 1 }, query: $route.query }"
|
||||
:class="{ active: $route.name === 'upcoming' || range === 'upcoming' }"
|
||||
class="range-button"
|
||||
>Upcoming</router-link>
|
||||
|
||||
<router-link
|
||||
:to="{ params: { range: 'new', pageNumber: 1 }, query: $route.query }"
|
||||
:class="{ active: $route.name === 'new' || range === 'new' }"
|
||||
class="range-button"
|
||||
>New</router-link>
|
||||
>{{ section }}</RouterLink>
|
||||
</div>
|
||||
|
||||
<span
|
||||
v-if="itemsTotal && showTotal"
|
||||
class="total"
|
||||
>{{ itemsTotal }} <template v-if="itemsTotal === 1">scene</template><template v-else>scenes</template></span>
|
||||
|
||||
<div class="filters">
|
||||
<ActorFilter
|
||||
class="filters-filter"
|
||||
:filter="filter"
|
||||
:available-actors="availableActors"
|
||||
/>
|
||||
|
||||
<ChannelFilter
|
||||
class="filters-filter"
|
||||
:filter="filter"
|
||||
:available-channels="availableChannels"
|
||||
/>
|
||||
|
||||
<TagFilter
|
||||
class="filters-filter"
|
||||
:filter="filter"
|
||||
:available-tags="availableTags"
|
||||
/>
|
||||
</div>
|
||||
@@ -49,10 +41,6 @@ import ActorFilter from './actor-filter.vue';
|
||||
import ChannelFilter from './channel-filter.vue';
|
||||
import TagFilter from './tag-filter.vue';
|
||||
|
||||
function filter(state) {
|
||||
return state.ui.filter;
|
||||
}
|
||||
|
||||
function range() {
|
||||
return this.$route.params.range;
|
||||
}
|
||||
@@ -88,6 +76,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
showTotal: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
itemsTotal: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
@@ -96,6 +88,10 @@ export default {
|
||||
type: Number,
|
||||
default: 10,
|
||||
},
|
||||
ranges: {
|
||||
type: Array,
|
||||
default: () => ['latest', 'upcoming', 'new'],
|
||||
},
|
||||
availableTags: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
@@ -111,7 +107,6 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
filter,
|
||||
range,
|
||||
batch,
|
||||
}),
|
||||
@@ -126,143 +121,7 @@ export default {
|
||||
<style lang="scss">
|
||||
@import 'breakpoints';
|
||||
|
||||
.filter-mode {
|
||||
width: 100%;
|
||||
background: none;
|
||||
padding: .75rem;
|
||||
margin: 0 0 .5rem 0;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
border-bottom: solid 1px var(--shadow-hint);
|
||||
}
|
||||
|
||||
.filter-clear {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: .5rem 1rem;
|
||||
color: var(--darken-weak);
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
|
||||
.icon {
|
||||
fill: var(--darken-hint);
|
||||
margin: 0 0 0 1rem;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--darken);
|
||||
|
||||
.icon {
|
||||
fill: var(--darken-weak);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--text);
|
||||
background: var(--darken-hint);
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: var(--alert);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-items .filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background: var(--darken-hint);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
.filter-include {
|
||||
.filter-add {
|
||||
fill: var(--success);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.filter-add {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.filter-remove {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-include {
|
||||
.icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
padding: .5rem 1rem;
|
||||
fill: var(--darken-hint);
|
||||
}
|
||||
|
||||
.filter-remove {
|
||||
display: none;
|
||||
fill: var(--alert);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-name {
|
||||
min-width: 8rem;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
padding: .5rem .75rem .5rem 1rem;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.filter-include:hover,
|
||||
.filter-name:hover {
|
||||
background: var(--darken-hint);
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-small) {
|
||||
.filter-applied {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.filters-filter:not(:last-child) .filter {
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.filters-filter:last-child .filter {
|
||||
padding: .5rem 0 .5rem .5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import 'theme';
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: .5rem 1rem 0 1rem;
|
||||
z-index: 1;
|
||||
background: var(--background-dim);
|
||||
font-size: 0;
|
||||
|
||||
.icon {
|
||||
margin: 0 .5rem 0 0;
|
||||
fill: var(--shadow);
|
||||
}
|
||||
}
|
||||
|
||||
::v-deep(.filter) {
|
||||
.filter {
|
||||
color: var(--shadow);
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -299,6 +158,150 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.filter-mode {
|
||||
width: 100%;
|
||||
color: var(--shadow);
|
||||
background: none;
|
||||
padding: .75rem;
|
||||
margin: 0 0 .5rem 0;
|
||||
font-size: 1rem;
|
||||
border: none;
|
||||
border-bottom: solid 1px var(--shadow-hint);
|
||||
|
||||
option {
|
||||
color: var(--text-dark);
|
||||
}
|
||||
}
|
||||
|
||||
.filter-clear {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: .5rem 1rem;
|
||||
color: var(--shadow-weak);
|
||||
text-decoration: none;
|
||||
cursor: default;
|
||||
|
||||
.icon {
|
||||
fill: var(--shadow-hint);
|
||||
margin: 0 0 0 1rem;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--shadow);
|
||||
|
||||
.icon {
|
||||
fill: var(--shadow-weak);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--text);
|
||||
background: var(--shadow-hint);
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: var(--alert);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-items .filter-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:hover {
|
||||
background: var(--shadow-hint);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.selected {
|
||||
.filter-include {
|
||||
.filter-add {
|
||||
fill: var(--success);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.filter-add {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.filter-remove {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.filter-include {
|
||||
.icon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
padding: .5rem 1rem;
|
||||
fill: var(--shadow-hint);
|
||||
}
|
||||
|
||||
.filter-remove {
|
||||
display: none;
|
||||
fill: var(--alert);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-name {
|
||||
min-width: 8rem;
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
padding: .5rem .75rem .5rem 1rem;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.filter-include:hover,
|
||||
.filter-name:hover {
|
||||
background: var(--shadow-hint);
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-small) {
|
||||
.filter-applied {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.filters-filter:not(:last-child) .filter {
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.filters-filter:last-child .filter {
|
||||
padding: .5rem 0 .5rem .5rem;
|
||||
}
|
||||
|
||||
.total {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import 'theme';
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: .5rem 1rem 0 1rem;
|
||||
z-index: 1;
|
||||
background: var(--background-dim);
|
||||
font-size: 0;
|
||||
|
||||
.icon {
|
||||
margin: 0 .5rem 0 0;
|
||||
fill: var(--shadow);
|
||||
}
|
||||
}
|
||||
|
||||
.filters {
|
||||
flex-shrink: 0;
|
||||
@@ -320,6 +323,7 @@ export default {
|
||||
text-decoration: none;
|
||||
border: solid 1px transparent;
|
||||
border-bottom: none;
|
||||
text-transform: capitalize;
|
||||
|
||||
&:hover:not(.active) {
|
||||
color: var(--shadow-strong);
|
||||
@@ -352,4 +356,11 @@ export default {
|
||||
margin: 0 1rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.total {
|
||||
flex-shrink: 0;
|
||||
padding: 0 1rem;
|
||||
color: var(--shadow);
|
||||
font-size: .8rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<template>
|
||||
<Dialog
|
||||
title="filters"
|
||||
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: {
|
||||
@@ -14,7 +14,7 @@
|
||||
>Tags</div>
|
||||
</div>
|
||||
|
||||
<template v-slot:tooltip>
|
||||
<template #tooltip>
|
||||
<div
|
||||
class="filter-options"
|
||||
@click.stop
|
||||
@@ -70,7 +70,7 @@
|
||||
<script>
|
||||
function getNewRange(tag) {
|
||||
if (this.selectedTags.includes(tag)) {
|
||||
return { tags: this.selectedTags.filter(selectedTag => selectedTag !== tag).join(',') || undefined };
|
||||
return { tags: this.selectedTags.filter((selectedTag) => selectedTag !== tag).join(',') || undefined };
|
||||
}
|
||||
|
||||
return { tags: this.selectedTags.concat(tag).join(',') };
|
||||
@@ -82,10 +82,6 @@ function selectedTags() {
|
||||
|
||||
export default {
|
||||
props: {
|
||||
filter: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
compact: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
|
||||
@@ -6,20 +6,32 @@
|
||||
:to="{ name: 'stats' }"
|
||||
class="segment footer-link nolink"
|
||||
>stats</router-link>
|
||||
|
||||
<a
|
||||
v-if="config.discord"
|
||||
:href="config.discord"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="segment footer-link nolink discord"
|
||||
><Icon icon="discord" /></a>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.footer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: var(--background-dim);
|
||||
color: var(--shadow);
|
||||
font-size: .8rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
box-shadow: inset -3px 0 3px var(--shadow-hint);
|
||||
}
|
||||
|
||||
.segment {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: .5rem;
|
||||
|
||||
&:not(:last-child) {
|
||||
@@ -32,6 +44,17 @@
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
|
||||
.icon {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.discord {
|
||||
.icon {
|
||||
fill: var(--shadow);
|
||||
width: 4rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -163,6 +163,12 @@ export default {
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.dark .range-container .range {
|
||||
--slider-range: var(--lighten-weak);
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@mixin thumb {
|
||||
appearance: none;
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<template>
|
||||
<label class="toggle-container noselect">
|
||||
<label
|
||||
class="toggle-container noselect"
|
||||
:class="{ light: $store.state.ui.theme === 'dark' }"
|
||||
>
|
||||
<input
|
||||
:id="`toggle-${id}`"
|
||||
:checked="checked"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<li class="nav-item">
|
||||
<router-link
|
||||
v-slot="{ href, isActive, navigate }"
|
||||
to="/actors"
|
||||
:to="{ name: 'actors', params: { pageNumber: 1 } }"
|
||||
custom
|
||||
>
|
||||
<a
|
||||
@@ -31,7 +31,7 @@
|
||||
<li class="nav-item">
|
||||
<router-link
|
||||
v-slot="{ href, isActive, navigate }"
|
||||
to="/channels"
|
||||
:to="{ name: 'channels' }"
|
||||
custom
|
||||
>
|
||||
<a
|
||||
@@ -46,7 +46,7 @@
|
||||
<li class="nav-item">
|
||||
<router-link
|
||||
v-slot="{ href, isActive, navigate }"
|
||||
to="/tags"
|
||||
:to="{ name: 'tags' }"
|
||||
custom
|
||||
>
|
||||
<a
|
||||
@@ -61,7 +61,7 @@
|
||||
<li class="nav-item">
|
||||
<router-link
|
||||
v-slot="{ href, isActive, navigate }"
|
||||
to="/movies"
|
||||
:to="{ name: 'movies', params: { range: 'latest', pageNumber: 1 } }"
|
||||
custom
|
||||
>
|
||||
<a
|
||||
@@ -82,8 +82,31 @@
|
||||
@click.stop="$emit('toggleSidebar')"
|
||||
><Icon icon="menu" /></div>
|
||||
|
||||
<Tooltip v-if="me">
|
||||
<div
|
||||
class="header-button header-notifications"
|
||||
:class="{ unseen: unseenNotificationsCount > 0 }"
|
||||
>
|
||||
<Icon icon="bell2" />
|
||||
|
||||
<span
|
||||
v-if="unseenNotificationsCount > 0"
|
||||
class="notifications-count"
|
||||
>{{ unseenNotificationsCount }}</span>
|
||||
</div>
|
||||
|
||||
<template v-slot:tooltip>
|
||||
<Notifications
|
||||
:notifications="notifications"
|
||||
:unseen-count="unseenNotificationsCount"
|
||||
@check="fetchNotifications"
|
||||
@add-alert="showAddAlert = true"
|
||||
/>
|
||||
</template>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip>
|
||||
<div class="header-account">
|
||||
<div class="header-button header-account">
|
||||
<div class="account">
|
||||
<Icon
|
||||
icon="user3-long"
|
||||
@@ -97,8 +120,6 @@
|
||||
</template>
|
||||
</Tooltip>
|
||||
|
||||
<Search class="search-full" />
|
||||
|
||||
<Tooltip
|
||||
class="search-compact"
|
||||
:open="searching"
|
||||
@@ -121,19 +142,42 @@
|
||||
/>
|
||||
</template>
|
||||
</Tooltip>
|
||||
|
||||
<Search class="search-full" />
|
||||
</div>
|
||||
|
||||
<AddAlert
|
||||
v-if="showAddAlert"
|
||||
@close="showAddAlert = false"
|
||||
>Alert</AddAlert>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Menu from './menu.vue';
|
||||
import Notifications from './notifications.vue';
|
||||
import AddAlert from '../alerts/add.vue';
|
||||
|
||||
import Search from './search.vue';
|
||||
|
||||
import logo from '../../img/logo.svg';
|
||||
|
||||
function me() {
|
||||
return this.$store.state.auth.user;
|
||||
}
|
||||
|
||||
async function fetchNotifications() {
|
||||
const { notifications, unseenCount } = await this.$store.dispatch('fetchNotifications');
|
||||
|
||||
this.notifications = notifications;
|
||||
this.unseenNotificationsCount = unseenCount;
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AddAlert,
|
||||
Menu,
|
||||
Notifications,
|
||||
Search,
|
||||
},
|
||||
emits: ['toggleSidebar', 'showFilters'],
|
||||
@@ -142,8 +186,21 @@ export default {
|
||||
logo,
|
||||
searching: false,
|
||||
showFilters: false,
|
||||
showAddAlert: false,
|
||||
notifications: [],
|
||||
unseenNotificationsCount: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
me,
|
||||
},
|
||||
watch: {
|
||||
me: fetchNotifications,
|
||||
},
|
||||
mounted: fetchNotifications,
|
||||
methods: {
|
||||
fetchNotifications,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -257,26 +314,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 +328,38 @@ export default {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
.icon {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.header-account {
|
||||
padding: 1rem 1.25rem 1rem .75rem;
|
||||
}
|
||||
|
||||
.header-notifications {
|
||||
position: relative;
|
||||
padding: 1rem .75rem;
|
||||
|
||||
&.unseen .icon {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.notifications-count {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
bottom: .3rem;
|
||||
left: 0;
|
||||
color: var(--primary);
|
||||
font-size: .6rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.account {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
@@ -364,7 +433,8 @@ export default {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.header-account {
|
||||
.header-account,
|
||||
.header-notifications {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,26 @@
|
||||
<template>
|
||||
<div class="menu">
|
||||
<ul class="menu-items noselect">
|
||||
<router-link
|
||||
v-if="auth && me"
|
||||
<RouterLink
|
||||
v-if="login && me"
|
||||
:to="{ name: 'user', params: { username: me.username } }"
|
||||
class="menu-username"
|
||||
>{{ me.username }}</router-link>
|
||||
>{{ me.username }}</RouterLink>
|
||||
|
||||
<router-link
|
||||
v-else-if="auth"
|
||||
<RouterLink
|
||||
v-if="me && favorites"
|
||||
:to="{ name: 'stash', params: { stashId: favorites.id, range: 'scenes', pageNumber: 1 } }"
|
||||
class="menu-item"
|
||||
><Icon icon="heart7" />Favorites</RouterLink>
|
||||
|
||||
<RouterLink
|
||||
v-else-if="login"
|
||||
:to="{ name: 'login', query: { ref: $route.path } }"
|
||||
class="menu-item"
|
||||
@click.stop
|
||||
>
|
||||
<Icon icon="enter2" />Log in
|
||||
</router-link>
|
||||
|
||||
<li
|
||||
v-if="auth && me"
|
||||
class="menu-item"
|
||||
@click.stop="$store.dispatch('logout')"
|
||||
>
|
||||
<Icon icon="exit2" />Log out
|
||||
</li>
|
||||
</RouterLink>
|
||||
|
||||
<li
|
||||
v-show="!sfw"
|
||||
@@ -74,6 +72,14 @@
|
||||
>
|
||||
<Icon icon="filter" />Filters
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="login && me"
|
||||
class="menu-item"
|
||||
@click.stop="$store.dispatch('logout')"
|
||||
>
|
||||
<Icon icon="exit2" />Log out
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
@@ -89,8 +95,16 @@ function theme(state) {
|
||||
return state.ui.theme;
|
||||
}
|
||||
|
||||
function auth(state) {
|
||||
return state.auth.enabled;
|
||||
function login(state) {
|
||||
return state.auth.login;
|
||||
}
|
||||
|
||||
function signup(state) {
|
||||
return state.auth.signup;
|
||||
}
|
||||
|
||||
function favorites() {
|
||||
return this.me?.stashes.find(stash => stash.primary);
|
||||
}
|
||||
|
||||
function me(state) {
|
||||
@@ -108,11 +122,13 @@ function setSfw(enabled) {
|
||||
export default {
|
||||
computed: {
|
||||
...mapState({
|
||||
auth,
|
||||
login,
|
||||
signup,
|
||||
sfw,
|
||||
theme,
|
||||
me,
|
||||
}),
|
||||
favorites,
|
||||
},
|
||||
emits: ['showFilters'],
|
||||
methods: {
|
||||
@@ -134,20 +150,20 @@ export default {
|
||||
.menu-item {
|
||||
display: flex;
|
||||
padding: .75rem 1rem .75rem .75rem;
|
||||
color: inherit;
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
|
||||
.icon {
|
||||
fill: var(--darken);
|
||||
fill: var(--shadow);
|
||||
margin: 0 1rem 0 0;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
color: var(--darken-weak);
|
||||
color: var(--shadow-weak);
|
||||
cursor: default;
|
||||
|
||||
.icon {
|
||||
fill: var(--darken-weak);
|
||||
fill: var(--shadow-weak);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,10 +180,10 @@ export default {
|
||||
.menu-username {
|
||||
display: block;
|
||||
font-weight: bold;
|
||||
color: var(--darken-strong);
|
||||
color: var(--shadow-strong);
|
||||
font-size: .9rem;
|
||||
padding: .75rem 1rem;
|
||||
border-bottom: solid 1px var(--darken-hint);
|
||||
border-bottom: solid 1px var(--shadow-hint);
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
341
assets/components/header/notifications.vue
Normal file
@@ -0,0 +1,341 @@
|
||||
<template>
|
||||
<div class="notifications">
|
||||
<div class="notifications-header">
|
||||
<h4 class="notifications-title">Notifications</h4>
|
||||
|
||||
<div class="notifications-actions">
|
||||
<Icon
|
||||
v-if="unseenCount > 0"
|
||||
v-tooltip="'Mark all as seen'"
|
||||
icon="checkmark"
|
||||
@click="checkNotifications"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-tooltip="'Add alert'"
|
||||
icon="plus3"
|
||||
@click="$emit('addAlert')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="notifications-body">
|
||||
<div
|
||||
v-if="notifications.length === 0"
|
||||
class="notifications-empty"
|
||||
>No notifications</div>
|
||||
|
||||
<ul
|
||||
v-else
|
||||
class="nolist"
|
||||
>
|
||||
<li
|
||||
v-for="notification in notifications"
|
||||
:key="`notification-${notification.id}`"
|
||||
:class="{ unseen: !notification.seen }"
|
||||
class="notification"
|
||||
@click="checkNotification(notification.id, true)"
|
||||
>
|
||||
<router-link
|
||||
:to="`/scene/${notification.scene.id}/${notification.scene.slug}`"
|
||||
class="notification-link"
|
||||
>
|
||||
<img
|
||||
:src="getPath(notification.scene.poster, 'thumbnail')"
|
||||
class="poster"
|
||||
>
|
||||
|
||||
<div class="notification-body">
|
||||
<div class="notification-row notification-title">
|
||||
<img
|
||||
v-if="notification.scene.entity.type === 'network' || notification.scene.entity.independent"
|
||||
v-tooltip="notification.scene.entity.name"
|
||||
:src="`/img/logos/${notification.scene.entity.slug}/favicon_${theme === 'dark' ? 'light' : 'dark'}.png`"
|
||||
class="notification-favicon"
|
||||
>
|
||||
|
||||
<img
|
||||
v-else
|
||||
v-tooltip="notification.scene.entity.name"
|
||||
:src="`/img/logos/${notification.scene.entity.parent.slug}/favicon_${theme === 'dark' ? 'light' : 'dark'}.png`"
|
||||
class="notification-favicon"
|
||||
>
|
||||
|
||||
New <ul
|
||||
v-if="notification.alert?.tags.length > 0"
|
||||
class="nolist notification-tags"
|
||||
>
|
||||
<li
|
||||
v-for="tag in notification.alert.tags"
|
||||
:key="`notification-tag-${tag.slug}`"
|
||||
class="notification-tag"
|
||||
>{{ tag.name }}</li>
|
||||
</ul>scene
|
||||
</div>
|
||||
|
||||
<div class="notification-row notification-details">
|
||||
<span class="notification-date">{{ formatDate(notification.scene.date, 'MMM D') }}</span>
|
||||
|
||||
<ul
|
||||
v-if="notification.scene.actors.length > 0"
|
||||
class="nolist notification-actors"
|
||||
>
|
||||
<li
|
||||
v-for="actor in notification.scene.actors"
|
||||
:key="`notification-actor-${actor.slug}`"
|
||||
class="notification-actor"
|
||||
>{{ actor.name }}</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Icon
|
||||
v-if="!notification.seen"
|
||||
v-tooltip="'Mark as seen'"
|
||||
icon="checkmark"
|
||||
class="notification-check"
|
||||
@click.prevent.stop="checkNotification(notification.id)"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-if="notification.alert"
|
||||
v-tooltip="`You set an alert for <strong>${notification.alert.tags.map(tag => tag.name).join(', ') || 'all'}</strong> scenes with <strong>${notification.alert.actors.map(actor => actor.name).join(', ') || 'any actor'}</strong> for <strong>${notification.alert.entity?.name || 'any channel'}</strong>`"
|
||||
icon="question5"
|
||||
@click.prevent.stop
|
||||
/>
|
||||
</router-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div @click="events.emit('blur')">
|
||||
<router-link
|
||||
to="/notifications"
|
||||
class="notification-link notification-more"
|
||||
>See all</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
async function checkNotifications() {
|
||||
await this.$store.dispatch('checkNotifications');
|
||||
|
||||
this.$emit('check');
|
||||
}
|
||||
|
||||
async function checkNotification(notificationId, blur) {
|
||||
await this.$store.dispatch('checkNotification', notificationId);
|
||||
|
||||
this.$emit('check');
|
||||
|
||||
if (blur) {
|
||||
this.events.emit('blur');
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
notifications: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
unseenCount: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showAddAlert: false,
|
||||
};
|
||||
},
|
||||
emits: ['addAlert'],
|
||||
methods: {
|
||||
checkNotifications,
|
||||
checkNotification,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.notifications {
|
||||
width: 30rem;
|
||||
max-height: calc(100vh - 5rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.notifications-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.icon {
|
||||
padding: .5rem;
|
||||
fill: var(--shadow);
|
||||
|
||||
&:first-child {
|
||||
padding: .5rem .5rem .5rem 1.5rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding: .5rem 1.5rem .5rem .5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
fill: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notifications-title {
|
||||
display: inline-block;
|
||||
padding: .5rem 1rem;
|
||||
margin: 0;
|
||||
color: var(--shadow);
|
||||
font-size: 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.notifications-body {
|
||||
flex-grow: 1;
|
||||
overflow-y: auto;
|
||||
box-shadow: 0 0 3px var(--shadow-weak);
|
||||
}
|
||||
|
||||
.notifications-empty {
|
||||
padding: .5rem 1rem;
|
||||
color: var(--shadow);
|
||||
}
|
||||
|
||||
.notification {
|
||||
display: block;
|
||||
border-right: solid .5rem var(--shadow-touch);
|
||||
color: var(--text);
|
||||
|
||||
&.unseen {
|
||||
border-right: solid .5rem var(--primary);
|
||||
}
|
||||
|
||||
.icon {
|
||||
padding: 1.3rem .5rem;
|
||||
fill: var(--shadow-weak);
|
||||
|
||||
&.notification-check {
|
||||
padding: 1.3rem .5rem 1.3rem 1rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding: 1.3rem 1rem 1.3rem .5rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:last-child) {
|
||||
border-bottom: solid 1px var(--shadow-hint);
|
||||
margin: 0 0 -1px 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--shadow-touch);
|
||||
|
||||
&:not(.unseen) {
|
||||
border-right: solid .5rem var(--shadow-weak);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.notification-link {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.notification-body {
|
||||
flex-grow: 1;
|
||||
padding: .4rem 0 0 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.notification-row {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.notification-title {
|
||||
margin: .15rem .5rem .3rem .5rem;
|
||||
}
|
||||
|
||||
.notification-favicon {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin: 0 .5rem 0 0;
|
||||
}
|
||||
|
||||
.notification-tags {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.notification-actors {
|
||||
padding: 0 .5rem;
|
||||
height: 1.25rem;
|
||||
display: inline-block;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.notification-date {
|
||||
width: 3rem;
|
||||
flex-shrink: 0;
|
||||
padding: .25rem .25rem .35rem .25rem;
|
||||
border-right: solid 1px var(--shadow-hint);
|
||||
border-top: solid 1px var(--shadow-hint);
|
||||
color: var(--shadow-strong);
|
||||
font-size: .8rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.notification-actor,
|
||||
.notification-tag {
|
||||
white-space: nowrap;
|
||||
|
||||
&:not(:last-child)::after {
|
||||
content: ',';
|
||||
padding: 0 .1rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-actor {
|
||||
padding: .25rem .15rem .35rem 0;
|
||||
color: var(--shadow-strong);
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.notification-tag {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.notification-more {
|
||||
display: block;
|
||||
padding: .5rem 1rem;
|
||||
color: var(--shadow);
|
||||
text-align: center;
|
||||
font-size: .9rem;
|
||||
font-weight: bold;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.poster {
|
||||
width: 6rem;
|
||||
height: 3.6rem;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
</style>
|
||||
@@ -51,6 +51,7 @@ export default {
|
||||
query: this.$route.query ? this.$route.query.q : null,
|
||||
};
|
||||
},
|
||||
emits: ['search'],
|
||||
watch: {
|
||||
$route: route,
|
||||
searching,
|
||||
@@ -71,8 +72,8 @@ export default {
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
padding: 0 1rem 0 0;
|
||||
border-left: solid 1px var(--shadow-hint);
|
||||
margin: 0 .25rem 0 0;
|
||||
|
||||
&.compact {
|
||||
padding: 0;
|
||||
@@ -92,7 +93,7 @@ export default {
|
||||
.search-input {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: .5rem 0 .5rem .5rem;
|
||||
padding: .5rem 0 .5rem .75rem;
|
||||
border: none;
|
||||
color: var(--text);
|
||||
background: var(--background);
|
||||
@@ -119,14 +120,20 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
&:focus::placeholder {
|
||||
color: var(--shadow-weak);
|
||||
&:focus {
|
||||
&::placeholder {
|
||||
color: var(--shadow-weak);
|
||||
}
|
||||
|
||||
& + .search-button:not(:hover) .icon {
|
||||
fill: var(--shadow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.search-button {
|
||||
height: 100%;
|
||||
padding: 0 1rem;
|
||||
padding: 0 1.25rem 0 1rem;
|
||||
background: none;
|
||||
border: none;
|
||||
margin: .3rem 0 0 0;
|
||||
@@ -140,7 +147,7 @@ export default {
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: var(--shadow);
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,13 @@
|
||||
:items-total="totalCount"
|
||||
:items-per-page="limit"
|
||||
:content="$refs.content"
|
||||
:show-total="false"
|
||||
/>
|
||||
|
||||
<Releases :releases="releases" />
|
||||
<Releases
|
||||
:releases="releases"
|
||||
:done="done"
|
||||
/>
|
||||
|
||||
<Pagination
|
||||
v-if="totalCount > 0"
|
||||
@@ -30,6 +34,8 @@ import Releases from '../releases/releases.vue';
|
||||
import Pagination from '../pagination/pagination.vue';
|
||||
|
||||
async function fetchReleases(scroll = true) {
|
||||
this.done = false;
|
||||
|
||||
const { releases, totalCount } = await this.$store.dispatch('fetchReleases', {
|
||||
limit: this.limit,
|
||||
range: this.$route.params.range,
|
||||
@@ -38,6 +44,7 @@ async function fetchReleases(scroll = true) {
|
||||
|
||||
this.totalCount = totalCount;
|
||||
this.releases = releases;
|
||||
this.done = true;
|
||||
|
||||
if (scroll) {
|
||||
this.$refs.filter?.$el.scrollIntoView();
|
||||
@@ -64,6 +71,7 @@ export default {
|
||||
limit: 30,
|
||||
totalCount: 0,
|
||||
from: null,
|
||||
done: false,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
||||
@@ -28,7 +28,7 @@ export default {
|
||||
};
|
||||
},
|
||||
beforeMount() {
|
||||
this.svg = require(`../../img/icons/${this.icon}.svg`).default;
|
||||
this.svg = require(`../../img/icons/${this.icon}.svg`).default; // eslint-disable-line global-require, import/no-dynamic-require
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
90
assets/components/loading/ellipsis.vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="load-container">
|
||||
<div class="load-ellipsis">
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
<div />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.load-container {
|
||||
display: inline-flex;
|
||||
position: relative;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.load-ellipsis {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
width: 5rem;
|
||||
height: .75rem;
|
||||
}
|
||||
|
||||
.load-ellipsis div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: .75rem;
|
||||
height: .75rem;
|
||||
border-radius: 50%;
|
||||
background: var(--primary);
|
||||
}
|
||||
|
||||
.load-ellipsis div:nth-child(1) {
|
||||
left: .5rem;
|
||||
animation: load-ellipsis1 0.5s infinite linear;
|
||||
}
|
||||
|
||||
.load-ellipsis div:nth-child(2) {
|
||||
left: .5rem;
|
||||
animation: load-ellipsis2 0.5s infinite linear;
|
||||
}
|
||||
|
||||
.load-ellipsis div:nth-child(3) {
|
||||
left: 2rem;
|
||||
animation: load-ellipsis3 0.5s infinite linear;
|
||||
}
|
||||
|
||||
.load-ellipsis div:nth-child(4) {
|
||||
left: 3.5rem;
|
||||
animation: load-ellipsis4 0.5s infinite linear;
|
||||
}
|
||||
|
||||
@keyframes load-ellipsis1 {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load-ellipsis2 {
|
||||
0% {
|
||||
transform: translate(0, 0) scale(0.5);
|
||||
}
|
||||
100% {
|
||||
transform: translate(1.5rem, 0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load-ellipsis3 {
|
||||
0% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
100% {
|
||||
transform: translate(1.5rem, 0) scale(0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes load-ellipsis4 {
|
||||
0% {
|
||||
transform: scale(0.5);
|
||||
}
|
||||
100% {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,44 +1,41 @@
|
||||
<template>
|
||||
<div class="networks">
|
||||
<form
|
||||
class="search"
|
||||
@submit.prevent="searchEntities"
|
||||
>
|
||||
<input
|
||||
v-model="query"
|
||||
:placeholder="`Find ${channelCount} channels in ${entities.length} networks`"
|
||||
class="query"
|
||||
@input="searchEntities"
|
||||
>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="search-button"
|
||||
><Icon icon="search" /></button>
|
||||
</form>
|
||||
|
||||
<div
|
||||
v-if="query.length > 0"
|
||||
class="entity-tiles"
|
||||
>
|
||||
<Entity
|
||||
v-for="entity in searchResults"
|
||||
:key="`${entity.type}-tile-${entity.slug}`"
|
||||
:entity="entity"
|
||||
<div class="content-inner">
|
||||
<SearchBar
|
||||
:placeholder="`Search ${channelCount} channels in ${entities.length} networks`"
|
||||
:eager="true"
|
||||
/>
|
||||
|
||||
<span v-if="searchResults.length === 0">No results for "{{ query }}"</span>
|
||||
</div>
|
||||
<span
|
||||
v-if="done && entities.length === 0"
|
||||
class="empty"
|
||||
>No results for "{{ $route.query.query }}"</span>
|
||||
|
||||
<div
|
||||
v-else
|
||||
class="entity-tiles"
|
||||
>
|
||||
<Entity
|
||||
v-for="entity in entities"
|
||||
:key="`entity-tile-${entity.slug}`"
|
||||
:entity="entity"
|
||||
/>
|
||||
<template v-else>
|
||||
<h2 class="heading">Popular</h2>
|
||||
|
||||
<div
|
||||
class="entity-tiles"
|
||||
>
|
||||
<Entity
|
||||
v-for="entity in popularEntities"
|
||||
:key="entity.parent ? `entity-tile-${entity.parent.slug}-${entity.slug}` : `entity-tile-${entity.slug}`"
|
||||
:entity="entity"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<h2 class="heading">All networks</h2>
|
||||
|
||||
<div
|
||||
class="entity-tiles"
|
||||
>
|
||||
<Entity
|
||||
v-for="entity in entities"
|
||||
:key="entity.parent ? `entity-tile-${entity.parent.slug}-${entity.slug}` : `entity-tile-${entity.slug}`"
|
||||
:entity="entity"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
@@ -47,21 +44,78 @@
|
||||
|
||||
<script>
|
||||
import Entity from '../entities/tile.vue';
|
||||
import SearchBar from '../search/bar.vue';
|
||||
|
||||
async function searchEntities() {
|
||||
this.searchResults = await this.$store.dispatch('searchEntities', {
|
||||
query: this.query,
|
||||
limit: 50,
|
||||
});
|
||||
}
|
||||
async function fetchEntities() {
|
||||
if (this.$route.query.query) {
|
||||
await this.searchEntities();
|
||||
return;
|
||||
}
|
||||
|
||||
this.done = false;
|
||||
|
||||
async function mounted() {
|
||||
this.entities = await this.$store.dispatch('fetchEntities', {
|
||||
type: 'network',
|
||||
entitySlugs: [],
|
||||
});
|
||||
|
||||
this.pageTitle = 'Networks';
|
||||
this.done = true;
|
||||
}
|
||||
|
||||
async function searchEntities() {
|
||||
this.done = false;
|
||||
|
||||
this.entities = await this.$store.dispatch('searchEntities', {
|
||||
query: this.$route.query.query,
|
||||
limit: 20,
|
||||
});
|
||||
|
||||
this.done = true;
|
||||
}
|
||||
|
||||
function popularEntities() {
|
||||
const entitiesBySlug = Object.fromEntries(this.entities.map((entity) => [entity.slug, entity]));
|
||||
|
||||
return [
|
||||
'21sextury',
|
||||
'amateurallure',
|
||||
'analvids',
|
||||
'bamvisions',
|
||||
'bang',
|
||||
'bangbros',
|
||||
'blowpass',
|
||||
'brazzers',
|
||||
'burningangel',
|
||||
'digitalplayground',
|
||||
'dogfartnetwork',
|
||||
'dorcel',
|
||||
'elegantangel',
|
||||
'evilangel',
|
||||
'fakehub',
|
||||
'girlsway',
|
||||
'hookuphotshot',
|
||||
'hussiepass',
|
||||
'insex',
|
||||
'julesjordan',
|
||||
'kellymadison',
|
||||
'kink',
|
||||
'mofos',
|
||||
'naughtyamerica',
|
||||
'newsensations',
|
||||
'pervcity',
|
||||
'pornpros',
|
||||
'private',
|
||||
'realitykings',
|
||||
'twistys',
|
||||
'vixen',
|
||||
'xempire',
|
||||
].map((slug) => entitiesBySlug[slug]).filter(Boolean);
|
||||
}
|
||||
|
||||
async function mounted() {
|
||||
this.pageTitle = 'Channels';
|
||||
|
||||
await this.fetchEntities();
|
||||
}
|
||||
|
||||
function channelCount() {
|
||||
@@ -71,20 +125,25 @@ function channelCount() {
|
||||
export default {
|
||||
components: {
|
||||
Entity,
|
||||
SearchBar,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
query: '',
|
||||
done: false,
|
||||
pageTitle: null,
|
||||
entities: [],
|
||||
searchResults: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
channelCount,
|
||||
popularEntities,
|
||||
},
|
||||
watch: {
|
||||
$route: fetchEntities,
|
||||
},
|
||||
mounted,
|
||||
methods: {
|
||||
fetchEntities,
|
||||
searchEntities,
|
||||
},
|
||||
};
|
||||
@@ -94,49 +153,17 @@ export default {
|
||||
@import 'theme';
|
||||
|
||||
.networks {
|
||||
padding: 0 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.content-inner {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.search {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
max-width: 40rem;
|
||||
}
|
||||
|
||||
.query {
|
||||
color: var(--text);
|
||||
background: var(--background);
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 3px var(--darken-weak);
|
||||
margin: 1rem 0;
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 3px var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.search-button {
|
||||
padding: 1rem;
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
.icon {
|
||||
fill: var(--shadow);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
margin: 1rem 0 0 0;
|
||||
}
|
||||
|
||||
.entity-tiles {
|
||||
@@ -144,6 +171,22 @@ export default {
|
||||
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
|
||||
grid-gap: .5rem;
|
||||
padding: 1rem 0;
|
||||
|
||||
.tile {
|
||||
height: 6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: block;
|
||||
margin: 1rem 0;
|
||||
color: var(--shadow);
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin: 1rem 0 0 0;
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint2) {
|
||||
|
||||
76
assets/components/notifications/notifications.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<template>
|
||||
<div
|
||||
ref="page"
|
||||
class="notifications-container"
|
||||
>
|
||||
<h1 class="heading">Notifications</h1>
|
||||
|
||||
<ul class="notifications nolist">
|
||||
<li
|
||||
v-for="notification in notifications"
|
||||
:key="notification.id"
|
||||
>
|
||||
<SceneTile :release="notification.scene" />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<Pagination
|
||||
:items-total="totalCount"
|
||||
:items-per-page="10"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Pagination from '../pagination/pagination.vue';
|
||||
import SceneTile from '../releases/scene-tile.vue';
|
||||
|
||||
async function fetchNotifications() {
|
||||
const { notifications, totalCount, unseenCount } = await this.$store.dispatch('fetchNotifications', {
|
||||
page: this.$route.params.pageNumber,
|
||||
limit: this.limit,
|
||||
});
|
||||
|
||||
this.notifications = notifications;
|
||||
this.unseenNotificationsCount = unseenCount;
|
||||
this.totalCount = totalCount;
|
||||
|
||||
this.$emit('scroll');
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Pagination,
|
||||
SceneTile,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
notifications: [],
|
||||
limit: 10,
|
||||
totalCount: 0,
|
||||
unseenNotificationsCount: 0,
|
||||
};
|
||||
},
|
||||
emits: ['scroll'],
|
||||
watch: {
|
||||
$route: fetchNotifications,
|
||||
},
|
||||
mounted: fetchNotifications,
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.notifications-container {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.notifications {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
|
||||
grid-gap: .5rem;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin: 1rem 0 0 0;
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="media-container">
|
||||
<div
|
||||
class="media"
|
||||
:class="{ center: release.photos.length < 2 }"
|
||||
:class="{ center: (release.photos?.length || 0) + (release.scenesPhotos?.length || 0) < 2, preview: !me }"
|
||||
>
|
||||
<div
|
||||
v-if="release.trailer || release.teaser"
|
||||
@@ -17,7 +17,6 @@
|
||||
@pause="playing = false; paused = true;"
|
||||
/>
|
||||
|
||||
|
||||
<Player
|
||||
v-else-if="release.teaser && /^video\//.test(release.teaser.mime)"
|
||||
:video="release.teaser"
|
||||
@@ -39,15 +38,30 @@
|
||||
class="item trailer"
|
||||
>
|
||||
|
||||
<a
|
||||
v-if="release.poster"
|
||||
v-tooltip="'View poster'"
|
||||
:href="getPath(release.poster)"
|
||||
:class="{ playing }"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="poster-link"
|
||||
><Icon icon="image" /></a>
|
||||
<span
|
||||
v-if="release.poster || release.teaser"
|
||||
class="poster-links"
|
||||
>
|
||||
<a
|
||||
v-if="release.teaser"
|
||||
v-tooltip="'View teaser'"
|
||||
:href="getPath(release.teaser)"
|
||||
:class="{ playing }"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="poster-link"
|
||||
><Icon icon="film4" /></a>
|
||||
|
||||
<a
|
||||
v-if="release.poster"
|
||||
v-tooltip="'View poster'"
|
||||
:href="getPath(release.poster)"
|
||||
:class="{ playing }"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="poster-link"
|
||||
><Icon icon="image" /></a>
|
||||
</span>
|
||||
|
||||
<span
|
||||
v-if="sfw && !playing"
|
||||
@@ -57,24 +71,28 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<template v-if="release.covers && release.covers.length > 0">
|
||||
<a
|
||||
<template v-if="release.covers?.length > 0">
|
||||
<div
|
||||
v-for="cover in release.covers"
|
||||
:key="`cover-${cover.id}`"
|
||||
:href="getPath(cover)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="item-container"
|
||||
>
|
||||
<img
|
||||
:src="getPath(cover, 'thumbnail')"
|
||||
:style="{ 'background-image': getBgPath(cover, 'lazy') }"
|
||||
:width="cover.thumbnailWidth"
|
||||
:height="cover.thumbnailHeight"
|
||||
class="item cover"
|
||||
loading="lazy"
|
||||
@load="$emit('load', $event)"
|
||||
<a
|
||||
:href="getPath(cover)"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
</a>
|
||||
<img
|
||||
:src="getPath(cover, 'thumbnail')"
|
||||
:style="{ 'background-image': getBgPath(cover, 'lazy') }"
|
||||
:width="cover.thumbnailWidth"
|
||||
:height="cover.thumbnailHeight"
|
||||
class="item cover"
|
||||
loading="lazy"
|
||||
@load="$emit('load', $event)"
|
||||
>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div
|
||||
@@ -108,6 +126,14 @@
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!me"
|
||||
class="item-container item-more"
|
||||
><router-link
|
||||
:to="{ name: 'login', query: { ref: $route.path } }"
|
||||
class="link"
|
||||
>Log in</router-link> for more photos, trailers and features!</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -119,6 +145,10 @@ function sfw() {
|
||||
return this.$store.state.ui.sfw;
|
||||
}
|
||||
|
||||
function me() {
|
||||
return this.$store.state.auth.user;
|
||||
}
|
||||
|
||||
function poster() {
|
||||
if (this.release.poster) {
|
||||
return this.getPath(this.release.poster, 'thumbnail');
|
||||
@@ -138,8 +168,8 @@ function poster() {
|
||||
function photos() {
|
||||
const clips = this.release.clips || [];
|
||||
const clipPostersById = clips.reduce((acc, clip) => ({ ...acc, [clip.poster.id]: clip.poster }), {});
|
||||
const uniqueClipPosters = Array.from(new Set(clips.map(clip => clip.poster.id) || [])).map(posterId => clipPostersById[posterId]);
|
||||
const photosWithClipPosters = (this.release.photos || []).concat(uniqueClipPosters);
|
||||
const uniqueClipPosters = Array.from(new Set(clips.map((clip) => clip.poster.id) || [])).map((posterId) => clipPostersById[posterId]);
|
||||
const photosWithClipPosters = (this.release.photos || []).concat(this.release.scenesPhotos || []).concat(uniqueClipPosters);
|
||||
|
||||
if (this.release.trailer || (this.release.teaser && this.release.teaser.mime !== 'image/gif')) {
|
||||
// poster will be on trailer video
|
||||
@@ -182,6 +212,7 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
me,
|
||||
photos,
|
||||
poster,
|
||||
sfw,
|
||||
@@ -192,10 +223,6 @@ export default {
|
||||
<style lang="scss" scoped>
|
||||
@import 'breakpoints';
|
||||
|
||||
.media-container {
|
||||
backdrop-filter: blur(1rem);
|
||||
}
|
||||
|
||||
.media {
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
@@ -205,14 +232,20 @@ export default {
|
||||
.media.center {
|
||||
width: 1200px;
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.poster-link {
|
||||
.poster-links {
|
||||
display: flex;
|
||||
position: absolute;
|
||||
top: .5rem;
|
||||
right: .5rem;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.poster-link {
|
||||
transition: opacity .1s ease;
|
||||
padding: .5rem;
|
||||
|
||||
.icon {
|
||||
width: 1.5rem;
|
||||
@@ -225,6 +258,14 @@ export default {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
padding-left: .75rem;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-right: .75rem;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
@@ -285,6 +326,22 @@ export default {
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.item-more {
|
||||
height: auto;
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
padding: .5rem 2rem;
|
||||
color: var(--text-light);
|
||||
text-shadow: 0 0 3px var(--darken);
|
||||
font-weight: bold;
|
||||
font-size: 1rem;
|
||||
|
||||
.link {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.trailer-container {
|
||||
width: 32rem;
|
||||
max-width: 100%;
|
||||
@@ -312,6 +369,14 @@ export default {
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-micro) {
|
||||
.media.center.preview {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.item-more {
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.media:not(.expanded) .item,
|
||||
.trailer-container {
|
||||
height: 56vw; /* 16:9 ratio for full-width video */
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<Details :release="movie" />
|
||||
|
||||
<div class="movie">
|
||||
<router-link
|
||||
<RouterLink
|
||||
:to="{ name: 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }"
|
||||
class="cover"
|
||||
>
|
||||
@@ -13,15 +13,42 @@
|
||||
:style="{ 'background-image': getBgPath(movie.covers[0], 'lazy') }"
|
||||
loading="lazy"
|
||||
>
|
||||
</router-link>
|
||||
|
||||
<div
|
||||
v-else
|
||||
:title="movie.title"
|
||||
class="unavailable"
|
||||
><Icon icon="blocked" /></div>
|
||||
|
||||
<Icon
|
||||
v-show="(!stash || stash.primary) && favorited"
|
||||
icon="heart7"
|
||||
class="stash stashed"
|
||||
@click.prevent.native="unstashMovie"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-show="(!stash || stash.primary) && favorited === false"
|
||||
icon="heart8"
|
||||
class="stash unstashed"
|
||||
@click.prevent.native="stashMovie"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-show="stash && !stash.primary"
|
||||
icon="cross2"
|
||||
class="stash unstash"
|
||||
@click.prevent.native="unstashMovie"
|
||||
/>
|
||||
</RouterLink>
|
||||
|
||||
<div class="info">
|
||||
<router-link
|
||||
<RouterLink
|
||||
:to="{ name: 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }"
|
||||
class="title-link"
|
||||
>
|
||||
<h3 class="title">{{ movie.title }}</h3>
|
||||
</router-link>
|
||||
</RouterLink>
|
||||
|
||||
<ul
|
||||
class="actors nolist"
|
||||
@@ -31,10 +58,10 @@
|
||||
v-for="actor in movie.actors"
|
||||
:key="`tag-${movie.id}-${actor.id}`"
|
||||
class="actor"
|
||||
><router-link
|
||||
><RouterLink
|
||||
:to="`/actor/${actor.id}/${actor.slug}`"
|
||||
class="actor-link"
|
||||
>{{ actor.name }}</router-link></li>
|
||||
>{{ actor.name }}</RouterLink></li>
|
||||
</ul>
|
||||
|
||||
<ul
|
||||
@@ -45,10 +72,10 @@
|
||||
v-for="tag in movie.tags"
|
||||
:key="`tag-${movie.id}-${tag.id}`"
|
||||
class="tag"
|
||||
><router-link
|
||||
><RouterLink
|
||||
:to="`/tag/${tag.slug}`"
|
||||
class="tag-link"
|
||||
>{{ tag.name }}</router-link></li>
|
||||
>{{ tag.name }}</RouterLink></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -58,6 +85,38 @@
|
||||
<script>
|
||||
import Details from './tile-details.vue';
|
||||
|
||||
async function stashMovie() {
|
||||
this.favorited = true;
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('stashMovie', {
|
||||
movieId: this.movie.id,
|
||||
stashId: this.$store.getters.favorites.id,
|
||||
});
|
||||
|
||||
this.$emit('stash', true);
|
||||
} catch (error) {
|
||||
this.favorited = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function unstashMovie() {
|
||||
if (!this.stash || this.stash.primary) {
|
||||
this.favorited = false;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('unstashMovie', {
|
||||
movieId: this.movie.id,
|
||||
stashId: this.stash?.id || this.$store.getters.favorites.id,
|
||||
});
|
||||
|
||||
this.$emit('stash', false);
|
||||
} catch (error) {
|
||||
this.favorited = true;
|
||||
}
|
||||
}
|
||||
|
||||
function sfw() {
|
||||
return this.$store.state.ui.sfw;
|
||||
}
|
||||
@@ -71,10 +130,23 @@ export default {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
stash: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
favorited: this.movie.isFavorited,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
sfw,
|
||||
},
|
||||
methods: {
|
||||
stashMovie,
|
||||
unstashMovie,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -87,10 +159,14 @@ export default {
|
||||
background: var(--background);
|
||||
box-shadow: 0 0 3px var(--darken-weak);
|
||||
font-size: 0;
|
||||
|
||||
&:hover .unstashed,
|
||||
&:hover .unstash {
|
||||
fill: var(--lighten);
|
||||
}
|
||||
}
|
||||
|
||||
.movie {
|
||||
height: 16rem;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -100,17 +176,30 @@ export default {
|
||||
}
|
||||
|
||||
.cover {
|
||||
height: 100%;
|
||||
height: 16rem;
|
||||
width: 11.25rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
box-shadow: 0 0 3px var(--darken-weak);
|
||||
background-color: var(--shadow-hint);
|
||||
|
||||
img {
|
||||
height: 100%;
|
||||
max-width: 12rem;
|
||||
width: 100%;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
object-fit: cover;
|
||||
object-position: center;
|
||||
}
|
||||
|
||||
.unavailable .icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
fill: var(--shadow-hint);
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
@@ -179,9 +268,31 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint) {
|
||||
.movie {
|
||||
.stash {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: .25rem .5rem .5rem .5rem;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
fill: var(--lighten-weak);
|
||||
filter: drop-shadow(0 0 2px var(--darken));
|
||||
|
||||
&:hover.unstashed,
|
||||
&.stashed {
|
||||
fill: var(--primary);
|
||||
}
|
||||
|
||||
&:hover.unstash {
|
||||
fill: var(--text-light);
|
||||
filter: drop-shadow(0 0 2px var(--darken-weak));
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-kilo) {
|
||||
.cover {
|
||||
height: 12rem;
|
||||
width: 8.25rem;
|
||||
}
|
||||
|
||||
/* ensure no half actor names show */
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
<template>
|
||||
<div class="movies">
|
||||
<div class="content-inner">
|
||||
<div class="tiles">
|
||||
<SearchBar :placeholder="`Search ${totalCount} movies`" />
|
||||
|
||||
<div
|
||||
ref="tiles"
|
||||
class="tiles"
|
||||
>
|
||||
<MovieTile
|
||||
v-for="movie in movies"
|
||||
:key="`movie-${movie.id}`"
|
||||
:movie="movie"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Pagination
|
||||
v-if="totalCount > 0"
|
||||
:items-total="totalCount"
|
||||
:items-per-page="limit"
|
||||
class="pagination-bottom"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Footer />
|
||||
@@ -16,27 +28,67 @@
|
||||
|
||||
<script>
|
||||
import MovieTile from './movie-tile.vue';
|
||||
import SearchBar from '../search/bar.vue';
|
||||
import Pagination from '../pagination/pagination.vue';
|
||||
|
||||
async function fetchMovies() {
|
||||
if (this.$route.query.query) {
|
||||
await this.searchMovies();
|
||||
return;
|
||||
}
|
||||
|
||||
async function mounted() {
|
||||
const { movies, totalCount } = await this.$store.dispatch('fetchMovies', {
|
||||
limit: 30,
|
||||
limit: this.limit,
|
||||
range: this.$route.params.range,
|
||||
pageNumber: this.$route.params.pageNumber,
|
||||
});
|
||||
|
||||
this.movies = movies;
|
||||
this.totalCount = totalCount;
|
||||
|
||||
this.$refs.tiles.scrollIntoView();
|
||||
}
|
||||
|
||||
async function searchMovies() {
|
||||
const { movies, totalCount } = await this.$store.dispatch('searchMovies', {
|
||||
query: this.$route.query.query,
|
||||
limit: this.limit,
|
||||
pageNumber: this.$route.params.pageNumber,
|
||||
});
|
||||
|
||||
this.movies = movies;
|
||||
this.totalCount = totalCount;
|
||||
|
||||
this.$refs.tiles.scrollIntoView();
|
||||
}
|
||||
|
||||
async function mounted() {
|
||||
this.pageTitle = 'Movies';
|
||||
|
||||
await this.fetchMovies();
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
MovieTile,
|
||||
SearchBar,
|
||||
Pagination,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
movies: [],
|
||||
totalCount: 0,
|
||||
limit: 20,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route: fetchMovies,
|
||||
},
|
||||
mounted,
|
||||
methods: {
|
||||
fetchMovies,
|
||||
searchMovies,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -49,14 +101,22 @@ export default {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.content-inner {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.search {
|
||||
margin: 1rem 0 0 0;
|
||||
}
|
||||
|
||||
.tiles {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(25rem, 1fr));
|
||||
grid-gap: 1rem;
|
||||
padding: 1rem;
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint) {
|
||||
@media(max-width: $breakpoint-kilo) {
|
||||
.tiles {
|
||||
grid-template-columns: repeat(auto-fill, minmax(20rem, 1fr));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
v-if="release"
|
||||
ref="content"
|
||||
class="content"
|
||||
@scroll="events.emit('scroll', $event)"
|
||||
>
|
||||
<Scroll
|
||||
v-slot="slotProps"
|
||||
@@ -20,14 +21,14 @@
|
||||
<Details :release="release" />
|
||||
|
||||
<button
|
||||
v-if="release.photos.length > 0"
|
||||
v-if="release.photos?.length > 0 || release.scenesPhotos?.length > 0"
|
||||
class="album-toggle"
|
||||
@click="$router.push({ hash: '#album' })"
|
||||
><Icon icon="grid3" />View album</button>
|
||||
|
||||
<Album
|
||||
v-if="showAlbum"
|
||||
:items="[release.poster, ...release.photos]"
|
||||
:items="[release.poster, ...(release.photos || []), ...(release.scenesPhotos || [])]"
|
||||
:title="release.title"
|
||||
:path="config.media.mediaPath"
|
||||
@close="$router.replace({ hash: undefined })"
|
||||
@@ -38,7 +39,10 @@
|
||||
<h2
|
||||
v-if="release.title"
|
||||
class="title"
|
||||
>{{ release.title }}</h2>
|
||||
>
|
||||
{{ release.title }}
|
||||
<template v-if="release.movies?.[0]?.title && /^scene \d+$/i.test(release.title)"><span class="title-composed"> from </span>{{ release.movies[0].title }}</template>
|
||||
</h2>
|
||||
|
||||
<h2
|
||||
v-else-if="release.actors.length > 0"
|
||||
@@ -51,18 +55,10 @@
|
||||
/>
|
||||
</h2>
|
||||
|
||||
<Icon
|
||||
v-show="me && release.isStashed"
|
||||
icon="heart7"
|
||||
class="stash stashed noselect"
|
||||
@click="unstashScene"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-show="me && !release.isStashed"
|
||||
icon="heart8"
|
||||
class="stash unstashed noselect"
|
||||
@click="stashScene"
|
||||
<StashButton
|
||||
:stashed-by="stashedBy"
|
||||
@stash="(stash) => stashScene(stash)"
|
||||
@unstash="(stash) => unstashScene(stash)"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -82,6 +78,35 @@
|
||||
:tags="release.tags"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="release.movies?.length > 0 || release.series?.length > 0"
|
||||
class="row"
|
||||
>
|
||||
<span class="row-label">Part of</span>
|
||||
|
||||
<div class="movies">
|
||||
<router-link
|
||||
v-for="movie in [...release.movies, ...release.series]"
|
||||
:key="`movie-${movie.id}`"
|
||||
:to="{ name: movie.type || 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }"
|
||||
class="movie"
|
||||
>
|
||||
<span class="movie-title">{{ movie.title }}</span>
|
||||
|
||||
<img
|
||||
v-if="movie.covers.length > 0 || movie.poster"
|
||||
:src="getPath(movie.covers[0] || movie.poster, 'thumbnail')"
|
||||
class="movie-cover"
|
||||
>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Releases
|
||||
v-if="release.scenes && release.scenes.length > 0"
|
||||
:releases="release.scenes"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="release.directors && release.directors.length > 0"
|
||||
class="row"
|
||||
@@ -96,34 +121,6 @@
|
||||
>{{ director.name }}</router-link>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="release.movies && release.movies.length > 0"
|
||||
class="row"
|
||||
>
|
||||
<span class="row-label">Part of</span>
|
||||
|
||||
<div class="movies">
|
||||
<router-link
|
||||
v-for="movie in release.movies"
|
||||
:key="`movie-${movie.id}`"
|
||||
:to="{ name: 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }"
|
||||
class="movie"
|
||||
>
|
||||
<span class="movie-title">{{ movie.title }}</span>
|
||||
<img
|
||||
v-if="movie.covers.length > 0"
|
||||
:src="getPath(movie.covers[0], 'thumbnail')"
|
||||
class="movie-cover"
|
||||
>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Releases
|
||||
v-if="release.scenes && release.scenes.length > 0"
|
||||
:releases="release.scenes"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="release.description"
|
||||
class="row"
|
||||
@@ -230,6 +227,7 @@
|
||||
<script>
|
||||
import Details from './details.vue';
|
||||
import Banner from './banner.vue';
|
||||
import StashButton from '../stashes/button.vue';
|
||||
import Album from '../album/album.vue';
|
||||
import Tags from './tags.vue';
|
||||
import Chapters from './chapters.vue';
|
||||
@@ -246,29 +244,31 @@ async function fetchRelease(scroll = true) {
|
||||
this.release = await this.$store.dispatch('fetchMovieById', this.$route.params.releaseId);
|
||||
}
|
||||
|
||||
if (this.$route.name === 'serie') {
|
||||
this.release = await this.$store.dispatch('fetchSerieById', this.$route.params.releaseId);
|
||||
}
|
||||
|
||||
if (scroll && this.$refs.content) {
|
||||
this.$refs.content.scrollTop = 0;
|
||||
}
|
||||
|
||||
this.stashedBy = this.release.stashes;
|
||||
}
|
||||
|
||||
async function stashScene() {
|
||||
this.$store.dispatch(this.$route.name === 'movie' ? 'stashMovie' : 'stashScene', {
|
||||
async function stashScene(stashId) {
|
||||
this.stashedBy = await this.$store.dispatch(this.$route.name === 'movie' ? 'stashMovie' : 'stashScene', {
|
||||
sceneId: this.release.id,
|
||||
movieId: this.release.id,
|
||||
stashId: this.$store.getters.favorites.id,
|
||||
stashId,
|
||||
});
|
||||
|
||||
this.fetchRelease(false);
|
||||
}
|
||||
|
||||
async function unstashScene() {
|
||||
this.$store.dispatch(this.$route.name === 'movie' ? 'unstashMovie' : 'unstashScene', {
|
||||
async function unstashScene(stashId) {
|
||||
this.stashedBy = await this.$store.dispatch(this.$route.name === 'movie' ? 'unstashMovie' : 'unstashScene', {
|
||||
sceneId: this.release.id,
|
||||
movieId: this.release.id,
|
||||
stashId: this.$store.getters.favorites.id,
|
||||
stashId,
|
||||
});
|
||||
|
||||
this.fetchRelease(false);
|
||||
}
|
||||
|
||||
function me() {
|
||||
@@ -283,27 +283,29 @@ function bannerBackground() {
|
||||
function pageTitle() {
|
||||
return this.release
|
||||
&& (this.release.title
|
||||
|| (this.release.actors.length > 0 ? `${this.release.actors.map(actor => actor.name).join(', ')} for ${this.release.entity.name}` : null));
|
||||
|| (this.release.actors.length > 0 ? `${this.release.actors.map((actor) => actor.name).join(', ')} for ${this.release.entity.name}` : null));
|
||||
}
|
||||
|
||||
function showAlbum() {
|
||||
return this.release.photos?.length > 0 && this.$route.hash === '#album';
|
||||
return (this.release.photos?.length > 0 || this.release.scenesPhotos?.length > 0) && this.$route.hash === '#album';
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Actor,
|
||||
Album,
|
||||
Details,
|
||||
Banner,
|
||||
Scroll,
|
||||
Releases,
|
||||
Chapters,
|
||||
Details,
|
||||
Releases,
|
||||
Scroll,
|
||||
StashButton,
|
||||
Tags,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
release: null,
|
||||
stashedBy: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -333,6 +335,10 @@ export default {
|
||||
.banner {
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
|
||||
:deep(.scrollable) {
|
||||
backdrop-filter: blur(1rem);
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
@@ -394,22 +400,6 @@ export default {
|
||||
color: var(--shadow);
|
||||
}
|
||||
|
||||
.stash.icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: 0 1rem;
|
||||
fill: var(--darken);
|
||||
|
||||
&.stashed {
|
||||
fill: var(--primary);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
fill: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.album-toggle {
|
||||
height: fit-content;
|
||||
display: inline-flex;
|
||||
|
||||
@@ -5,8 +5,10 @@
|
||||
class="heading"
|
||||
><span class="range">{{ range }}</span> releases for '{{ context }}'</h3>
|
||||
|
||||
<Ellipsis v-if="!done" />
|
||||
|
||||
<ul
|
||||
v-if="releases.length > 0"
|
||||
v-else-if="releases.length > 0"
|
||||
:key="sfw"
|
||||
class="nolist tiles"
|
||||
>
|
||||
@@ -18,13 +20,14 @@
|
||||
:release="release"
|
||||
:referer="referer"
|
||||
:index="index"
|
||||
:stash="stash"
|
||||
@stash="isStashed => $emit('stash', isStashed)"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<span
|
||||
v-if="releases.length === 0 && range !== 'all'"
|
||||
v-else-if="releases.length === 0 && range !== 'all'"
|
||||
class="empty"
|
||||
>No {{ range }} releases</span>
|
||||
|
||||
@@ -36,6 +39,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Ellipsis from '../loading/ellipsis.vue';
|
||||
import SceneTile from './scene-tile.vue';
|
||||
|
||||
function range() {
|
||||
@@ -48,6 +52,7 @@ function sfw() {
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Ellipsis,
|
||||
SceneTile,
|
||||
},
|
||||
props: {
|
||||
@@ -59,10 +64,18 @@ export default {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
done: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
referer: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
stash: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['stash'],
|
||||
computed: {
|
||||
@@ -113,10 +126,29 @@ export default {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-mega) {
|
||||
.tiles {
|
||||
grid-template-columns: repeat(auto-fill, minmax(19rem, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-kilo) {
|
||||
.tiles {
|
||||
grid-template-columns: repeat(auto-fill, minmax(18.5rem, 1fr));
|
||||
grid-gap: .5rem;
|
||||
padding: .5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint) {
|
||||
.tiles {
|
||||
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-micro) {
|
||||
.tiles {
|
||||
grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -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>
|
||||
@@ -63,11 +70,12 @@ export default {
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
background: var(--shadow-weak);
|
||||
background: var(--darken-weak);
|
||||
color: var(--text-light);
|
||||
font-size: .7rem;
|
||||
font-weight: bold;
|
||||
overflow: hidden;
|
||||
text-shadow: 0 0 2px var(--text-dark);
|
||||
}
|
||||
|
||||
.scene-header {
|
||||
@@ -80,7 +88,9 @@ export default {
|
||||
|
||||
.scene-title {
|
||||
padding: .25rem .5rem;
|
||||
text-shadow: 0 0 2px var(--shadow);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.scene-actors {
|
||||
@@ -22,6 +22,8 @@
|
||||
:src="getPath(release.poster, 'thumbnail')"
|
||||
:style="{ 'background-image': getBgPath(release.poster, 'lazy') }"
|
||||
:alt="release.title"
|
||||
:width="release.poster.thumbnailWidth"
|
||||
:height="release.poster.thumbnailHeight"
|
||||
class="thumbnail"
|
||||
loading="lazy"
|
||||
>
|
||||
@@ -31,6 +33,8 @@
|
||||
:src="getPath(release.photos[0], 'thumbnail')"
|
||||
:style="{ 'background-image': getBgPath(release.photos[0], 'lazy') } "
|
||||
:alt="release.title"
|
||||
:width="release.photos[0].thumbnailWidth"
|
||||
:height="release.photos[0].thumbnailHeight"
|
||||
class="thumbnail"
|
||||
loading="lazy"
|
||||
>
|
||||
@@ -42,18 +46,25 @@
|
||||
><Icon icon="blocked" />No thumbnail available</div>
|
||||
|
||||
<Icon
|
||||
v-show="stashed"
|
||||
v-show="(!stash || stash.primary) && favorited"
|
||||
icon="heart7"
|
||||
class="stash stashed"
|
||||
@click.prevent.native="unstashScene"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-show="stashed === false"
|
||||
v-show="(!stash || stash.primary) && favorited === false"
|
||||
icon="heart8"
|
||||
class="stash unstashed"
|
||||
@click.prevent.native="stashScene"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-show="stash && !stash.primary"
|
||||
icon="cross2"
|
||||
class="stash unstash"
|
||||
@click.prevent.native="unstashScene"
|
||||
/>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
@@ -74,7 +85,9 @@
|
||||
v-tooltip.bottom="release.title"
|
||||
:title="release.title"
|
||||
class="title"
|
||||
>{{ release.title }}</h3>
|
||||
>
|
||||
{{ release.title }}<template v-if="release.movies?.[0]?.title && /^scene \d+$/i.test(release.title)"><span class="title-composed"> from </span>{{ release.movies[0].title }}</template>
|
||||
</h3>
|
||||
|
||||
<h3
|
||||
v-else-if="release.actors.length > 0"
|
||||
@@ -100,21 +113,22 @@
|
||||
:key="actor.id"
|
||||
class="actor"
|
||||
>
|
||||
<router-link
|
||||
<RouterLink
|
||||
:to="{ name: 'actor', params: { actorId: actor.id, actorSlug: actor.slug } }"
|
||||
:class="{ [actor.gender]: !!actor.gender }"
|
||||
class="actor-link"
|
||||
>{{ actor.name }}</router-link>
|
||||
>{{ actor.name }}</RouterLink>
|
||||
</li>
|
||||
</ul>
|
||||
</span>
|
||||
|
||||
<div class="labels">
|
||||
<router-link
|
||||
<RouterLink
|
||||
v-if="release.shootId && release.studio"
|
||||
:to="{ name: 'studio', params: { entitySlug: release.studio.slug } }"
|
||||
:to="`/studio/${release.studio.slug}`"
|
||||
:title="release.studio && release.studio.name"
|
||||
class="shoot nolink"
|
||||
>{{ release.shootId }}</router-link>
|
||||
>{{ release.shootId }}</RouterLink>
|
||||
|
||||
<span
|
||||
v-else-if="release.shootId"
|
||||
@@ -132,10 +146,10 @@
|
||||
:key="`tag-${tag.slug}`"
|
||||
class="tag"
|
||||
>
|
||||
<router-link
|
||||
<RouterLink
|
||||
:to="`/tag/${tag.slug}`"
|
||||
class="tag-link"
|
||||
>{{ tag.name }}</router-link>
|
||||
>{{ tag.name }}</RouterLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -148,7 +162,7 @@
|
||||
import Details from './tile-details.vue';
|
||||
|
||||
async function stashScene() {
|
||||
this.stashed = true;
|
||||
this.favorited = true;
|
||||
|
||||
try {
|
||||
await this.$store.dispatch('stashScene', {
|
||||
@@ -158,22 +172,24 @@ async function stashScene() {
|
||||
|
||||
this.$emit('stash', true);
|
||||
} catch (error) {
|
||||
this.stashed = false;
|
||||
this.favorited = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function unstashScene() {
|
||||
this.stashed = false;
|
||||
if (!this.stash || this.stash.primary) {
|
||||
this.favorited = false;
|
||||
}
|
||||
|
||||
try {
|
||||
this.$store.dispatch('unstashScene', {
|
||||
await this.$store.dispatch('unstashScene', {
|
||||
sceneId: this.release.id,
|
||||
stashId: this.$store.getters.favorites.id,
|
||||
stashId: this.stash?.id || this.$store.getters.favorites.id,
|
||||
});
|
||||
|
||||
this.$emit('stash', false);
|
||||
} catch (error) {
|
||||
this.stashed = true;
|
||||
this.favorited = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,11 +202,15 @@ export default {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
stash: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['stash'],
|
||||
data() {
|
||||
return {
|
||||
stashed: this.release.isStashed,
|
||||
favorited: this.release.isFavorited,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
@@ -214,23 +234,27 @@ export default {
|
||||
box-shadow: 0 0 3px var(--darken-weak);
|
||||
|
||||
&.new .poster::after {
|
||||
content: '★';
|
||||
content: 'new';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
bottom: 0;
|
||||
right: .5rem;
|
||||
width: 2rem;
|
||||
box-sizing: border-box;
|
||||
margin: .25rem;
|
||||
border-radius: 0 0 .5rem 0;
|
||||
color: var(--primary);
|
||||
font-size: 1rem;
|
||||
padding: .1rem 0 .05rem 0;
|
||||
border-radius: .25rem .25rem 0 0;
|
||||
background: var(--info);
|
||||
color: var(--text-light);
|
||||
font-size: .7rem;
|
||||
font-weight: bold;
|
||||
line-height: 1;
|
||||
text-shadow: 0 0 2px var(--darken-weak);
|
||||
box-shadow: 0 0 3px var(--shadow);
|
||||
}
|
||||
|
||||
&:hover .unstashed {
|
||||
&:hover .unstashed,
|
||||
&:hover .unstash {
|
||||
fill: var(--lighten);
|
||||
}
|
||||
}
|
||||
@@ -275,8 +299,8 @@ export default {
|
||||
}
|
||||
|
||||
.stash {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
@@ -289,20 +313,26 @@ export default {
|
||||
fill: var(--primary);
|
||||
filter: drop-shadow(0 0 2px var(--darken-weak));
|
||||
}
|
||||
|
||||
&:hover.unstash {
|
||||
fill: var(--text-light);
|
||||
filter: drop-shadow(0 0 2px var(--darken-weak));
|
||||
}
|
||||
}
|
||||
|
||||
.row {
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
padding: 0 .5rem;
|
||||
margin: 0 0 .1rem 0;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -314,7 +344,7 @@ export default {
|
||||
.title {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
font-size: 1rem;
|
||||
font-size: .9rem;
|
||||
line-height: 1.5;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
@@ -340,8 +370,9 @@ export default {
|
||||
}
|
||||
|
||||
.actor-link {
|
||||
color: var(--link);
|
||||
color: var(--text);
|
||||
text-decoration: none;
|
||||
font-size: .9rem;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
@@ -349,7 +380,7 @@ export default {
|
||||
}
|
||||
|
||||
.labels {
|
||||
padding: 0 .5rem 1.5rem .5rem;
|
||||
padding: 0 .5rem 1.25rem .5rem;
|
||||
max-height: .5rem;
|
||||
overflow-y: hidden;
|
||||
font-size: 0;
|
||||
@@ -380,11 +411,11 @@ export default {
|
||||
}
|
||||
|
||||
.tag-link {
|
||||
background: var(--background);
|
||||
background: var(--shadow-touch);
|
||||
color: var(--shadow);
|
||||
display: inline-block;
|
||||
padding: .25rem .5rem;
|
||||
border: solid 1px var(--shadow-hint);
|
||||
margin: 0 1px 0 0;
|
||||
font-size: .75rem;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
@@ -396,14 +427,20 @@ export default {
|
||||
}
|
||||
|
||||
.details-wide {
|
||||
margin: 0 0 .5rem 0;
|
||||
margin: 0 0 .4rem 0;
|
||||
}
|
||||
|
||||
.details-compact {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-kilo) {
|
||||
@media(max-width: $breakpoint) {
|
||||
.thumbnail {
|
||||
height: 11rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-micro) {
|
||||
.tile-body {
|
||||
flex-direction: row;
|
||||
}
|
||||
@@ -449,14 +486,15 @@ export default {
|
||||
}
|
||||
|
||||
.tile.new .poster::after {
|
||||
bottom: 0;
|
||||
top: auto;
|
||||
margin: 0 .25rem;
|
||||
top: 0;
|
||||
right: .25rem;
|
||||
bottom: auto;
|
||||
border-radius: 0 0 .25rem .25rem;
|
||||
padding: .05rem 0 .1rem 0;
|
||||
}
|
||||
|
||||
.stash {
|
||||
left: 0;
|
||||
right: auto;
|
||||
padding: .25rem .5rem .5rem .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,10 +57,7 @@
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="date"
|
||||
><Icon
|
||||
v-if="release.url"
|
||||
icon="share2"
|
||||
/>{{ formatDate(release.date, 'MMMM D, YYYY', release.datePrecision) }}</a>
|
||||
>{{ formatDate(release.date, 'MMMM D, YYYY', release.datePrecision) }}</a>
|
||||
|
||||
<a
|
||||
v-else
|
||||
@@ -91,27 +88,17 @@ export default {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
z-index: 1;
|
||||
white-space: nowrap;
|
||||
background: var(--profile);
|
||||
background: var(--info);
|
||||
font-size: 0;
|
||||
font-weight: bold;
|
||||
|
||||
.favicon {
|
||||
width: 2rem;
|
||||
box-sizing: border-box;
|
||||
padding: .5rem;
|
||||
padding: .4rem .5rem;
|
||||
}
|
||||
|
||||
/*
|
||||
&.new .date::before {
|
||||
content: '';
|
||||
width: .5rem;
|
||||
height: .5rem;
|
||||
border-radius: 50%;
|
||||
margin: 0 .25rem 0 0;
|
||||
background: var(--primary);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
.site,
|
||||
@@ -120,7 +107,7 @@ export default {
|
||||
align-items: center;
|
||||
position: relative;
|
||||
font-size: .8rem;
|
||||
padding: .5rem;
|
||||
padding: .4rem .5rem;
|
||||
color: var(--text-light);
|
||||
text-decoration: none;
|
||||
|
||||
|
||||
101
assets/components/search/bar.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<form
|
||||
class="search"
|
||||
@submit.prevent="() => search()"
|
||||
>
|
||||
<input
|
||||
v-model="query"
|
||||
:placeholder="placeholder || 'Search'"
|
||||
class="query"
|
||||
@input="() => search(true)"
|
||||
>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="search-button"
|
||||
><Icon icon="search" /></button>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
function search(typing) {
|
||||
if (!typing || this.eager) {
|
||||
this.$router.replace({
|
||||
query: { query: this.query || undefined },
|
||||
params: { ...this.$route.params, pageNumber: 1 },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function resetQuery() {
|
||||
this.query = this.$route.query.query || null;
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
eager: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
query: this.$route.query.query || null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route: resetQuery,
|
||||
},
|
||||
methods: {
|
||||
search,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.search {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.query {
|
||||
max-width: 40rem;
|
||||
min-width: 10rem;
|
||||
color: var(--text);
|
||||
background: var(--background);
|
||||
flex-grow: 1;
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
border: none;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 0 3px var(--darken-weak);
|
||||
font-size: 1rem;
|
||||
outline: none;
|
||||
|
||||
&:focus {
|
||||
box-shadow: 0 0 3px var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.search-button {
|
||||
padding: 1rem;
|
||||
background: none;
|
||||
border: none;
|
||||
|
||||
.icon {
|
||||
fill: var(--shadow);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: var(--primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -5,7 +5,7 @@
|
||||
class="summary"
|
||||
>Searching...</span>
|
||||
|
||||
<template v-if="!loader && actors.length > 0">
|
||||
<template v-if="actors.length > 0">
|
||||
<span class="summary">Found {{ actors.length }} actors for '{{ query }}'</span>
|
||||
|
||||
<div class="tiles">
|
||||
@@ -18,7 +18,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="!loader && releases.length > 0">
|
||||
<template v-if="releases.length > 0">
|
||||
<span class="summary">Found {{ releases.length }} releases for '{{ query }}'</span>
|
||||
|
||||
<Releases
|
||||
|
||||
@@ -131,7 +131,7 @@
|
||||
|
||||
<div class="sidebar-section controls noselect">
|
||||
<label
|
||||
v-if="auth && me"
|
||||
v-if="login && me"
|
||||
@click="$emit('toggleSidebar', false)"
|
||||
>
|
||||
<router-link
|
||||
@@ -142,7 +142,7 @@
|
||||
|
||||
<div class="toggles noselect">
|
||||
<label
|
||||
v-if="auth && !me"
|
||||
v-if="login && !me"
|
||||
@click="$emit('toggleSidebar', false)"
|
||||
>
|
||||
<router-link
|
||||
@@ -152,7 +152,7 @@
|
||||
</label>
|
||||
|
||||
<label
|
||||
v-if="auth && me"
|
||||
v-if="login && me"
|
||||
class="toggle"
|
||||
@click.stop="$store.dispatch('logout')"
|
||||
><Icon icon="exit2" />Log out</label>
|
||||
@@ -206,8 +206,12 @@ function theme(state) {
|
||||
return state.ui.theme;
|
||||
}
|
||||
|
||||
function auth(state) {
|
||||
return state.auth.enabled;
|
||||
function login(state) {
|
||||
return state.auth.login;
|
||||
}
|
||||
|
||||
function signup(state) {
|
||||
return state.auth.signup;
|
||||
}
|
||||
|
||||
function me(state) {
|
||||
@@ -234,7 +238,8 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
auth,
|
||||
login,
|
||||
signup,
|
||||
me,
|
||||
sfw,
|
||||
theme,
|
||||
|
||||
@@ -3,7 +3,10 @@
|
||||
title="Add stash"
|
||||
@close="$emit('close', false)"
|
||||
>
|
||||
<form @submit.prevent="addStash">
|
||||
<form
|
||||
class="dialog-body"
|
||||
@submit.prevent="addStash"
|
||||
>
|
||||
<input
|
||||
ref="name"
|
||||
v-model="name"
|
||||
112
assets/components/stashes/button.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<template>
|
||||
<span class="stash-container">
|
||||
<Tooltip class="stash-trigger">
|
||||
<Icon
|
||||
v-show="me"
|
||||
icon="menu"
|
||||
class="stash noselect"
|
||||
:class="{ stashed }"
|
||||
/>
|
||||
|
||||
<template v-slot:tooltip>
|
||||
<StashMenu
|
||||
:stashed-by="stashedBy"
|
||||
@stash="(stashId) => $emit('stash', stashId)"
|
||||
@unstash="(stashId) => $emit('unstash', stashId)"
|
||||
/>
|
||||
</template>
|
||||
</Tooltip>
|
||||
|
||||
<Icon
|
||||
v-show="me && favorited"
|
||||
icon="heart7"
|
||||
class="stash stashed noselect"
|
||||
@click.native="() => $emit('unstash', favorites.id)"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-show="me && !favorited"
|
||||
icon="heart8"
|
||||
class="stash unstashed noselect"
|
||||
@click.native="() => $emit('stash', favorites.id)"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import StashMenu from './menu.vue';
|
||||
|
||||
function favorited() {
|
||||
return this.stashedBy.some(stash => stash.primary);
|
||||
}
|
||||
|
||||
function stashed() {
|
||||
return this.stashedBy.some(stash => !stash.primary);
|
||||
}
|
||||
|
||||
function favorites() {
|
||||
return this.$store.getters.favorites;
|
||||
}
|
||||
|
||||
function me() {
|
||||
return this.$store.state.auth.user;
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
StashMenu,
|
||||
},
|
||||
props: {
|
||||
stashedBy: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
emits: ['stash', 'unstash'],
|
||||
computed: {
|
||||
me,
|
||||
favorites,
|
||||
favorited,
|
||||
stashed,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import 'breakpoints';
|
||||
|
||||
.stash-container {
|
||||
flex-shrink: 0;
|
||||
|
||||
&.light .icon {
|
||||
fill: var(--lighten);
|
||||
}
|
||||
}
|
||||
|
||||
.stash.icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
padding: 0 .5rem;
|
||||
fill: var(--shadow);
|
||||
|
||||
&.stashed {
|
||||
fill: var(--primary);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
fill: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.stash-trigger {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint) {
|
||||
.stash.icon {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
71
assets/components/stashes/menu.vue
Normal file
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<ul class="menu nolist">
|
||||
<li
|
||||
v-for="stash in stashes"
|
||||
:key="`stash-${stash.id}`"
|
||||
class="menu-item"
|
||||
>
|
||||
<label class="menu-stash noselect">
|
||||
<Checkbox
|
||||
:checked="stashedByIds.has(stash.id)"
|
||||
class="menu-check"
|
||||
@change="(checked) => checked ? $emit('stash', stash.id) : $emit('unstash', stash.id)"
|
||||
/>{{ stash.name }}
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Checkbox from '../form/checkbox.vue';
|
||||
|
||||
function stashes() {
|
||||
return this.$store.state.auth.user?.stashes || [];
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Checkbox,
|
||||
},
|
||||
props: {
|
||||
stashedBy: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
emits: ['stash', 'unstash'],
|
||||
data() {
|
||||
const stashedByIds = new Set(this.stashedBy.map(stash => stash.id));
|
||||
|
||||
return {
|
||||
stashedByIds,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
stashes,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.menu-item {
|
||||
color: var(--text);
|
||||
display: block;
|
||||
}
|
||||
|
||||
.menu-stash {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .5rem 1rem .5rem .5rem;
|
||||
|
||||
&:hover {
|
||||
color: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.menu-check {
|
||||
display: inline-block;
|
||||
margin: 0 .75rem 0 0;
|
||||
}
|
||||
</style>
|
||||
41
assets/components/stashes/remove.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<Dialog
|
||||
title="Remove stash"
|
||||
@close="$emit('close', false)"
|
||||
>
|
||||
<form
|
||||
class="dialog-body"
|
||||
@submit.prevent="removeStash"
|
||||
>
|
||||
Are you sure you want to remove stash "{{ stash.name }}"?
|
||||
|
||||
<div class="dialog-actions right">
|
||||
<button
|
||||
type="submit"
|
||||
class="button button-primary"
|
||||
>Remove</button>
|
||||
</div>
|
||||
</form>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
async function removeStash() {
|
||||
await this.$store.dispatch('removeStash', this.stash.id);
|
||||
|
||||
this.$emit('close', true);
|
||||
}
|
||||
|
||||
export default {
|
||||
props: {
|
||||
stash: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['close'],
|
||||
methods: {
|
||||
removeStash,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -4,9 +4,18 @@
|
||||
class="stash content"
|
||||
>
|
||||
<div class="stash-header">
|
||||
<h2 class="stash-name">{{ stash.name }}</h2>
|
||||
<h2
|
||||
:title="stash.name"
|
||||
class="stash-name"
|
||||
>{{ stash.name }}</h2>
|
||||
|
||||
<span class="header-section">
|
||||
<RouterLink
|
||||
v-if="stash.user"
|
||||
:to="{ name: 'user', params: { username: stash.user.username } }"
|
||||
class="header-item stash-username nolink"
|
||||
><Icon icon="user3" /><span class="username-name">{{ stash.user.username }}</span></RouterLink>
|
||||
|
||||
<label
|
||||
v-if="isMine"
|
||||
v-tooltip="'Public'"
|
||||
@@ -30,31 +39,66 @@
|
||||
/>
|
||||
</label>
|
||||
|
||||
<router-link
|
||||
v-if="stash.user"
|
||||
:to="{ name: 'user', params: { username: stash.user.username } }"
|
||||
class="header-item stash-username nolink"
|
||||
><Icon icon="user3" />{{ stash.user.username }}</router-link>
|
||||
<Icon
|
||||
v-if="isMine && !stash.primary"
|
||||
icon="bin"
|
||||
class="stash-remove"
|
||||
@click.native="showRemoveStash = true"
|
||||
/>
|
||||
|
||||
<RemoveStash
|
||||
v-if="showRemoveStash"
|
||||
:stash="stash"
|
||||
@close="removeStash"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="content-inner">
|
||||
<FilterBar :ranges="['scenes', 'actors', 'movies']" />
|
||||
|
||||
<Releases
|
||||
v-if="stash.scenes?.length > 0"
|
||||
v-if="$route.params.range === 'scenes' && stash.scenes?.length > 0"
|
||||
:releases="stash.scenes.map(item => item.scene)"
|
||||
:stash="stash"
|
||||
class="stash-section stash-scenes"
|
||||
@stash="fetchStash"
|
||||
/>
|
||||
|
||||
<ul
|
||||
v-if="stash.actors?.length > 0"
|
||||
v-if="$route.params.range === 'actors'"
|
||||
class="stash-section stash-actors nolist"
|
||||
>
|
||||
<li
|
||||
v-for="item in stash.actors"
|
||||
:key="item.id"
|
||||
><Actor :actor="item.actor" /></li>
|
||||
><Actor
|
||||
:actor="item.actor"
|
||||
:stash="stash"
|
||||
@stash="fetchStash"
|
||||
/></li>
|
||||
</ul>
|
||||
|
||||
<div
|
||||
v-if="$route.params.range === 'movies'"
|
||||
class="stash-movies"
|
||||
>
|
||||
<Movie
|
||||
v-for="item in stash.movies"
|
||||
:key="`movie-${item.id}`"
|
||||
:movie="item.movie"
|
||||
:stash="stash"
|
||||
@stash="fetchStash"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Pagination
|
||||
:items-total="totalCount"
|
||||
:items-per-page="limit"
|
||||
class="pagination-bottom"
|
||||
/>
|
||||
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -62,11 +106,35 @@
|
||||
<script>
|
||||
import Actor from '../actors/tile.vue';
|
||||
import Releases from '../releases/releases.vue';
|
||||
import Movie from '../releases/movie-tile.vue';
|
||||
import RemoveStash from './remove.vue';
|
||||
import Toggle from '../form/toggle.vue';
|
||||
import FilterBar from '../filters/filter-bar.vue';
|
||||
import Pagination from '../pagination/pagination.vue';
|
||||
|
||||
async function fetchStash() {
|
||||
this.stash = await this.$store.dispatch('fetchStash', this.$route.params.stashId);
|
||||
this.stash = await this.$store.dispatch('fetchStash', {
|
||||
stashId: this.$route.params.stashId,
|
||||
section: this.$route.params.range,
|
||||
pageNumber: this.$route.params.pageNumber || 1,
|
||||
limit: this.limit,
|
||||
});
|
||||
|
||||
this.isMine = this.stash.user?.id === this.$store.state.auth.user?.id;
|
||||
|
||||
if (this.$route.params.range === 'scenes') {
|
||||
this.totalCount = this.stash.sceneTotal;
|
||||
}
|
||||
|
||||
if (this.$route.params.range === 'actors') {
|
||||
this.totalCount = this.stash.actorTotal;
|
||||
}
|
||||
|
||||
if (this.$route.params.range === 'movies') {
|
||||
this.totalCount = this.stash.movieTotal;
|
||||
}
|
||||
|
||||
this.pageTitle = this.stash.name;
|
||||
}
|
||||
|
||||
async function publishStash(isPublic) {
|
||||
@@ -78,31 +146,58 @@ async function publishStash(isPublic) {
|
||||
this.fetchStash();
|
||||
}
|
||||
|
||||
async function removeStash(removed) {
|
||||
this.showRemoveStash = false;
|
||||
|
||||
if (removed && this.stash.user) {
|
||||
this.$router.replace({ name: 'user', params: { username: this.stash.user.username } });
|
||||
return;
|
||||
}
|
||||
|
||||
if (removed) {
|
||||
this.$router.replace({ name: 'home' });
|
||||
}
|
||||
}
|
||||
|
||||
async function mounted() {
|
||||
this.fetchStash();
|
||||
await this.fetchStash();
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Actor,
|
||||
Movie,
|
||||
Releases,
|
||||
RemoveStash,
|
||||
Pagination,
|
||||
FilterBar,
|
||||
Toggle,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
stash: null,
|
||||
limit: Number(this.$route.query.limit) || 20,
|
||||
pageTitle: null,
|
||||
showRemoveStash: false,
|
||||
isMine: false,
|
||||
totalCount: 0,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
$route: fetchStash,
|
||||
},
|
||||
mounted,
|
||||
methods: {
|
||||
fetchStash,
|
||||
publishStash,
|
||||
removeStash,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import 'breakpoints';
|
||||
|
||||
.stash-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
@@ -123,12 +218,29 @@ export default {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-item:not(:last-child) {
|
||||
margin: 0 1rem 0 0;
|
||||
.stash-name,
|
||||
.stash-username {
|
||||
box-sizing: border-box;
|
||||
padding: .5rem 1rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stash-username {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin: 0 .5rem 0 0;
|
||||
}
|
||||
|
||||
.stash-name {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.stash-public {
|
||||
cursor: pointer;
|
||||
margin: 0 .5rem 0 0;
|
||||
|
||||
.icon {
|
||||
margin: 0 .75rem 0 0;
|
||||
@@ -136,32 +248,53 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.stash-name,
|
||||
.stash-username {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: .5rem 1rem;
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
.stash-remove.icon {
|
||||
height: 100%;
|
||||
padding: 0 1rem;
|
||||
fill: var(--lighten-strong);
|
||||
|
||||
.stash-section:not(:last-child) {
|
||||
border-bottom: solid 1px var(--shadow-hint);
|
||||
&:hover {
|
||||
fill: var(--text-light);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.stash-actors {
|
||||
display: grid;
|
||||
grid-gap: .5rem;
|
||||
grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr));
|
||||
grid-template-rows: min-content;
|
||||
flex-grow: 1;
|
||||
padding: 1rem;
|
||||
border-top: solid 1px var(--shadow-hint);
|
||||
}
|
||||
|
||||
.stash-scenes {
|
||||
flex-grow: 0;
|
||||
.stash-movies {
|
||||
display: grid;
|
||||
flex-grow: 1;
|
||||
grid-template-columns: repeat(auto-fill, minmax(25rem, 1fr));
|
||||
grid-template-rows: min-content;
|
||||
grid-gap: 1rem;
|
||||
padding: 1rem;
|
||||
border-top: solid 1px var(--shadow-hint);
|
||||
}
|
||||
|
||||
.tiles {
|
||||
grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
|
||||
.stash-scenes .tiles {
|
||||
grid-template-columns: repeat(auto-fill, minmax(22rem, 1fr));
|
||||
grid-template-rows: min-content;
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-small) {
|
||||
.stash-name {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.username-name {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.stash-username {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
<template>
|
||||
<div class="photos">
|
||||
<Campaign
|
||||
:tag="tag"
|
||||
:min-height="240"
|
||||
:max-ratio="1.5"
|
||||
class="photo-link photo"
|
||||
@campaign="campaign => $emit('campaign', campaign)"
|
||||
/>
|
||||
|
||||
<a
|
||||
v-for="photo in photos"
|
||||
:key="`photo-${photo.id}`"
|
||||
@@ -37,6 +45,7 @@
|
||||
|
||||
<script>
|
||||
import Logo from '../album/logo.vue';
|
||||
import Campaign from '../campaigns/campaign.vue';
|
||||
|
||||
function photos() {
|
||||
if (this.tag.poster && this.$store.state.ui.sfw) {
|
||||
@@ -57,6 +66,7 @@ function photos() {
|
||||
export default {
|
||||
components: {
|
||||
Logo,
|
||||
Campaign,
|
||||
},
|
||||
props: {
|
||||
tag: {
|
||||
@@ -64,7 +74,7 @@ export default {
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
emits: ['load'],
|
||||
emits: ['load', 'campaign'],
|
||||
computed: {
|
||||
photos,
|
||||
},
|
||||
@@ -150,4 +160,9 @@ export default {
|
||||
transform: translateY(100%);
|
||||
transition: transform .25s ease;
|
||||
}
|
||||
|
||||
::v-deep(.campaign) .campaign-banner {
|
||||
max-height: 15rem;
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -10,7 +10,10 @@
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="content-inner">
|
||||
<div
|
||||
class="content-inner"
|
||||
@scroll="events.emit('scroll', $event)"
|
||||
>
|
||||
<div
|
||||
v-if="description"
|
||||
class="description header-description"
|
||||
@@ -26,6 +29,7 @@
|
||||
:tag="tag"
|
||||
:class="{ expanded }"
|
||||
@load="scroll.loaded"
|
||||
@campaign="campaign => showBannerCampaign = !campaign"
|
||||
/>
|
||||
</Scroll>
|
||||
|
||||
@@ -40,16 +44,30 @@
|
||||
:items="[tag.poster, ...tag.photos]"
|
||||
:title="tag.name"
|
||||
:local="true"
|
||||
:tag="tag"
|
||||
class="portrait"
|
||||
@close="$router.replace({ hash: undefined })"
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="showBannerCampaign"
|
||||
class="campaign-container"
|
||||
>
|
||||
<Campaign
|
||||
:tag="tag"
|
||||
:min-ratio="3"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<FilterBar
|
||||
ref="filter"
|
||||
:fetch-releases="fetchReleases"
|
||||
/>
|
||||
|
||||
<Releases :releases="releases" />
|
||||
<Releases
|
||||
:releases="releases"
|
||||
:done="done"
|
||||
/>
|
||||
|
||||
<Pagination
|
||||
:items-total="totalCount"
|
||||
@@ -73,10 +91,13 @@ import Album from '../album/album.vue';
|
||||
import Releases from '../releases/releases.vue';
|
||||
import Pagination from '../pagination/pagination.vue';
|
||||
import Scroll from '../scroll/scroll.vue';
|
||||
import Campaign from '../campaigns/campaign.vue';
|
||||
|
||||
const converter = new Converter();
|
||||
|
||||
async function fetchReleases(scroll = true) {
|
||||
this.done = false;
|
||||
|
||||
const { tag, releases, totalCount } = await this.$store.dispatch('fetchTagBySlug', {
|
||||
tagSlug: this.$route.params.tagSlug,
|
||||
pageNumber: Number(this.$route.params.pageNumber),
|
||||
@@ -91,6 +112,12 @@ async function fetchReleases(scroll = true) {
|
||||
this.hasMedia = this.tag.poster || this.tag.photos.length > 0;
|
||||
this.description = this.tag.description && converter.makeHtml(escapeHtml(this.tag.description));
|
||||
|
||||
this.done = true;
|
||||
|
||||
if (this.hasMedia) {
|
||||
this.showBannerCampaign = true;
|
||||
}
|
||||
|
||||
if (scroll && this.$refs.filter) {
|
||||
this.$refs.filter.$el.scrollIntoView();
|
||||
}
|
||||
@@ -119,17 +146,20 @@ export default {
|
||||
Photos,
|
||||
Pagination,
|
||||
Scroll,
|
||||
Campaign,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tag: null,
|
||||
description: null,
|
||||
releases: null,
|
||||
done: false,
|
||||
totalCount: 0,
|
||||
limit: 20,
|
||||
pageTitle: null,
|
||||
hasMedia: false,
|
||||
expanded: false,
|
||||
showBannerCampaign: false, // only show if photo campaign is not available
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -169,12 +199,14 @@ export default {
|
||||
@import 'theme';
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
background: var(--profile);
|
||||
color: var(--text-light);
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.title {
|
||||
display: inline-block;
|
||||
padding: .5rem 1rem;
|
||||
margin: 0;
|
||||
flex-shrink: 0;
|
||||
@@ -197,4 +229,11 @@ export default {
|
||||
.scroll {
|
||||
background: var(--background-dim);
|
||||
}
|
||||
|
||||
.campaign-container {
|
||||
max-height: 90px;
|
||||
padding: .5rem 1rem 0 1rem;
|
||||
background: var(--background-dim);
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,22 +1,29 @@
|
||||
<template>
|
||||
<div class="tags">
|
||||
<div
|
||||
v-for="(tags, category) in categories"
|
||||
:key="category"
|
||||
class="category"
|
||||
>
|
||||
<h3 class="heading">{{ category }}</h3>
|
||||
<div class="content-inner">
|
||||
<SearchBar
|
||||
:placeholder="'Search tags'"
|
||||
class="search"
|
||||
/>
|
||||
|
||||
<div
|
||||
:key="sfw"
|
||||
class="tiles"
|
||||
v-for="(tags, category) in categories"
|
||||
:key="category"
|
||||
class="category"
|
||||
>
|
||||
<Tag
|
||||
v-for="tag in tags"
|
||||
:key="`tag-${tag.id}`"
|
||||
:tag="tag"
|
||||
:lazy="true"
|
||||
/>
|
||||
<h3 class="heading">{{ category }}</h3>
|
||||
|
||||
<div
|
||||
:key="sfw"
|
||||
class="tiles"
|
||||
>
|
||||
<Tag
|
||||
v-for="tag in tags"
|
||||
:key="`tag-${tag.id}`"
|
||||
:tag="tag"
|
||||
:lazy="true"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -26,121 +33,133 @@
|
||||
|
||||
<script>
|
||||
import Tag from './tile.vue';
|
||||
import SearchBar from '../search/bar.vue';
|
||||
|
||||
const tagSlugsByCategory = {
|
||||
popular: [
|
||||
'anal',
|
||||
'lesbian',
|
||||
'interracial',
|
||||
'mff',
|
||||
'mfm',
|
||||
'teen',
|
||||
'milf',
|
||||
'blowjob',
|
||||
'gay',
|
||||
'transsexual',
|
||||
'dp',
|
||||
'gangbang',
|
||||
'facial',
|
||||
'creampie',
|
||||
'squirting',
|
||||
],
|
||||
appearance: [
|
||||
'asian',
|
||||
'black',
|
||||
'latina',
|
||||
'white',
|
||||
'natural-boobs',
|
||||
'enhanced-boobs',
|
||||
'blonde',
|
||||
'brunette',
|
||||
'redhead',
|
||||
'tattoos',
|
||||
'piercings',
|
||||
],
|
||||
sexuality: [
|
||||
'gay',
|
||||
'bisexual',
|
||||
'transsexual',
|
||||
],
|
||||
oral: [
|
||||
'blowjob',
|
||||
'pussy-eating',
|
||||
'ass-eating',
|
||||
'deepthroat',
|
||||
'facefucking',
|
||||
'69',
|
||||
'atm',
|
||||
],
|
||||
manual: [
|
||||
'handjob',
|
||||
'fingering',
|
||||
'anal-fingering',
|
||||
'titty-fucking',
|
||||
'fisting',
|
||||
'anal-fisting',
|
||||
],
|
||||
group: [
|
||||
'mfm',
|
||||
'mff',
|
||||
'orgy',
|
||||
'gangbang',
|
||||
'blowbang',
|
||||
],
|
||||
cumshot: [
|
||||
'facial',
|
||||
'creampie',
|
||||
'cum-in-mouth',
|
||||
'cum-on-boobs',
|
||||
'cum-on-butt',
|
||||
'cum-on-pussy',
|
||||
'anal-creampie',
|
||||
'oral-creampie',
|
||||
'bukkake',
|
||||
'fake-cum',
|
||||
],
|
||||
roleplay: [
|
||||
'family',
|
||||
'parody',
|
||||
'schoolgirl',
|
||||
'nurse',
|
||||
'maid',
|
||||
'nun',
|
||||
],
|
||||
extreme: [
|
||||
'dp',
|
||||
'airtight',
|
||||
'dap',
|
||||
'dvp',
|
||||
'triple-penetration',
|
||||
'tap',
|
||||
'tvp',
|
||||
],
|
||||
fetish: [
|
||||
'bdsm',
|
||||
'femdom',
|
||||
'bondage',
|
||||
'free-use',
|
||||
'latex',
|
||||
'blindfold',
|
||||
],
|
||||
toys: [
|
||||
'toys',
|
||||
'toy-anal',
|
||||
'toy-dp',
|
||||
'double-dildo',
|
||||
'double-dildo-blowjob',
|
||||
'double-dildo-kiss',
|
||||
'double-dildo-anal',
|
||||
'double-dildo-dp',
|
||||
],
|
||||
misc: [
|
||||
'gaping',
|
||||
'squirting',
|
||||
'oil',
|
||||
'vr',
|
||||
'bts',
|
||||
],
|
||||
};
|
||||
|
||||
function sfw() {
|
||||
return this.$store.state.ui.sfw;
|
||||
}
|
||||
|
||||
async function mounted() {
|
||||
const tagSlugsByCategory = {
|
||||
popular: [
|
||||
'anal',
|
||||
'lesbian',
|
||||
'interracial',
|
||||
'mff',
|
||||
'mfm',
|
||||
'teen',
|
||||
'milf',
|
||||
'blowjob',
|
||||
'dp',
|
||||
'gangbang',
|
||||
'facial',
|
||||
'creampie',
|
||||
'squirting',
|
||||
],
|
||||
appearance: [
|
||||
'asian',
|
||||
'black',
|
||||
'latina',
|
||||
'white',
|
||||
'natural-boobs',
|
||||
'enhanced-boobs',
|
||||
'blonde',
|
||||
'brunette',
|
||||
'redhead',
|
||||
'tattoos',
|
||||
'piercings',
|
||||
],
|
||||
oral: [
|
||||
'blowjob',
|
||||
'pussy-eating',
|
||||
'ass-eating',
|
||||
'69',
|
||||
'double-blowjob',
|
||||
'deepthroat',
|
||||
'facefucking',
|
||||
'atm',
|
||||
],
|
||||
manual: [
|
||||
'handjob',
|
||||
'fingering',
|
||||
'anal-fingering',
|
||||
'titty-fucking',
|
||||
'fisting',
|
||||
'anal-fisting',
|
||||
'fisting-dp',
|
||||
],
|
||||
group: [
|
||||
'mfm',
|
||||
'mff',
|
||||
'orgy',
|
||||
'gangbang',
|
||||
'blowbang',
|
||||
],
|
||||
cumshot: [
|
||||
'facial',
|
||||
'creampie',
|
||||
'cum-in-mouth',
|
||||
'cum-on-boobs',
|
||||
'cum-on-butt',
|
||||
'cum-on-pussy',
|
||||
'anal-creampie',
|
||||
'oral-creampie',
|
||||
'bukkake',
|
||||
'fake-cum',
|
||||
],
|
||||
toys: [
|
||||
'toys',
|
||||
'toy-anal',
|
||||
'toy-dp',
|
||||
'double-dildo',
|
||||
'double-dildo-blowjob',
|
||||
'double-dildo-kiss',
|
||||
'double-dildo-anal',
|
||||
'double-dildo-dp',
|
||||
],
|
||||
roleplay: [
|
||||
'family',
|
||||
'parody',
|
||||
'schoolgirl',
|
||||
'nurse',
|
||||
'maid',
|
||||
'nun',
|
||||
],
|
||||
fetish: [
|
||||
'bdsm',
|
||||
'femdom',
|
||||
'bondage',
|
||||
'latex',
|
||||
'blindfold',
|
||||
],
|
||||
extreme: [
|
||||
'dp',
|
||||
'airtight',
|
||||
'dap',
|
||||
'dvp',
|
||||
'da-tp',
|
||||
'dv-tp',
|
||||
'tap',
|
||||
],
|
||||
misc: [
|
||||
'gaping',
|
||||
'squirting',
|
||||
'oil',
|
||||
'vr',
|
||||
'bts',
|
||||
],
|
||||
};
|
||||
async function fetchTags() {
|
||||
if (this.$route.query.query) {
|
||||
await this.searchTags();
|
||||
return;
|
||||
}
|
||||
|
||||
const tags = await this.$store.dispatch('fetchTags', {
|
||||
slugs: Object.values(tagSlugsByCategory).flat(),
|
||||
@@ -148,17 +167,42 @@ async function mounted() {
|
||||
|
||||
const tagsBySlug = tags.reduce((acc, tag) => ({ ...acc, [tag.slug]: tag }), {});
|
||||
|
||||
this.categories = Object.entries(tagSlugsByCategory).reduce((acc, [category, tagSlugs]) => ({
|
||||
...acc,
|
||||
[category]: tagSlugs.map(tagSlug => tagsBySlug[tagSlug]),
|
||||
}), {});
|
||||
this.categories = Object.entries(tagSlugsByCategory).reduce((acc, [category, tagSlugs]) => {
|
||||
const categoryTags = tagSlugs.map((tagSlug) => tagsBySlug[tagSlug]).filter(Boolean);
|
||||
|
||||
if (categoryTags.length === 0) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
return {
|
||||
...acc,
|
||||
[category]: categoryTags,
|
||||
};
|
||||
}, {});
|
||||
}
|
||||
|
||||
async function searchTags() {
|
||||
const tags = await this.$store.dispatch('searchTags', {
|
||||
minLength: 1,
|
||||
query: this.$route.query.query,
|
||||
limit: 20,
|
||||
});
|
||||
|
||||
this.categories = {
|
||||
results: tags,
|
||||
};
|
||||
}
|
||||
|
||||
async function mounted() {
|
||||
this.pageTitle = 'Tags';
|
||||
|
||||
await this.fetchTags();
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Tag,
|
||||
SearchBar,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -169,14 +213,28 @@ export default {
|
||||
computed: {
|
||||
sfw,
|
||||
},
|
||||
watch: {
|
||||
$route: fetchTags,
|
||||
'$store.state.ui.tagFilter': fetchTags,
|
||||
},
|
||||
mounted,
|
||||
methods: {
|
||||
fetchTags,
|
||||
searchTags,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import 'theme';
|
||||
@import 'breakpoints';
|
||||
|
||||
.tags {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.content-inner {
|
||||
padding: 1rem 1rem 0 1rem;
|
||||
}
|
||||
|
||||
@@ -184,35 +242,97 @@ export default {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(20rem, .33fr));
|
||||
grid-gap: 1rem;
|
||||
margin: 0 0 1.5rem 0;
|
||||
}
|
||||
|
||||
.heading {
|
||||
text-transform: capitalize;
|
||||
padding: 0 0 0 .5rem;
|
||||
margin: 2rem 0 1rem 0;
|
||||
margin: 1.25rem 0 1rem 0;
|
||||
}
|
||||
|
||||
.category:first-child .heading {
|
||||
margin: .5rem 0 1rem 0;
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint3) {
|
||||
.search {
|
||||
margin: 0 0 .25rem 0;
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-mega) {
|
||||
.tiles {
|
||||
grid-template-columns: repeat(auto-fill, minmax(18rem, .5fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(17.75rem, .5fr));
|
||||
}
|
||||
|
||||
::v-deep(.poster),
|
||||
::v-deep(.blank) {
|
||||
height: 12rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-kilo) {
|
||||
.tiles {
|
||||
grid-template-columns: repeat(auto-fill, minmax(18rem, 1fr));
|
||||
grid-gap: .5rem;
|
||||
}
|
||||
|
||||
::v-deep(.poster),
|
||||
::v-deep(.blank) {
|
||||
height: 14rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint) {
|
||||
.tiles {
|
||||
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(14.5rem, 1fr));
|
||||
grid-gap: .5rem;
|
||||
}
|
||||
|
||||
::v-deep(.poster),
|
||||
::v-deep(.blank) {
|
||||
height: 11rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint0) {
|
||||
@media(max-width: $breakpoint-small) {
|
||||
::v-deep(.poster),
|
||||
::v-deep(.blank) {
|
||||
height: 10rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-micro) {
|
||||
.tiles {
|
||||
grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr));
|
||||
grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
|
||||
}
|
||||
|
||||
::v-deep(.poster),
|
||||
::v-deep(.blank) {
|
||||
height: 7.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-mini) {
|
||||
.tiles {
|
||||
grid-template-columns: repeat(auto-fill, minmax(11.5rem, 1fr));
|
||||
}
|
||||
|
||||
::v-deep(.poster),
|
||||
::v-deep(.blank) {
|
||||
height: 12rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-nano) {
|
||||
::v-deep(.poster),
|
||||
::v-deep(.blank) {
|
||||
height: 10rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-pico) {
|
||||
::v-deep(.poster),
|
||||
::v-deep(.blank) {
|
||||
height: 8rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,55 +1,62 @@
|
||||
<template>
|
||||
<div
|
||||
v-if="tag.poster"
|
||||
class="tile"
|
||||
>
|
||||
<router-link
|
||||
:to="`/tag/${tag.slug}`"
|
||||
:title="tag.name"
|
||||
:class="{ blank: !tag.poster }"
|
||||
class="poster-link"
|
||||
>
|
||||
<img
|
||||
v-if="!lazy && !sfw"
|
||||
:src="`/img/${tag.poster.thumbnail}`"
|
||||
:style="{ 'background-image': `url(/img/${tag.poster.lazy})` }"
|
||||
:title="comment"
|
||||
:alt="tag.name"
|
||||
class="poster"
|
||||
>
|
||||
<template v-if="tag.poster">
|
||||
<img
|
||||
v-if="!lazy && !sfw"
|
||||
:src="`/img/${tag.poster.thumbnail}`"
|
||||
:style="{ 'background-image': `url(/img/${tag.poster.lazy})` }"
|
||||
:title="comment"
|
||||
:alt="tag.name"
|
||||
class="poster"
|
||||
>
|
||||
|
||||
<img
|
||||
v-if="!lazy && sfw"
|
||||
:src="`/img/${tag.poster.sfw.thumbnail}`"
|
||||
:style="{ 'background-image': `url(/img/${tag.poster.sfw.lazy})` }"
|
||||
:title="tag.poster.sfw.comment"
|
||||
:alt="tag.name"
|
||||
class="poster"
|
||||
>
|
||||
<img
|
||||
v-if="!lazy && sfw"
|
||||
:src="`/img/${tag.poster.sfw.thumbnail}`"
|
||||
:style="{ 'background-image': `url(/img/${tag.poster.sfw.lazy})` }"
|
||||
:title="tag.poster.sfw.comment"
|
||||
:alt="tag.name"
|
||||
class="poster"
|
||||
>
|
||||
|
||||
<img
|
||||
v-if="lazy && !sfw"
|
||||
:src="`/img/${tag.poster.thumbnail}`"
|
||||
:style="{ 'background-image': `url(/img/${tag.poster.lazy})` }"
|
||||
:title="comment"
|
||||
:alt="tag.name"
|
||||
loading="lazy"
|
||||
class="poster"
|
||||
>
|
||||
<img
|
||||
v-if="lazy && !sfw"
|
||||
:src="`/img/${tag.poster.thumbnail}`"
|
||||
:style="{ 'background-image': `url(/img/${tag.poster.lazy})` }"
|
||||
:title="comment"
|
||||
:alt="tag.name"
|
||||
loading="lazy"
|
||||
class="poster"
|
||||
>
|
||||
|
||||
<img
|
||||
v-if="lazy && sfw"
|
||||
:src="`/img/${tag.poster.sfw.thumbnail}`"
|
||||
:style="{ 'background-image': `url(/img/${tag.poster.sfw.lazy})` }"
|
||||
:title="tag.poster.sfw.comment"
|
||||
:alt="tag.name"
|
||||
loading="lazy"
|
||||
class="poster"
|
||||
>
|
||||
<img
|
||||
v-if="lazy && sfw"
|
||||
:src="`/img/${tag.poster.sfw.thumbnail}`"
|
||||
:style="{ 'background-image': `url(/img/${tag.poster.sfw.lazy})` }"
|
||||
:title="tag.poster.sfw.comment"
|
||||
:alt="tag.name"
|
||||
loading="lazy"
|
||||
class="poster"
|
||||
>
|
||||
|
||||
<Logo
|
||||
v-if="!sfw"
|
||||
:photo="tag.poster"
|
||||
favicon
|
||||
<Logo
|
||||
v-if="!sfw"
|
||||
:photo="tag.poster"
|
||||
favicon
|
||||
/>
|
||||
</template>
|
||||
|
||||
<Icon
|
||||
v-else
|
||||
icon="price-tag2"
|
||||
/>
|
||||
</router-link>
|
||||
|
||||
@@ -59,11 +66,6 @@
|
||||
:title="tag.name"
|
||||
>{{ tag.name }}</router-link>
|
||||
</div>
|
||||
|
||||
<span
|
||||
v-else
|
||||
class="title"
|
||||
>{{ tag.name }}</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -100,13 +102,16 @@ export default {
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.tile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
font-size: 0;
|
||||
|
||||
&:hover {
|
||||
.poster {
|
||||
.poster,
|
||||
.blank {
|
||||
box-shadow: 0 0 2px var(--darken);
|
||||
}
|
||||
|
||||
@@ -119,15 +124,30 @@ export default {
|
||||
.poster {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
height: 13.5rem;
|
||||
object-fit: cover;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
box-shadow: 0 0 3px var(--darken-weak);
|
||||
}
|
||||
|
||||
.poster,
|
||||
.blank {
|
||||
height: 13.5rem;
|
||||
}
|
||||
|
||||
.poster-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
flex-grow: 1;
|
||||
background: var(--shadow-hint);
|
||||
|
||||
.icon {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
fill: var(--shadow-weak);
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
|
||||
@@ -16,16 +16,19 @@
|
||||
:style="{ transform: `translate3d(${tooltipX}px, ${tooltipY}px, 0)` }"
|
||||
@click.stop
|
||||
>
|
||||
<div class="tooltip-inner">
|
||||
<div
|
||||
class="tooltip-inner"
|
||||
:style="{ 'max-height': `calc(100vh - ${tooltipY}px - 1rem)` }"
|
||||
>
|
||||
<div class="tooltip">
|
||||
<slot name="tooltip" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tooltip-arrow"
|
||||
:style="{ transform: `translate3d(${arrowOffset}px, 0, 0)` }"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="tooltip-arrow"
|
||||
:style="{ transform: `translate3d(${arrowOffset}px, 0, 0)` }"
|
||||
/>
|
||||
</div>
|
||||
</teleport>
|
||||
</div>
|
||||
@@ -155,12 +158,14 @@ export default {
|
||||
|
||||
.tooltip-inner {
|
||||
position: relative;
|
||||
box-shadow: 0 0 3px var(--darken-weak);
|
||||
overflow-y: auto;
|
||||
background: var(--background);
|
||||
box-shadow: 0 0 .5rem var(--darken);
|
||||
}
|
||||
|
||||
.tooltip {
|
||||
position: relative;
|
||||
background: var(--background-light);
|
||||
background: var(--background);
|
||||
}
|
||||
|
||||
.tooltip-arrow {
|
||||
@@ -172,7 +177,7 @@ export default {
|
||||
left: calc(50% - .5rem);
|
||||
border-left: .5rem solid transparent;
|
||||
border-right: .5rem solid transparent;
|
||||
border-bottom: .5rem solid var(--background-light);
|
||||
border-bottom: .5rem solid var(--background);
|
||||
margin: 0 auto;
|
||||
filter: drop-shadow(0 0 3px var(--darken-weak));
|
||||
}
|
||||
|
||||
252
assets/components/users/alert.vue
Normal file
@@ -0,0 +1,252 @@
|
||||
<template>
|
||||
<div class="alert">
|
||||
<div class="alert-section alert-header">
|
||||
<div class="alert-targets">
|
||||
<Icon
|
||||
v-if="alert.notify"
|
||||
icon="bell2"
|
||||
class="alert-action"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-if="alert.email"
|
||||
icon="envelop"
|
||||
class="alert-action"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-if="alert.stashes?.length > 0"
|
||||
v-tooltip="alert.stashes.map(stash => stash.name).join()"
|
||||
icon="heart7"
|
||||
class="alert-action"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span class="header-actions noselect">
|
||||
<Icon
|
||||
v-if="isMe"
|
||||
icon="bin"
|
||||
class="alert-remove"
|
||||
@click.native="showRemoveAlert = true"
|
||||
/>
|
||||
|
||||
<RemoveAlert
|
||||
v-if="showRemoveAlert"
|
||||
:alert="alert"
|
||||
@close="removeAlert"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="alert-triggers">
|
||||
<div
|
||||
v-if="alert.actors?.length > 0"
|
||||
class="alert-section alert-trigger"
|
||||
>
|
||||
<h4
|
||||
v-if="alert.actors.length > 1"
|
||||
class="alert-heading"
|
||||
>Actors</h4>
|
||||
|
||||
<h4
|
||||
v-else
|
||||
class="alert-heading"
|
||||
>Actor</h4>
|
||||
|
||||
<ul class="alert-actors nolist">
|
||||
<li
|
||||
v-for="actor in alert.actors"
|
||||
:key="`actor-${actor.id}`"
|
||||
class="alert-actor"
|
||||
>
|
||||
<ActorPreview
|
||||
:actor="actor"
|
||||
:alert="alert"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="alert.tags?.length > 0"
|
||||
class="alert-section alert-trigger"
|
||||
>
|
||||
<h4
|
||||
v-if="alert.tags.length > 1"
|
||||
class="alert-heading"
|
||||
>Tags</h4>
|
||||
|
||||
<h4
|
||||
v-else
|
||||
class="alert-heading"
|
||||
>Tag</h4>
|
||||
|
||||
<ul class="alert-tags nolist">
|
||||
<li
|
||||
v-for="tag in alert.tags"
|
||||
:key="`tag-${tag.id}`"
|
||||
><router-link
|
||||
:to="`/tag/${tag.slug}`"
|
||||
class="tag nolink"
|
||||
>{{ tag.name }}</router-link></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="alert.entity"
|
||||
class="alert-section alert-trigger"
|
||||
>
|
||||
<h4 class="alert-heading">Channel</h4>
|
||||
|
||||
<Entity
|
||||
v-if="alert.entity"
|
||||
:entity="alert.entity"
|
||||
class="entity"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ActorPreview from '../actors/preview.vue';
|
||||
import RemoveAlert from '../alerts/remove.vue';
|
||||
import Entity from '../entities/tile.vue';
|
||||
|
||||
async function removeAlert(removed) {
|
||||
this.showRemoveAlert = false;
|
||||
|
||||
if (removed) {
|
||||
this.$emit('remove');
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ActorPreview,
|
||||
Entity,
|
||||
RemoveAlert,
|
||||
},
|
||||
props: {
|
||||
alert: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
isMe: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['remove'],
|
||||
data() {
|
||||
return {
|
||||
showRemoveAlert: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
removeAlert,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.alert {
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
background: var(--background);
|
||||
box-shadow: 0 0 3px var(--darken-weak);
|
||||
}
|
||||
|
||||
.alert-header {
|
||||
border-bottom: solid 1px var(--shadow-hint);
|
||||
}
|
||||
|
||||
.alert-section {
|
||||
display: inline-block;
|
||||
padding: .5rem;
|
||||
}
|
||||
|
||||
.alert-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
padding: 0;
|
||||
|
||||
.alert-action {
|
||||
padding: .5rem;
|
||||
fill: var(--shadow);
|
||||
}
|
||||
}
|
||||
|
||||
.alert-triggers {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.alert-heading {
|
||||
display: block;
|
||||
padding: 0 .5rem .5rem 0;
|
||||
margin: 0;
|
||||
color: var(--shadow-strong);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.alert-more {
|
||||
flex-shrink: 0;
|
||||
margin: 0 0 0 .5rem;
|
||||
color: var(--shadow);
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.alert-remove {
|
||||
height: auto;
|
||||
padding: 0 .5rem 0 .75rem;
|
||||
fill: var(--shadow);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
fill: var(--shadow-strong);
|
||||
}
|
||||
}
|
||||
|
||||
.alert-triggers {
|
||||
dislay: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.alert-trigger {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.alert-actors,
|
||||
.alert-tags {
|
||||
display: flex;
|
||||
grid-gap: .5rem;
|
||||
}
|
||||
|
||||
.tag {
|
||||
color: var(--shadow-strong);
|
||||
padding: .5rem;
|
||||
border: solid 1px var(--shadow-hint);
|
||||
font-size: .9rem;
|
||||
font-weight: bold;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
border: solid 1px var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.entity {
|
||||
width: 10rem;
|
||||
height: 2.5rem;
|
||||
}
|
||||
</style>
|
||||
@@ -2,33 +2,49 @@
|
||||
<div class="stash">
|
||||
<div class="stash-section stash-header">
|
||||
<router-link
|
||||
:to="{ name: 'stash', params: { stashId: stash.id, stashSlug: stash.slug } }"
|
||||
:to="{ name: 'stash', params: { stashId: stash.id, stashSlug: stash.slug, range: 'scenes', pageNumber: 1 } }"
|
||||
class="stash-link nolink"
|
||||
>
|
||||
<h4 class="stash-name">{{ stash.name }}</h4>
|
||||
<span class="stash-more">Browse</span>
|
||||
</router-link>
|
||||
|
||||
<label
|
||||
v-if="isMe"
|
||||
v-tooltip="'Public'"
|
||||
:class="{ public: stash.public }"
|
||||
class="stash-public"
|
||||
>
|
||||
<Icon
|
||||
v-show="stash.public"
|
||||
icon="eye"
|
||||
/>
|
||||
<span class="header-actions noselect">
|
||||
<label
|
||||
v-if="isMe"
|
||||
v-tooltip="'Public'"
|
||||
:class="{ public: stash.public }"
|
||||
class="stash-public"
|
||||
>
|
||||
<Icon
|
||||
v-show="stash.public"
|
||||
icon="eye"
|
||||
/>
|
||||
|
||||
<Icon
|
||||
v-show="!stash.public"
|
||||
icon="eye-blocked"
|
||||
/>
|
||||
|
||||
<Toggle
|
||||
:checked="stash.public"
|
||||
@change="checked => publishStash(checked)"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<Icon
|
||||
v-show="!stash.public"
|
||||
icon="eye-blocked"
|
||||
v-if="isMe && !stash.primary"
|
||||
icon="bin"
|
||||
class="stash-remove"
|
||||
@click.native="showRemoveStash = true"
|
||||
/>
|
||||
|
||||
<Toggle
|
||||
:checked="stash.public"
|
||||
@change="checked => publishStash(stash, checked)"
|
||||
<RemoveStash
|
||||
v-if="showRemoveStash"
|
||||
:stash="stash"
|
||||
@close="removeStash"
|
||||
/>
|
||||
</label>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ul
|
||||
@@ -66,23 +82,33 @@
|
||||
</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.vue';
|
||||
import Toggle from '../form/toggle.vue';
|
||||
|
||||
async function publishStash(stash, isPublic) {
|
||||
async function publishStash(isPublic) {
|
||||
await this.$store.dispatch('updateStash', {
|
||||
stashId: stash.id,
|
||||
stashId: this.stash.id,
|
||||
stash: { public: isPublic },
|
||||
});
|
||||
|
||||
this.$emit('publish', isPublic);
|
||||
}
|
||||
|
||||
async function removeStash(removed) {
|
||||
this.showRemoveStash = false;
|
||||
|
||||
if (removed) {
|
||||
this.$emit('remove');
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
ActorPreview,
|
||||
ScenePreview,
|
||||
RemoveStash,
|
||||
Toggle,
|
||||
},
|
||||
props: {
|
||||
@@ -95,9 +121,15 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
emits: ['publish'],
|
||||
emits: ['publish', 'remove'],
|
||||
data() {
|
||||
return {
|
||||
showRemoveStash: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
publishStash,
|
||||
removeStash,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -107,7 +139,7 @@ export default {
|
||||
min-width: 0;
|
||||
height: 100%;
|
||||
background: var(--background);
|
||||
box-shadow: 0 0 3px var(--shadow-weak);
|
||||
box-shadow: 0 0 3px var(--darken-weak);
|
||||
}
|
||||
|
||||
.stash-section {
|
||||
@@ -123,17 +155,46 @@ export default {
|
||||
|
||||
.stash-header {
|
||||
justify-content: space-between;
|
||||
align-items: stretch;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.stash-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
overflow: hidden;
|
||||
|
||||
&:hover .stash-more {
|
||||
color: var(--primary);
|
||||
}
|
||||
}
|
||||
|
||||
.stash-name {
|
||||
display: inline-block;
|
||||
padding: .5rem;
|
||||
margin: 0;
|
||||
color: var(--shadow-strong);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.stash-more {
|
||||
flex-shrink: 0;
|
||||
margin: 0 0 0 .5rem;
|
||||
color: var(--shadow);
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.stash-public {
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: .5rem;
|
||||
cursor: pointer;
|
||||
@@ -144,10 +205,15 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.stash-name {
|
||||
padding: .5rem;
|
||||
margin: 0;
|
||||
color: var(--shadow-strong);
|
||||
.stash-remove {
|
||||
height: auto;
|
||||
padding: 0 .5rem 0 .75rem;
|
||||
fill: var(--shadow);
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
fill: var(--shadow-strong);
|
||||
}
|
||||
}
|
||||
|
||||
.stash-actors,
|
||||
@@ -172,6 +238,10 @@ export default {
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
font-size: 0;
|
||||
|
||||
&:last-child {
|
||||
padding: 0 .5rem 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -11,44 +11,94 @@
|
||||
v-if="user.stashes?.length > 0"
|
||||
class="section"
|
||||
>
|
||||
<h3 class="heading">Stashes</h3>
|
||||
<div class="section-header">
|
||||
<h3 class="section-heading">Stashes</h3>
|
||||
|
||||
<ul class="stashes nolist">
|
||||
<Icon
|
||||
icon="plus3"
|
||||
class="header-add"
|
||||
@click="showAddStash = true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ul class="section-body stashes nolist">
|
||||
<li
|
||||
v-for="stash in user.stashes"
|
||||
:key="stash.id"
|
||||
class="stashes-stash"
|
||||
>
|
||||
<Stash
|
||||
:stash="stash"
|
||||
:is-me="isMe"
|
||||
@publish="() => fetchUser()"
|
||||
@remove="() => fetchUser()"
|
||||
/>
|
||||
</li>
|
||||
|
||||
<li
|
||||
v-if="isMe"
|
||||
class="stashes-add"
|
||||
class="stashes-stash stashes-add"
|
||||
@click="showAddStash = true"
|
||||
>
|
||||
<Icon icon="plus2" />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<AddStash
|
||||
v-if="showAddStash"
|
||||
@close="closeAddStash"
|
||||
/>
|
||||
</section>
|
||||
|
||||
<AddStash
|
||||
v-if="showAddStash"
|
||||
@close="closeAddStash"
|
||||
/>
|
||||
<section class="section">
|
||||
<div class="section-header">
|
||||
<h3 class="section-heading">Alerts</h3>
|
||||
|
||||
<Icon
|
||||
icon="plus3"
|
||||
class="header-add"
|
||||
@click="showAddAlert = true"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ul class="section-body alerts nolist">
|
||||
<li
|
||||
v-for="alert in user.alerts"
|
||||
:key="`alert-${alert.id}`"
|
||||
class="alert"
|
||||
>
|
||||
<Alert
|
||||
:alert="alert"
|
||||
:is-me="isMe"
|
||||
@remove="() => fetchUser()"
|
||||
/>
|
||||
</li>
|
||||
|
||||
<li
|
||||
class="alerts-add"
|
||||
@click="showAddAlert = true"
|
||||
>
|
||||
<Icon icon="plus2" />
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<AddAlert
|
||||
v-if="showAddAlert"
|
||||
@close="closeAddAlert"
|
||||
>Alert</AddAlert>
|
||||
</section>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Stash from './stash.vue';
|
||||
import AddStash from '../stashes/add-stash.vue';
|
||||
import Alert from './alert.vue';
|
||||
import AddStash from '../stashes/add.vue';
|
||||
import AddAlert from '../alerts/add.vue';
|
||||
|
||||
async function fetchUser() {
|
||||
this.user = await this.$store.dispatch('fetchUser', this.$route.params.username);
|
||||
this.isMe = this.user.id === this.$store.state.auth.user?.id;
|
||||
this.isMe = this.user?.id === this.$store.state.auth.user?.id;
|
||||
|
||||
this.pageTitle = this.user?.username;
|
||||
}
|
||||
@@ -61,13 +111,23 @@ async function closeAddStash(addedStash) {
|
||||
}
|
||||
}
|
||||
|
||||
async function closeAddAlert(addedAlert) {
|
||||
this.showAddAlert = false;
|
||||
|
||||
if (addedAlert) {
|
||||
await this.fetchUser();
|
||||
}
|
||||
}
|
||||
|
||||
async function mounted() {
|
||||
await this.fetchUser();
|
||||
}
|
||||
|
||||
export default {
|
||||
components: {
|
||||
AddAlert,
|
||||
AddStash,
|
||||
Alert,
|
||||
Stash,
|
||||
},
|
||||
data() {
|
||||
@@ -78,10 +138,12 @@ export default {
|
||||
isMe: false,
|
||||
pageTitle: null,
|
||||
showAddStash: false,
|
||||
showAddAlert: false,
|
||||
};
|
||||
},
|
||||
mounted,
|
||||
methods: {
|
||||
closeAddAlert,
|
||||
closeAddStash,
|
||||
fetchUser,
|
||||
},
|
||||
@@ -92,57 +154,96 @@ export default {
|
||||
@import 'breakpoints';
|
||||
|
||||
.header {
|
||||
padding: .5rem 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: var(--profile);
|
||||
}
|
||||
|
||||
.username {
|
||||
padding: .5rem 1rem;
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
color: var(--text-light);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding: 1rem;
|
||||
padding: 1rem 0;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
.stashes {
|
||||
.stashes,
|
||||
.alerts {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
grid-auto-rows: 15fr;
|
||||
grid-gap: 1rem;
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: var(--primary);
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 0 0 1rem 0;
|
||||
}
|
||||
|
||||
.stashes-add {
|
||||
.section-body {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.section-heading {
|
||||
color: var(--primary);
|
||||
padding: 0 1rem;
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.header-add {
|
||||
height: auto;
|
||||
padding: .5rem 1rem;
|
||||
fill: var(--shadow);
|
||||
|
||||
&:hover {
|
||||
fill: var(--primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.stashes-stash {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.stashes-add,
|
||||
.alerts-add {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: var(--shadow-hint);
|
||||
box-sizing: border-box;
|
||||
padding: 1rem;
|
||||
background: var(--shadow-touch);
|
||||
|
||||
.icon {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
fill: var(--shadow-weak);
|
||||
fill: var(--shadow-hint);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: var(--shadow-weak);
|
||||
background: var(--shadow-hint);
|
||||
cursor: pointer;
|
||||
|
||||
.icon {
|
||||
fill: var(--shadow);
|
||||
fill: var(--shadow-weak);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media(max-width: $breakpoint-kilo) {
|
||||
.stashes {
|
||||
.stashes,
|
||||
.alerts {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,23 @@
|
||||
<video
|
||||
ref="player"
|
||||
:poster="poster"
|
||||
crossorigin="anonymous"
|
||||
class="player video-js vjs-big-play-centered"
|
||||
@playing="$emit('play')"
|
||||
@pause="$emit('pause')"
|
||||
>
|
||||
<source
|
||||
:src="getPath(video)"
|
||||
type="video/mp4"
|
||||
>
|
||||
</video>
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import videoJs from 'video.js';
|
||||
import 'videojs-vr/dist/videojs-vr.min';
|
||||
|
||||
function mounted() {
|
||||
function updatePlayer() {
|
||||
this.player.src(this.getPath(this.video));
|
||||
this.player.poster(this.poster);
|
||||
}
|
||||
|
||||
function initPlayer() {
|
||||
this.player = videoJs(this.$refs.player, {
|
||||
controls: true,
|
||||
inactivityTimeout: 1000,
|
||||
@@ -28,6 +29,8 @@ function mounted() {
|
||||
},
|
||||
},
|
||||
}, () => {
|
||||
this.player.src(this.getPath(this.video));
|
||||
|
||||
if (this.video.isVr) {
|
||||
this.player.vr({ projection: '180' });
|
||||
}
|
||||
@@ -45,7 +48,17 @@ export default {
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
mounted,
|
||||
data() {
|
||||
return {
|
||||
player: null,
|
||||
};
|
||||
},
|
||||
emits: ['play', 'pause'],
|
||||
watch: {
|
||||
video: updatePlayer,
|
||||
poster: updatePlayer,
|
||||
},
|
||||
mounted: initPlayer,
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
$breakpoint-pico: 270px;
|
||||
$breakpoint-nano: 320px;
|
||||
$breakpoint-mini: 400px;
|
||||
$breakpoint-micro: 540px;
|
||||
$breakpoint-small: 620px;
|
||||
$breakpoint: 720px;
|
||||
|
||||
@@ -44,6 +44,11 @@
|
||||
&:hover {
|
||||
background: var(--primary-strong);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background: var(--shadow-weak);
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
.button-secondary {
|
||||
|
||||
@@ -5,14 +5,21 @@ $breakpoint3: 1200px;
|
||||
$breakpoint4: 1500px;
|
||||
|
||||
:root {
|
||||
--primary: #f65596;
|
||||
--primary-strong: #f90071;
|
||||
--primary-faded: #ffcce4;
|
||||
|
||||
/*
|
||||
--primary: #f04288;
|
||||
|
||||
--primary: #ff6c88;
|
||||
--primary-strong: #ff4166;
|
||||
--primary-faded: #ffdfee;
|
||||
*/
|
||||
|
||||
--primary: #f28;
|
||||
--primary-strong: #f90071;
|
||||
--primary-faded: #ffcce4;
|
||||
*/
|
||||
|
||||
--text-dark: #222;
|
||||
--text-light: #fff;
|
||||
@@ -21,21 +28,26 @@ $breakpoint4: 1500px;
|
||||
--background-dark: #222;
|
||||
|
||||
--darken: rgba(0, 0, 0, .5);
|
||||
--darken-strong: rgba(0, 0, 0, .7);
|
||||
--darken-extreme: rgba(0, 0, 0, .9);
|
||||
--darken-censor: rgba(0, 0, 0, .95);
|
||||
--darken-extreme: rgba(0, 0, 0, .9);
|
||||
--darken-strong: rgba(0, 0, 0, .7);
|
||||
--darken-weak: rgba(0, 0, 0, .2);
|
||||
--darken-hint: rgba(0, 0, 0, .1);
|
||||
--darken-touch: rgba(0, 0, 0, .05);
|
||||
|
||||
--lighten: rgba(255, 255, 255, .5);
|
||||
--lighten-strong: rgba(255, 255, 255, .7);
|
||||
--lighten-censor: rgba(255, 255, 255, .95);
|
||||
--lighten-extreme: rgba(255, 255, 255, .9);
|
||||
--lighten-strong: rgba(255, 255, 255, .7);
|
||||
--lighten: rgba(255, 255, 255, .5);
|
||||
--lighten-weak: rgba(255, 255, 255, .2);
|
||||
--lighten-hint: rgba(255, 255, 255, .05);
|
||||
--lighten-touch: rgba(255, 255, 255, .03);
|
||||
|
||||
--logo-shadow: drop-shadow(1px 0 0 $shadow-weak) drop-shadow(-1px 0 0 $shadow-weak) drop-shadow(0 1px 0 $shadow-weak) drop-shadow(0 -1px 0 $shadow-weak);
|
||||
--logo-highlight: drop-shadow(0 0 1px $highlight);
|
||||
|
||||
--info: #321b24;
|
||||
|
||||
--male: #0af;
|
||||
--female: #f0a;
|
||||
|
||||
@@ -56,6 +68,7 @@ $breakpoint4: 1500px;
|
||||
--text-contrast: #fff;
|
||||
|
||||
--background: var(--background-light);
|
||||
--background-censor: rgba(255, 255, 255, .95);
|
||||
--background-dim: #f5f5f5;
|
||||
--background-soft: #fdfdfd;
|
||||
|
||||
@@ -69,6 +82,7 @@ $breakpoint4: 1500px;
|
||||
--crease: #eaeaea;
|
||||
|
||||
--shadow: rgba(0, 0, 0, .5);
|
||||
--shadow-censor: rgba(0, 0, 0, .95);
|
||||
--shadow-extreme: rgba(0, 0, 0, .9);
|
||||
--shadow-strong: rgba(0, 0, 0, .7);
|
||||
--shadow-modest: rgba(0, 0, 0, .3);
|
||||
@@ -88,11 +102,12 @@ $breakpoint4: 1500px;
|
||||
--text: #fff;
|
||||
--text-contrast: #222;
|
||||
|
||||
--background: var(--background-dark);
|
||||
--background-dim: #181818;
|
||||
--background-soft: #111;
|
||||
--background: #181818;
|
||||
--background-censor: rgba(0, 0, 0, .95);
|
||||
--background-dim: #111;
|
||||
--background-soft: #000;
|
||||
|
||||
--profile: #000;
|
||||
--profile: #0a0a0a;
|
||||
--tile: #2a2a2a;
|
||||
|
||||
--link: #dd6688;
|
||||
|
||||
BIN
assets/img/favicon/2021-03/favicon-180.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
assets/img/favicon/2021-03/favicon-192.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
assets/img/favicon/2021-03/favicon-32.png
Normal file
|
After Width: | Height: | Size: 635 B |
BIN
assets/img/favicon/2021-03/favicon-512.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
69
assets/img/favicon/2021-03/favicon.svg
Normal file
@@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="26.528891mm"
|
||||
height="26.528891mm"
|
||||
viewBox="0 0 26.528891 26.528891"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
sodipodi:docname="favicon.svg"
|
||||
inkscape:version="1.0.2 (e86c870879, 2021-01-15)">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="3.959798"
|
||||
inkscape:cx="-25.862887"
|
||||
inkscape:cy="46.089189"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="text835"
|
||||
inkscape:document-rotation="0"
|
||||
showgrid="false"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1024"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0" />
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-41.157469,-57.836902)">
|
||||
<g
|
||||
aria-label="t"
|
||||
id="text835"
|
||||
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:35.2778px;line-height:1.25;font-family:Orbitron;-inkscape-font-specification:'Orbitron Heavy';fill:#ff2280;fill-opacity:1;stroke:none;stroke-width:0.264583">
|
||||
<path
|
||||
d="m 53.328303,69.30218 v 9.666111 h 7.584722 v 5.3975 h -7.831667 q -1.058333,0 -2.010833,-0.388055 -0.9525,-0.423334 -1.658056,-1.128889 -0.670277,-0.705556 -1.093611,-1.622778 -0.388055,-0.917222 -0.388055,-1.975556 V 57.836902 h 5.3975 v 6.067778 h 7.584722 v 5.3975 z"
|
||||
style="font-style:normal;font-variant:normal;font-weight:900;font-stretch:normal;font-size:35.2778px;font-family:Orbitron;-inkscape-font-specification:'Orbitron Heavy';fill:#ff2280;fill-opacity:1;stroke-width:0.264583"
|
||||
id="path858" />
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
6
assets/img/icons/alarm-add.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>alarm-add</title>
|
||||
<path d="M8 2c-3.866 0-7 3.134-7 7s3.134 7 7 7 7-3.134 7-7-3.134-7-7-7zM8 14.625c-3.107 0-5.625-2.518-5.625-5.625s2.518-5.625 5.625-5.625c3.107 0 5.625 2.518 5.625 5.625s-2.518 5.625-5.625 5.625zM14.606 4.487c0.251-0.438 0.394-0.946 0.394-1.487 0-1.657-1.343-3-3-3-0.966 0-1.825 0.457-2.374 1.166 2.061 0.426 3.831 1.644 4.98 3.322v0zM6.374 1.166c-0.549-0.709-1.408-1.166-2.374-1.166-1.657 0-3 1.343-3 3 0 0.541 0.143 1.049 0.394 1.487 1.148-1.678 2.919-2.896 4.98-3.322z"></path>
|
||||
<path d="M11 8h-2v-2h-2v2h-2v2h2v2h2v-2h2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 696 B |
6
assets/img/icons/alarm-cancel.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>alarm-cancel</title>
|
||||
<path d="M8 2c-3.866 0-7 3.134-7 7s3.134 7 7 7 7-3.134 7-7-3.134-7-7-7zM8 14.625c-3.107 0-5.625-2.518-5.625-5.625s2.518-5.625 5.625-5.625c3.107 0 5.625 2.518 5.625 5.625s-2.518 5.625-5.625 5.625zM14.606 4.487c0.251-0.438 0.394-0.946 0.394-1.487 0-1.657-1.343-3-3-3-0.966 0-1.825 0.457-2.374 1.166 2.061 0.426 3.831 1.644 4.98 3.322v0zM6.374 1.166c-0.549-0.709-1.408-1.166-2.374-1.166-1.657 0-3 1.343-3 3 0 0.541 0.143 1.049 0.394 1.487 1.148-1.678 2.919-2.896 4.98-3.322z"></path>
|
||||
<path d="M11.457 11.043l-1.414 1.414-2.043-2.043-2.043 2.043-1.414-1.414 2.043-2.043-2.043-2.043 1.414-1.414 2.043 2.043 2.043-2.043 1.414 1.414-2.043 2.043 2.043 2.043z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 826 B |
6
assets/img/icons/alarm-check.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>alarm-check</title>
|
||||
<path d="M8 2c-3.866 0-7 3.134-7 7s3.134 7 7 7 7-3.134 7-7-3.134-7-7-7zM8 14.625c-3.107 0-5.625-2.518-5.625-5.625s2.518-5.625 5.625-5.625c3.107 0 5.625 2.518 5.625 5.625s-2.518 5.625-5.625 5.625zM14.606 4.487c0.251-0.438 0.394-0.946 0.394-1.487 0-1.657-1.343-3-3-3-0.966 0-1.825 0.457-2.374 1.166 2.061 0.426 3.831 1.644 4.98 3.322v0zM6.374 1.166c-0.549-0.709-1.408-1.166-2.374-1.166-1.657 0-3 1.343-3 3 0 0.541 0.143 1.049 0.394 1.487 1.148-1.678 2.919-2.896 4.98-3.322z"></path>
|
||||
<path d="M11 6.5l-4 4-1.5-1.5-1 1 2.5 2.5 5-5z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 702 B |
6
assets/img/icons/alarm.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>alarm</title>
|
||||
<path d="M8 2c-3.866 0-7 3.134-7 7s3.134 7 7 7 7-3.134 7-7-3.134-7-7-7zM8 14.625c-3.107 0-5.625-2.518-5.625-5.625s2.518-5.625 5.625-5.625c3.107 0 5.625 2.518 5.625 5.625s-2.518 5.625-5.625 5.625zM14.606 4.487c0.251-0.438 0.394-0.946 0.394-1.487 0-1.657-1.343-3-3-3-0.966 0-1.825 0.457-2.374 1.166 2.061 0.426 3.831 1.644 4.98 3.322v0zM6.374 1.166c-0.549-0.709-1.408-1.166-2.374-1.166-1.657 0-3 1.343-3 3 0 0.541 0.143 1.049 0.394 1.487 1.148-1.678 2.919-2.896 4.98-3.322z"></path>
|
||||
<path d="M8 9v-4h-1v5h4v-1z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 677 B |
5
assets/img/icons/bell-check.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>bell-check</title>
|
||||
<path d="M16 13c-1.657 0-3-1.343-3-3v-4.455c0-2.199-1.718-4.033-4-4.454v-1.091h-2v1.091c-2.282 0.421-4 2.255-4 4.454v4.455c0 1.657-1.343 3-3 3v1h6.712c-0.081 0.178-0.127 0.377-0.127 0.586 0 0.781 0.633 1.414 1.414 1.414s1.414-0.633 1.414-1.414c0-0.209-0.045-0.407-0.127-0.586h6.713v-1zM7 11.559l-2.42-3.201 0.747-0.747 1.674 1.205 3.83-3.392 0.778 0.778-4.608 5.358z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 540 B |
5
assets/img/icons/bell-cross.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>bell-cross</title>
|
||||
<path d="M16 13c-1.657 0-3-1.343-3-3v-4.455c0-2.199-1.718-4.033-4-4.454v-1.091h-2v1.091c-2.282 0.421-4 2.255-4 4.454v4.455c0 1.657-1.343 3-3 3v1h6.712c-0.081 0.178-0.127 0.377-0.127 0.586 0 0.781 0.633 1.414 1.414 1.414s1.414-0.633 1.414-1.414c0-0.209-0.045-0.407-0.127-0.586h6.713v-1zM11 9.5l-1 1-2-2-2 2-1-1 2-2-2-2 1-1 2 2 2-2 1 1-2 2 2 2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 516 B |
5
assets/img/icons/bell-minus.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>bell-minus</title>
|
||||
<path d="M16 13c-1.657 0-3-1.343-3-3v-4.455c0-2.199-1.718-4.033-4-4.454v-1.091h-2v1.091c-2.282 0.421-4 2.255-4 4.454v4.455c0 1.657-1.343 3-3 3v1h6.712c-0.081 0.178-0.127 0.377-0.127 0.586 0 0.781 0.633 1.414 1.414 1.414s1.414-0.633 1.414-1.414c0-0.209-0.045-0.407-0.127-0.586h6.713v-1zM11 9h-6v-2h6v2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 475 B |
5
assets/img/icons/bell-plus.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>bell-plus</title>
|
||||
<path d="M16 13c-1.657 0-3-1.343-3-3v-4.455c0-2.199-1.718-4.033-4-4.454v-1.091h-2v1.091c-2.282 0.421-4 2.255-4 4.454v4.455c0 1.657-1.343 3-3 3v1h6.712c-0.081 0.178-0.127 0.377-0.127 0.586 0 0.781 0.633 1.414 1.414 1.414s1.414-0.633 1.414-1.414c0-0.209-0.045-0.407-0.127-0.586h6.713v-1zM11 9h-2v2h-2v-2h-2v-2h2v-2h2v2h2v2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 494 B |
5
assets/img/icons/bell.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>bell</title>
|
||||
<path d="M16.023 12.5c0-4.5-4-3.5-4-7 0-0.29-0.028-0.538-0.079-0.749-0.263-1.766-1.44-3.183-2.965-3.615 0.014-0.062 0.021-0.125 0.021-0.191 0-0.52-0.45-0.945-1-0.945s-1 0.425-1 0.945c0 0.065 0.007 0.129 0.021 0.191-1.71 0.484-2.983 2.208-3.020 4.273-0.001 0.030-0.001 0.060-0.001 0.091 0 3.5-4 2.5-4 7 0 1.191 2.665 2.187 6.234 2.439 0.336 0.631 1.001 1.061 1.766 1.061s1.43-0.43 1.766-1.061c3.568-0.251 6.234-1.248 6.234-2.439 0-0.004-0-0.007-0-0.011l0.024 0.011zM12.91 13.345c-0.847 0.226-1.846 0.389-2.918 0.479-0.089-1.022-0.947-1.824-1.992-1.824s-1.903 0.802-1.992 1.824c-1.072-0.090-2.071-0.253-2.918-0.479-1.166-0.311-1.724-0.659-1.928-0.845 0.204-0.186 0.762-0.534 1.928-0.845 1.356-0.362 3.1-0.561 4.91-0.561s3.554 0.199 4.91 0.561c1.166 0.311 1.724 0.659 1.928 0.845-0.204 0.186-0.762 0.534-1.928 0.845z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 981 B |
5
assets/img/icons/bell2.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>bell2</title>
|
||||
<path d="M16 13c-1.657 0-3-1.343-3-3v-4.455c0-2.199-1.718-4.033-4-4.454v-1.091h-2v1.091c-2.282 0.421-4 2.255-4 4.454v4.455c0 1.657-1.343 3-3 3v1h6.712c-0.081 0.178-0.127 0.377-0.127 0.586 0 0.781 0.633 1.414 1.414 1.414s1.414-0.633 1.414-1.414c0-0.209-0.045-0.407-0.127-0.586h6.713v-1z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 454 B |
7
assets/img/icons/bell3.svg
Normal file
@@ -0,0 +1,7 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>bell3</title>
|
||||
<path d="M16 13c-1.657 0-3-1.343-3-3v-4.455c0-2.199-1.718-4.033-4-4.454v-1.091h-2v1.091c-2.282 0.421-4 2.255-4 4.454v4.455c0 1.657-1.343 3-3 3v1h6.712c-0.081 0.178-0.127 0.377-0.127 0.586 0 0.781 0.633 1.414 1.414 1.414s1.414-0.633 1.414-1.414c0-0.209-0.045-0.407-0.127-0.586h6.713v-1z"></path>
|
||||
<path d="M15.483 6c-0.261 0-0.481-0.203-0.498-0.467-0.118-1.787-0.908-3.444-2.226-4.666-0.202-0.188-0.214-0.504-0.027-0.707s0.504-0.214 0.707-0.027c1.506 1.397 2.409 3.291 2.543 5.334 0.018 0.276-0.191 0.514-0.466 0.532-0.011 0.001-0.022 0.001-0.033 0.001z"></path>
|
||||
<path d="M0.517 6c-0.011 0-0.022-0-0.033-0.001-0.276-0.018-0.484-0.256-0.466-0.532 0.134-2.043 1.038-3.937 2.543-5.334 0.203-0.188 0.519-0.176 0.707 0.027s0.176 0.519-0.027 0.707c-1.318 1.222-2.108 2.879-2.226 4.666-0.017 0.264-0.237 0.467-0.498 0.467z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 982 B |
6
assets/img/icons/calendar-day.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>calendar-day</title>
|
||||
<path d="M8 9c-0.553 0-1-0.447-1-1s0.447-1 1-1 1 0.447 1 1-0.447 1-1 1z"></path>
|
||||
<path d="M14 2v-2h-2v2h-8v-2h-2v2h-2v14h16v-14h-2zM3 15h-2v-2h2v2zM3 12h-2v-2h2v2zM3 9h-2v-2h2v2zM3 6h-2v-2h2v2zM6 15h-2v-2h2v2zM6 12h-2v-2h2v2zM6 9h-2v-2h2v2zM6 6h-2v-2h2v2zM9 15h-2v-2h2v2zM9 12h-2v-2h2v2zM9 9h-2v-2h2v2zM9 6h-2v-2h2v2zM12 15h-2v-2h2v2zM12 12h-2v-2h2v2zM12 9h-2v-2h2v2zM12 6h-2v-2h2v2zM15 15h-2v-2h2v2zM15 12h-2v-2h2v2zM15 9h-2v-2h2v2zM15 6h-2v-2h2v2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 625 B |
5
assets/img/icons/calendar-empty.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>calendar-empty</title>
|
||||
<path d="M14 2v-2h-2v2h-8v-2h-2v2h-2v14h16v-14h-2zM3 15h-2v-2h2v2zM3 12h-2v-2h2v2zM3 9h-2v-2h2v2zM3 6h-2v-2h2v2zM6 15h-2v-2h2v2zM6 12h-2v-2h2v2zM6 9h-2v-2h2v2zM6 6h-2v-2h2v2zM9 15h-2v-2h2v2zM9 12h-2v-2h2v2zM9 9h-2v-2h2v2zM9 6h-2v-2h2v2zM12 15h-2v-2h2v2zM12 12h-2v-2h2v2zM12 9h-2v-2h2v2zM12 6h-2v-2h2v2zM15 15h-2v-2h2v2zM15 12h-2v-2h2v2zM15 9h-2v-2h2v2zM15 6h-2v-2h2v2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 546 B |
6
assets/img/icons/calendar-week.svg
Normal file
@@ -0,0 +1,6 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>calendar-week</title>
|
||||
<path d="M8 9c-0.553 0-1-0.447-1-1s0.447-1 1-1 1 0.447 1 1-0.447 1-1 1zM12 8c0-0.553-0.447-1-1-1s-1 0.447-1 1 0.447 1 1 1 1-0.447 1-1zM15 8c0-0.553-0.447-1-1-1s-1 0.447-1 1 0.447 1 1 1 1-0.447 1-1zM6 8c0-0.553-0.447-1-1-1s-1 0.447-1 1 0.447 1 1 1 1-0.447 1-1zM3 8c0-0.553-0.447-1-1-1s-1 0.447-1 1 0.447 1 1 1 1-0.447 1-1z"></path>
|
||||
<path d="M14 2v-2h-2v2h-8v-2h-2v2h-2v14h16v-14h-2zM3 15h-2v-2h2v2zM3 12h-2v-2h2v2zM3 9h-2v-2h2v2zM3 6h-2v-2h2v2zM6 15h-2v-2h2v2zM6 12h-2v-2h2v2zM6 9h-2v-2h2v2zM6 6h-2v-2h2v2zM9 15h-2v-2h2v2zM9 12h-2v-2h2v2zM9 9h-2v-2h2v2zM9 6h-2v-2h2v2zM12 15h-2v-2h2v2zM12 12h-2v-2h2v2zM12 9h-2v-2h2v2zM12 6h-2v-2h2v2zM15 15h-2v-2h2v2zM15 12h-2v-2h2v2zM15 9h-2v-2h2v2zM15 6h-2v-2h2v2z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 876 B |
35
assets/img/icons/calendar3.svg
Normal file
@@ -0,0 +1,35 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>calendar3</title>
|
||||
<path d="M8 7h1v1h-1v-1z"></path>
|
||||
<path d="M10 9h1v1h-1v-1z"></path>
|
||||
<path d="M10 11h1v1h-1v-1z"></path>
|
||||
<path d="M8 5h1v1h-1v-1z"></path>
|
||||
<path d="M8 9h1v1h-1v-1z"></path>
|
||||
<path d="M8 13h1v1h-1v-1z"></path>
|
||||
<path d="M8 11h1v1h-1v-1z"></path>
|
||||
<path d="M6 5h1v1h-1v-1z"></path>
|
||||
<path d="M12 9h1v1h-1v-1z"></path>
|
||||
<path d="M12 7h1v1h-1v-1z"></path>
|
||||
<path d="M12 5h1v1h-1v-1z"></path>
|
||||
<path d="M12 11h1v1h-1v-1z"></path>
|
||||
<path d="M10 5h1v1h-1v-1z"></path>
|
||||
<path d="M12 13h1v1h-1v-1z"></path>
|
||||
<path d="M10 7h1v1h-1v-1z"></path>
|
||||
<path d="M10 13h1v1h-1v-1z"></path>
|
||||
<path d="M2 7h1v1h-1v-1z"></path>
|
||||
<path d="M2 9h1v1h-1v-1z"></path>
|
||||
<path d="M4 13h1v1h-1v-1z"></path>
|
||||
<path d="M6 7h1v1h-1v-1z"></path>
|
||||
<path d="M2 13h1v1h-1v-1z"></path>
|
||||
<path d="M2 11h1v1h-1v-1z"></path>
|
||||
<path d="M2 5h1v1h-1v-1z"></path>
|
||||
<path d="M6 11h1v1h-1v-1z"></path>
|
||||
<path d="M4 11h1v1h-1v-1z"></path>
|
||||
<path d="M6 9h1v1h-1v-1z"></path>
|
||||
<path d="M6 13h1v1h-1v-1z"></path>
|
||||
<path d="M4 5h1v1h-1v-1z"></path>
|
||||
<path d="M4 9h1v1h-1v-1z"></path>
|
||||
<path d="M4 7h1v1h-1v-1z"></path>
|
||||
<path d="M12 1v-1h-2v1h-5v-1h-2v1h-3v15h15v-15h-3zM10 2h2v1h-2v-1zM3 2h2v1h-2v-1zM14 15h-13v-11h13v11z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
5
assets/img/icons/calendar5.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>calendar5</title>
|
||||
<path d="M14 2h-1.5v0.5c0 0.551-0.449 1-1 1s-1-0.449-1-1v-0.5h-5v0.5c0 0.551-0.449 1-1 1s-1-0.449-1-1v-0.5h-1.5c-0.55 0-1 0.45-1 1v11c0 0.55 0.45 1 1 1h12c0.55 0 1-0.45 1-1v-11c0-0.55-0.45-1-1-1zM14 13.998c-0.001 0.001-0.001 0.001-0.002 0.002h-11.996c-0.001-0.001-0.001-0.001-0.002-0.002v-8.998h12v8.998zM4.5 3c0.276 0 0.5-0.224 0.5-0.5v-2c0-0.276-0.224-0.5-0.5-0.5s-0.5 0.224-0.5 0.5v2c0 0.276 0.224 0.5 0.5 0.5zM11.5 3c0.276 0 0.5-0.224 0.5-0.5v-2c0-0.276-0.224-0.5-0.5-0.5s-0.5 0.224-0.5 0.5v2c0 0.276 0.224 0.5 0.5 0.5zM9 6h-5v1h4v2h-4v1h4v2h-4v1h5zM11 13h1v-7h-2v1h1zM13.625 15.375h-11.25c-0.55 0-1-0.325-1-0.875v0.5c0 0.55 0.45 1 1 1h11.25c0.55 0 1-0.45 1-1v-0.5c0 0.55-0.45 0.875-1 0.875z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 868 B |
5
assets/img/icons/device_hub.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||
<title>device_hub</title>
|
||||
<path d="M17.016 15.984h3.984v5.016h-5.016v-3.047l-3.984-4.219-3.984 4.219v3.047h-5.016v-5.016h3.984l4.031-3.984v-3.188q-0.891-0.328-1.453-1.078t-0.563-1.734q0-1.219 0.891-2.109t2.109-0.891 2.109 0.891 0.891 2.109q0 0.984-0.563 1.734t-1.453 1.078v3.188z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 427 B |
33
assets/img/icons/discord.svg
Executable file
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 725.59998 198.3"
|
||||
version="1.1"
|
||||
id="svg6"
|
||||
width="725.59998"
|
||||
height="198.3">
|
||||
<metadata
|
||||
id="metadata12">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs10" />
|
||||
<path
|
||||
d="m 105.9,83.2 c -5.7,0 -10.2,4.9 -10.2,11 0,6.1 4.6,11 10.2,11 5.7,0 10.2,-4.9 10.2,-11 0,-6.1 -4.6,-11 -10.2,-11 z m -36.5,0 c -5.7,0 -10.2,4.9 -10.2,11 0,6.1 4.6,11 10.2,11 5.7,0 10.2,-4.9 10.2,-11 0.1,-6.1 -4.5,-11 -10.2,-11 z"
|
||||
id="path2" />
|
||||
<path
|
||||
d="M 154.5,0 H 20.5 C 9.2,0 0,9.2 0,20.5 v 134 C 0,165.8 9.2,175 20.5,175 h 113.4 l -5.3,-18.3 12.8,11.8 12.1,11.1 21.6,18.7 V 20.5 C 175,9.2 165.8,0 154.5,0 Z m -38.6,129.5 c 0,0 -3.6,-4.3 -6.6,-8 13.1,-3.7 18.1,-11.8 18.1,-11.8 -4.1,2.7 -8,4.6 -11.5,5.9 -5,2.1 -9.8,3.4 -14.5,4.3 -9.6,1.8 -18.4,1.3 -25.9,-0.1 -5.7,-1.1 -10.6,-2.6 -14.7,-4.3 -2.3,-0.9 -4.8,-2 -7.3,-3.4 -0.3,-0.2 -0.6,-0.3 -0.9,-0.5 -0.2,-0.1 -0.3,-0.2 -0.4,-0.2 -1.8,-1 -2.8,-1.7 -2.8,-1.7 0,0 4.8,7.9 17.5,11.7 -3,3.8 -6.7,8.2 -6.7,8.2 C 38.1,128.9 29.7,114.5 29.7,114.5 29.7,82.6 44.1,56.7 44.1,56.7 58.5,46 72.1,46.3 72.1,46.3 l 1,1.2 c -18,5.1 -26.2,13 -26.2,13 0,0 2.2,-1.2 5.9,-2.8 10.7,-4.7 19.2,-5.9 22.7,-6.3 0.6,-0.1 1.1,-0.2 1.7,-0.2 6.1,-0.8 13,-1 20.2,-0.2 9.5,1.1 19.7,3.9 30.1,9.5 0,0 -7.9,-7.5 -24.9,-12.6 l 1.4,-1.6 c 0,0 13.7,-0.3 28,10.4 0,0 14.4,25.9 14.4,57.8 0,-0.1 -8.4,14.3 -30.5,15 z m 151,-86.7 H 233.7 V 80.1 L 255.8,100 V 63.8 h 11.8 c 7.5,0 11.2,3.6 11.2,9.4 v 27.7 c 0,5.8 -3.5,9.7 -11.2,9.7 h -34 v 21.1 h 33.2 c 17.8,0.1 34.5,-8.8 34.5,-29.2 V 72.7 C 301.4,51.9 284.7,42.8 266.9,42.8 Z m 174,59.7 V 71.9 c 0,-11 19.8,-13.5 25.8,-2.5 L 485,62 C 477.8,46.2 464.7,41.6 453.8,41.6 436,41.6 418.4,51.9 418.4,71.9 v 30.6 c 0,20.2 17.6,30.3 35,30.3 11.2,0 24.6,-5.5 32,-19.9 l -19.6,-9 c -4.8,12.3 -24.9,9.3 -24.9,-1.4 z M 380.4,76.1 c -6.9,-1.5 -11.5,-4 -11.8,-8.3 0.4,-10.3 16.3,-10.7 25.6,-0.8 l 14.7,-11.3 c -9.2,-11.2 -19.6,-14.2 -30.3,-14.2 -16.3,0 -32.1,9.2 -32.1,26.6 0,16.9 13,26 27.3,28.2 7.3,1 15.4,3.9 15.2,8.9 -0.6,9.5 -20.2,9 -29.1,-1.8 l -14.2,13.3 c 8.3,10.7 19.6,16.1 30.2,16.1 16.3,0 34.4,-9.4 35.1,-26.6 C 412,84.5 396.2,79 380.4,76.1 Z m -67,55.5 h 22.4 V 42.8 H 313.4 Z M 691.1,42.8 H 657.9 V 80.1 L 680,100 V 63.8 h 11.8 c 7.5,0 11.2,3.6 11.2,9.4 v 27.7 c 0,5.8 -3.5,9.7 -11.2,9.7 h -34 v 21.1 h 33.3 c 17.8,0.1 34.5,-8.8 34.5,-29.2 V 72.7 c 0,-20.8 -16.7,-29.9 -34.5,-29.9 z M 528.2,41.6 c -18.4,0 -36.7,10 -36.7,30.5 v 30.3 c 0,20.3 18.4,30.5 36.9,30.5 18.4,0 36.7,-10.2 36.7,-30.5 V 72.1 c 0,-20.4 -18.5,-30.5 -36.9,-30.5 z m 14.4,60.8 c 0,6.4 -7.2,9.7 -14.3,9.7 -7.2,0 -14.4,-3.1 -14.4,-9.7 V 72.1 c 0,-6.5 7,-10 14,-10 7.3,0 14.7,3.1 14.7,10 z M 645.5,72.1 C 645,51.3 630.8,42.9 612.5,42.9 H 577 v 88.8 h 22.7 v -28.2 h 4 l 20.6,28.2 h 28 l -24.2,-30.5 c 10.7,-3.4 17.4,-12.7 17.4,-29.1 z m -32.6,12 H 599.7 V 63.8 h 13.2 c 14.1,0 14.1,20.3 0,20.3 z"
|
||||
id="path4" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
5
assets/img/icons/earth.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>earth</title>
|
||||
<path d="M8 0c-4.418 0-8 3.582-8 8s3.582 8 8 8 8-3.582 8-8-3.582-8-8-8zM8 15c-0.984 0-1.92-0.203-2.769-0.57l3.643-4.098c0.081-0.092 0.126-0.21 0.126-0.332v-1.5c0-0.276-0.224-0.5-0.5-0.5-1.765 0-3.628-1.835-3.646-1.854-0.094-0.094-0.221-0.146-0.354-0.146h-2c-0.276 0-0.5 0.224-0.5 0.5v3c0 0.189 0.107 0.363 0.276 0.447l1.724 0.862v2.936c-1.813-1.265-3-3.366-3-5.745 0-1.074 0.242-2.091 0.674-3h1.826c0.133 0 0.26-0.053 0.354-0.146l2-2c0.094-0.094 0.146-0.221 0.146-0.354v-1.21c0.634-0.189 1.305-0.29 2-0.29 1.1 0 2.141 0.254 3.067 0.706-0.065 0.055-0.128 0.112-0.188 0.172-0.567 0.567-0.879 1.32-0.879 2.121s0.312 1.555 0.879 2.121c0.569 0.569 1.332 0.879 2.119 0.879 0.049 0 0.099-0.001 0.149-0.004 0.216 0.809 0.605 2.917-0.131 5.818-0.007 0.027-0.011 0.055-0.013 0.082-1.271 1.298-3.042 2.104-5.002 2.104z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 976 B |
5
assets/img/icons/envelop.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>envelop</title>
|
||||
<path d="M15 2h-14c-0.55 0-1 0.45-1 1v10c0 0.55 0.45 1 1 1h14c0.55 0 1-0.45 1-1v-10c0-0.55-0.45-1-1-1zM5.831 9.773l-3 2.182c-0.1 0.073-0.216 0.108-0.33 0.108-0.174 0-0.345-0.080-0.455-0.232-0.183-0.251-0.127-0.603 0.124-0.786l3-2.182c0.251-0.183 0.603-0.127 0.786 0.124s0.127 0.603-0.124 0.786zM13.955 11.831c-0.11 0.151-0.282 0.232-0.455 0.232-0.115 0-0.23-0.035-0.33-0.108l-3-2.182c-0.251-0.183-0.307-0.534-0.124-0.786s0.535-0.307 0.786-0.124l3 2.182c0.251 0.183 0.307 0.535 0.124 0.786zM13.831 4.955l-5.5 4c-0.099 0.072-0.215 0.108-0.331 0.108s-0.232-0.036-0.331-0.108l-5.5-4c-0.251-0.183-0.307-0.534-0.124-0.786s0.535-0.307 0.786-0.124l5.169 3.759 5.169-3.759c0.251-0.183 0.603-0.127 0.786 0.124s0.127 0.603-0.124 0.786z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 895 B |
8
assets/img/icons/envelop2.svg
Normal file
@@ -0,0 +1,8 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>envelop2</title>
|
||||
<path d="M14.998 3c0.001 0.001 0.001 0.001 0.002 0.002v9.996c-0.001 0.001-0.001 0.001-0.002 0.002h-13.996c-0.001-0.001-0.001-0.001-0.002-0.002v-9.996c0.001-0.001 0.001-0.001 0.002-0.002h13.996zM15 2h-14c-0.55 0-1 0.45-1 1v10c0 0.55 0.45 1 1 1h14c0.55 0 1-0.45 1-1v-10c0-0.55-0.45-1-1-1v0z"></path>
|
||||
<path d="M5.831 9.773l-3 2.182c-0.1 0.073-0.216 0.108-0.33 0.108-0.174 0-0.345-0.080-0.455-0.232-0.183-0.251-0.127-0.603 0.124-0.786l3-2.182c0.251-0.183 0.603-0.127 0.786 0.124s0.127 0.603-0.124 0.786z"></path>
|
||||
<path d="M13.955 11.831c-0.11 0.151-0.282 0.232-0.455 0.232-0.115 0-0.23-0.035-0.33-0.108l-3-2.182c-0.251-0.183-0.307-0.534-0.124-0.786s0.534-0.307 0.786-0.124l3 2.182c0.251 0.183 0.307 0.535 0.124 0.786z"></path>
|
||||
<path d="M13.831 4.955l-5.5 4c-0.099 0.072-0.215 0.108-0.331 0.108s-0.232-0.036-0.331-0.108l-5.5-4c-0.251-0.183-0.307-0.534-0.124-0.786s0.535-0.307 0.786-0.124l5.169 3.759 5.169-3.759c0.251-0.183 0.603-0.127 0.786 0.124s0.127 0.603-0.124 0.786v0z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
5
assets/img/icons/envelop3.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>envelop3</title>
|
||||
<path d="M15 2h-14c-0.55 0-1 0.45-1 1v10c0 0.55 0.45 1 1 1h14c0.55 0 1-0.45 1-1v-10c0-0.55-0.45-1-1-1zM14 4v0.719l-6 3.536-6-3.536v-0.719h12zM2 12v-5.54l6 3.536 6-3.536v5.54h-12z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 350 B |
5
assets/img/icons/envelop4.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>envelop4</title>
|
||||
<path d="M14.5 2h-13c-0.825 0-1.5 0.675-1.5 1.5v10c0 0.825 0.675 1.5 1.5 1.5h13c0.825 0 1.5-0.675 1.5-1.5v-10c0-0.825-0.675-1.5-1.5-1.5zM6.23 8.6l-4.23 3.295v-7.838l4.23 4.543zM2.756 4h10.488l-5.244 3.938-5.244-3.938zM6.395 8.777l1.605 1.723 1.605-1.723 3.29 4.223h-9.79l3.29-4.223zM9.77 8.6l4.23-4.543v7.838l-4.23-3.295z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 493 B |
5
assets/img/icons/envelop5.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>envelop5</title>
|
||||
<path d="M14.5 2h-13c-0.825 0-1.5 0.675-1.5 1.5v9c0 0.825 0.675 1.5 1.5 1.5h13c0.825 0 1.5-0.675 1.5-1.5v-9c0-0.825-0.675-1.5-1.5-1.5zM14 5l-6 4.5-6-4.5v-1l6 3 6-3v1z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 338 B |
5
assets/img/icons/film4.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>film4</title>
|
||||
<path d="M0 2v12h16v-12h-16zM3 13h-2v-2h2v2zM3 9h-2v-2h2v2zM3 5h-2v-2h2v2zM12 13h-8v-10h8v10zM15 13h-2v-2h2v2zM15 9h-2v-2h2v2zM15 5h-2v-2h2v2zM6 5v6l4-3z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 322 B |
5
assets/img/icons/lan2.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>lan2</title>
|
||||
<path d="M14.5 8h-6.5v-2h1.5c0.276 0 0.5-0.224 0.5-0.5v-4c0-0.276-0.224-0.5-0.5-0.5h-4c-0.276 0-0.5 0.224-0.5 0.5v4c0 0.276 0.224 0.5 0.5 0.5h1.5v2h-6.5c-0.276 0-0.5 0.224-0.5 0.5s0.224 0.5 0.5 0.5h2.5v2h-1.5c-0.276 0-0.5 0.224-0.5 0.5v4c0 0.276 0.224 0.5 0.5 0.5h4c0.276 0 0.5-0.224 0.5-0.5v-4c0-0.276-0.224-0.5-0.5-0.5h-1.5v-2h7v2h-1.5c-0.276 0-0.5 0.224-0.5 0.5v4c0 0.276 0.224 0.5 0.5 0.5h4c0.276 0 0.5-0.224 0.5-0.5v-4c0-0.276-0.224-0.5-0.5-0.5h-1.5v-2h2.5c0.276 0 0.5-0.224 0.5-0.5s-0.224-0.5-0.5-0.5zM6 2h3v3h-3v-3zM5 15h-3v-3h3v3zM13 15h-3v-3h3v3z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 723 B |
5
assets/img/icons/notification2.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>notification2</title>
|
||||
<path d="M8 0c-4.418 0-8 3.582-8 8s3.582 8 8 8 8-3.582 8-8-3.582-8-8-8zM9 13h-2v-2h2v2zM9 9h-2v-6h2v6z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 279 B |
5
assets/img/icons/presentation.svg
Normal file
@@ -0,0 +1,5 @@
|
||||
<!-- Generated by IcoMoon.io -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>presentation</title>
|
||||
<path d="M14.25 0h-13.5c-0.412 0-0.75 0.338-0.75 0.75v11.5c0 0.412 0.338 0.75 0.75 0.75h4.455l-2.040 2.040c-0.22 0.22-0.22 0.576 0 0.795 0.11 0.11 0.254 0.165 0.398 0.165s0.288-0.055 0.398-0.165l2.835-2.835h0.205v2.5c0 0.276 0.224 0.5 0.5 0.5s0.5-0.224 0.5-0.5v-2.5h0.205l2.835 2.835c0.11 0.11 0.254 0.165 0.398 0.165s0.288-0.055 0.398-0.165c0.22-0.22 0.22-0.576 0-0.795l-2.040-2.040h4.455c0.412 0 0.75-0.338 0.75-0.75v-11.5c0-0.412-0.338-0.75-0.75-0.75zM6 3l4 2.5-4 2.5v-5zM12.5 11h-10c-0.276 0-0.5-0.224-0.5-0.5s0.224-0.5 0.5-0.5h10c0.276 0 0.5 0.224 0.5 0.5s-0.224 0.5-0.5 0.5z"></path>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 756 B |