Merged filters into new settings dialog, added experimental summary field.

This commit is contained in:
DebaucheryLibrarian 2022-11-28 03:33:46 +01:00
parent 637669e3d1
commit b5e308562e
15 changed files with 740 additions and 72 deletions

View File

@ -8,17 +8,10 @@
/> />
<transition name="slide"> <transition name="slide">
<Sidebar <Sidebar v-if="showSidebar" />
v-if="showSidebar"
@toggle-sidebar="(state) => toggleSidebar(state)"
@show-filters="(state) => toggleFilters(state)"
/>
</transition> </transition>
<Header <Header />
@toggle-sidebar="(state) => toggleSidebar(state)"
@show-filters="(state) => toggleFilters(state)"
/>
<p <p
v-if="config.showDisclaimer" v-if="config.showDisclaimer"
@ -30,13 +23,18 @@
class="content" class="content"
@scroll="scroll" @scroll="scroll"
> >
<router-view @scroll="scrollToTop" /> <RouterView @scroll="scrollToTop" />
</div> </div>
<Filters <Filters
v-if="showFilters" v-if="showFilters"
@close="toggleFilters(false)" @close="toggleFilters(false)"
/> />
<Settings
v-if="showSettings"
@close="toggleSettings(false)"
/>
</div> </div>
</template> </template>
@ -45,6 +43,7 @@ import Warning from './warning.vue';
import Header from '../header/header.vue'; import Header from '../header/header.vue';
import Sidebar from '../sidebar/sidebar.vue'; import Sidebar from '../sidebar/sidebar.vue';
import Filters from '../filters/filters.vue'; import Filters from '../filters/filters.vue';
import Settings from '../settings/settings.vue';
function toggleSidebar(state) { function toggleSidebar(state) {
this.showSidebar = typeof state === 'boolean' ? state : !this.showSidebar; this.showSidebar = typeof state === 'boolean' ? state : !this.showSidebar;
@ -55,6 +54,11 @@ function toggleFilters(state) {
this.showSidebar = false; this.showSidebar = false;
} }
function toggleSettings(state) {
this.showSettings = state;
this.showSidebar = false;
}
async function setConsent(consent, includeQueer) { async function setConsent(consent, includeQueer) {
if (consent) { if (consent) {
this.showWarning = false; this.showWarning = false;
@ -88,6 +92,9 @@ function scrollToTop() {
function mounted() { function mounted() {
document.addEventListener('click', this.blur); document.addEventListener('click', this.blur);
window.addEventListener('resize', this.resize); window.addEventListener('resize', this.resize);
this.events.on('toggleSettings', this.toggleSettings);
this.events.on('toggleSidebar', this.toggleSidebar);
} }
function beforeUnmount() { function beforeUnmount() {
@ -101,12 +108,14 @@ export default {
Sidebar, Sidebar,
Warning, Warning,
Filters, Filters,
Settings,
}, },
data() { data() {
return { return {
showSidebar: false, showSidebar: false,
showWarning: localStorage.getItem('consent') !== window.env.sessionId, showWarning: localStorage.getItem('consent') !== window.env.sessionId,
showFilters: false, showFilters: false,
showSettings: false,
selected: null, selected: null,
}; };
}, },
@ -115,6 +124,7 @@ export default {
methods: { methods: {
toggleSidebar, toggleSidebar,
toggleFilters, toggleFilters,
toggleSettings,
setConsent, setConsent,
blur, blur,
resize, resize,

View File

@ -114,13 +114,16 @@ export default {
} }
::v-deep(.dialog-body) { ::v-deep(.dialog-body) {
padding: 1rem;
flex-grow: 1; flex-grow: 1;
box-sizing: border-box;
padding: 1rem;
overflow-y: auto; overflow-y: auto;
} }
::v-deep(.dialog-section:not(:last-child)) { ::v-deep(.dialog-section:not(:last-child)) {
padding-bottom: 1rem;
border-bottom: solid 1px var(--shadow-hint); border-bottom: solid 1px var(--shadow-hint);
margin-bottom: 1rem;
overflow: auto; overflow: auto;
} }

View File

@ -1,7 +1,7 @@
<template> <template>
<header class="header"> <header class="header">
<div class="header-nav"> <div class="header-nav">
<router-link <RouterLink
to="/" to="/"
class="logo-link" class="logo-link"
><h1 class="header-logo"> ><h1 class="header-logo">
@ -9,12 +9,12 @@
class="logo" class="logo"
v-html="logo" v-html="logo"
/> />
</h1></router-link> </h1></RouterLink>
<nav class="nav"> <nav class="nav">
<ul class="nav-list nolist"> <ul class="nav-list nolist">
<li class="nav-item"> <li class="nav-item">
<router-link <RouterLink
v-slot="{ href, isActive, navigate }" v-slot="{ href, isActive, navigate }"
:to="{ name: 'actors', params: { pageNumber: 1 } }" :to="{ name: 'actors', params: { pageNumber: 1 } }"
custom custom
@ -25,11 +25,11 @@
:class="{ active: isActive }" :class="{ active: isActive }"
@click="navigate" @click="navigate"
>Actors</a> >Actors</a>
</router-link> </RouterLink>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<router-link <RouterLink
v-slot="{ href, isActive, navigate }" v-slot="{ href, isActive, navigate }"
:to="{ name: 'channels' }" :to="{ name: 'channels' }"
custom custom
@ -40,11 +40,11 @@
:class="{ active: isActive }" :class="{ active: isActive }"
@click="navigate" @click="navigate"
>Channels</a> >Channels</a>
</router-link> </RouterLink>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<router-link <RouterLink
v-slot="{ href, isActive, navigate }" v-slot="{ href, isActive, navigate }"
:to="{ name: 'tags' }" :to="{ name: 'tags' }"
custom custom
@ -55,11 +55,11 @@
:class="{ active: isActive }" :class="{ active: isActive }"
@click="navigate" @click="navigate"
>Tags</a> >Tags</a>
</router-link> </RouterLink>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<router-link <RouterLink
v-slot="{ href, isActive, navigate }" v-slot="{ href, isActive, navigate }"
:to="{ name: 'movies', params: { range: 'latest', pageNumber: 1 } }" :to="{ name: 'movies', params: { range: 'latest', pageNumber: 1 } }"
custom custom
@ -70,7 +70,7 @@
:class="{ active: isActive }" :class="{ active: isActive }"
@click="navigate" @click="navigate"
>Movies</a> >Movies</a>
</router-link> </RouterLink>
</li> </li>
</ul> </ul>
</nav> </nav>
@ -79,7 +79,7 @@
<div class="header-section"> <div class="header-section">
<div <div
class="sidebar-toggle noselect" class="sidebar-toggle noselect"
@click.stop="$emit('toggleSidebar')" @click.stop="events.emit('toggleSidebar')"
><Icon icon="menu" /></div> ><Icon icon="menu" /></div>
<Tooltip v-if="me"> <Tooltip v-if="me">
@ -95,7 +95,7 @@
>{{ unseenNotificationsCount }}</span> >{{ unseenNotificationsCount }}</span>
</div> </div>
<template v-slot:tooltip> <template #tooltip>
<Notifications <Notifications
:notifications="notifications" :notifications="notifications"
:unseen-count="unseenNotificationsCount" :unseen-count="unseenNotificationsCount"
@ -115,8 +115,8 @@
</div> </div>
</div> </div>
<template v-slot:tooltip> <template #tooltip>
<Menu @show-filters="state => $emit('showFilters', state)" /> <Menu />
</template> </template>
</Tooltip> </Tooltip>
@ -133,7 +133,7 @@
icon="search" icon="search"
/></button> /></button>
<template v-slot:tooltip> <template #tooltip>
<Search <Search
:searching="searching" :searching="searching"
class="compact" class="compact"

View File

@ -68,9 +68,9 @@
<li <li
class="menu-item" class="menu-item"
@click="$emit('showFilters', true)" @click="events.emit('toggleSettings', true)"
> >
<Icon icon="filter" />Filters <Icon icon="cog" />Settings
</li> </li>
<li <li
@ -104,7 +104,7 @@ function signup(state) {
} }
function favorites() { function favorites() {
return this.me?.stashes.find(stash => stash.primary); return this.me?.stashes.find((stash) => stash.primary);
} }
function me(state) { function me(state) {
@ -130,7 +130,6 @@ export default {
}), }),
favorites, favorites,
}, },
emits: ['showFilters'],
methods: { methods: {
setSfw, setSfw,
setTheme, setTheme,

View File

@ -85,7 +85,7 @@
<span class="row-label">Part of</span> <span class="row-label">Part of</span>
<div class="movies"> <div class="movies">
<router-link <RouterLink
v-for="movie in [...release.movies, ...release.series]" v-for="movie in [...release.movies, ...release.series]"
:key="`movie-${movie.id}`" :key="`movie-${movie.id}`"
:to="{ name: movie.type || 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }" :to="{ name: movie.type || 'movie', params: { releaseId: movie.id, releaseSlug: movie.slug } }"
@ -98,7 +98,7 @@
:src="getPath(movie.covers[0] || movie.poster, 'thumbnail')" :src="getPath(movie.covers[0] || movie.poster, 'thumbnail')"
class="movie-cover" class="movie-cover"
> >
</router-link> </RouterLink>
</div> </div>
</div> </div>
@ -113,12 +113,12 @@
> >
<span class="row-label">Director</span> <span class="row-label">Director</span>
<router-link <RouterLink
v-for="director in release.directors" v-for="director in release.directors"
:key="`director-${director.id}`" :key="`director-${director.id}`"
class="link director" class="link director"
:to="`/director/${director.id}/${director.slug}`" :to="`/director/${director.id}/${director.slug}`"
>{{ director.name }}</router-link> >{{ director.name }}</RouterLink>
</div> </div>
<div <div
@ -162,10 +162,10 @@
> >
<span class="row-label">Studio</span> <span class="row-label">Studio</span>
<router-link <RouterLink
:to="`/studio/${release.studio.slug}`" :to="`/studio/${release.studio.slug}`"
class="link studio" class="link studio"
>{{ release.studio.name }}</router-link> >{{ release.studio.name }}</RouterLink>
</div> </div>
<div <div
@ -227,17 +227,40 @@
<div class="row"> <div class="row">
<span class="row-label">Added</span> <span class="row-label">Added</span>
<router-link <RouterLink
:to="`/added/${formatDate(release.createdAt, 'YYYY/MM/DD')}`" :to="`/added/${formatDate(release.createdAt, 'YYYY/MM/DD')}`"
:title="`Added on ${formatDate(release.createdAt, 'MMMM D, YYYY HH:mm')}`" :title="`Added on ${formatDate(release.createdAt, 'MMMM D, YYYY HH:mm')}`"
class="link added" class="link added"
>{{ release.createdBatchId }}: {{ formatDate(release.createdAt, 'MMMM D, YYYY HH:mm') }}</router-link> >{{ release.createdBatchId }}: {{ formatDate(release.createdAt, 'MMMM D, YYYY HH:mm') }}</RouterLink>
</div>
<div class="row">
<span class="row-label">Summary</span>
<div class="summary">
<input
ref="summary"
v-model="summary"
class="input"
@focus="selectSummary"
>
<button
v-if="hasClipboard"
type="button"
class="button button-secondary"
:disabled="summaryCopied"
@focus="copySummary"
>{{ summaryCopied ? 'Copied!' : 'Copy' }}</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import formatSummary from '../../js/utils/format-summary';
import Details from './details.vue'; import Details from './details.vue';
import Banner from './banner.vue'; import Banner from './banner.vue';
import StashButton from '../stashes/button.vue'; import StashButton from '../stashes/button.vue';
@ -266,6 +289,7 @@ async function fetchRelease(scroll = true) {
} }
this.stashedBy = this.release.stashes; this.stashedBy = this.release.stashes;
this.setSummary();
} }
async function stashScene(stashId) { async function stashScene(stashId) {
@ -288,6 +312,37 @@ function me() {
return this.$store.state.auth.user; return this.$store.state.auth.user;
} }
function setSummary() {
// this.summary = `${this.release.entity.name} - ${this.release.title} (${this.release.actors.map((actor) => actor.name).join(', ')}, ${this.formatDate(this.release.date, 'DD-MM-YYYY')})`;
const simpleRelease = {
channel: this.release.entity.name,
network: this.release.entity.parent?.name || this.release.entity.name,
title: this.release.title,
movie: this.release.movies?.[0]?.title,
actors: this.release.actors.map((actor) => actor.name),
tags: this.release.tags.map((tag) => tag.name),
date: this.release.date,
};
this.summary = formatSummary(simpleRelease, this.$store.state.ui.summaryFormat);
}
async function selectSummary() {
this.$refs.summary.select();
}
async function copySummary() {
const { state } = await navigator.permissions.query({ name: 'clipboard-write' });
if (state === 'granted' || state === 'prompt') {
await navigator.clipboard.writeText(this.summary);
this.summaryCopied = true;
setTimeout(() => { this.summaryCopied = false; }, 1000);
}
}
function bannerBackground() { function bannerBackground() {
return (this.release.poster && this.getBgPath(this.release.poster, 'thumbnail')) return (this.release.poster && this.getBgPath(this.release.poster, 'thumbnail'))
|| (this.release.covers.length > 0 && this.getBgPath(this.release.covers[0], 'thumbnail')); || (this.release.covers.length > 0 && this.getBgPath(this.release.covers[0], 'thumbnail'));
@ -318,7 +373,10 @@ export default {
data() { data() {
return { return {
release: null, release: null,
summary: null,
summaryCopied: false,
stashedBy: [], stashedBy: [],
hasClipboard: !!navigator?.clipboard?.writeText,
}; };
}, },
computed: { computed: {
@ -332,6 +390,9 @@ export default {
}, },
mounted: fetchRelease, mounted: fetchRelease,
methods: { methods: {
copySummary,
selectSummary,
setSummary,
fetchRelease, fetchRelease,
stashScene, stashScene,
unstashScene, unstashScene,
@ -502,6 +563,18 @@ export default {
margin: 0 0 -.15rem .1rem; margin: 0 0 -.15rem .1rem;
} }
.summary {
display: flex;
.input {
flex-grow: 1;
}
.button {
width: 4rem;
}
}
.link { .link {
display: inline-flex; display: inline-flex;
color: var(--link); color: var(--link);

View File

@ -0,0 +1,83 @@
<template>
<div class="dialog-section">
<h3 class="form-heading">Show me</h3>
<ul class="tags nolist">
<li
v-for="tag in tags"
:key="tag"
class="tags-item"
>
<Checkbox
:checked="!tagFilter.includes(tag)"
:label="tag"
class="tag"
@change="(state) => filterTag(tag, state)"
/>
</li>
</ul>
<p class="disclaimer">You may still incidentally see filtered out content</p>
</div>
</template>
<script>
import Checkbox from '../form/checkbox.vue';
function tagFilter() {
return this.$store.state.ui.tagFilter;
}
function filterTag(tag, isChecked) {
if (isChecked) {
this.$store.dispatch('setTagFilter', this.tagFilter.filter((filteredTag) => filteredTag !== tag));
} else {
this.$store.dispatch('setTagFilter', this.tagFilter.concat(tag));
}
}
export default {
components: {
Checkbox,
},
data() {
return {
tags: ['anal', 'gay', 'transsexual', 'bisexual', 'pissing', 'anal prolapse'],
};
},
computed: {
tagFilter,
},
methods: {
filterTag,
},
};
</script>
<style lang="scss" scoped>
.dialog-body {
width: 40rem;
max-width: 100%;
}
.filters {
width: 20rem;
max-width: 100%;
}
.tags-item {
display: block;
}
.tag {
padding: .5rem 0;
}
.disclaimer {
margin: 1rem 0 0 0;
line-height: 1.5;
text-align: center;
font-size: .9rem;
color: var(--shadow);
}
</style>

View File

@ -0,0 +1,81 @@
<template>
<Dialog
title="Settings"
@close="$emit('close')"
>
<nav class="tabs">
<button
class="tab"
:class="{ selected: section === 'filters' }"
@click="section = 'filters'"
>Filters</button>
<button
class="tab"
:class="{ selected: section === 'summary' }"
@click="section = 'summary'"
>Summary</button>
</nav>
<div class="dialog-body">
<component :is="sections[section]" />
</div>
</Dialog>
</template>
<script>
import { shallowRef } from 'vue';
import Filters from './filters.vue';
import Summary from './summary.vue';
export default {
emits: ['close'],
data() {
return {
sections: {
filters: shallowRef(Filters),
summary: shallowRef(Summary),
},
section: 'filters',
};
},
};
</script>
<style lang="scss" scoped>
.dialog-body {
width: 40rem;
max-width: 100%;
}
.tabs {
display: flex;
}
.tab {
flex-grow: 1;
padding: .75rem 1rem;
background: var(--shadow-touch);
border: none;
border-bottom: solid 1px var(--shadow-hint);
color: var(--shadow);
font-size: 1rem;
font-weight: bold;
&:not(:first-child) {
border-left: solid 1px var(--shadow-hint);
}
&.selected {
background: none;
color: var(--primary);
border-bottom: none;
}
&:hover {
color: var(--shadow-strong);
cursor: pointer;
}
}
</style>

View File

@ -0,0 +1,327 @@
<template>
<div>
<div class="dialog-section">
<h3 class="form-heading">Summary format</h3>
<ul class="summary nolist">
<li
v-for="(group, groupIndex) in summaryFormat"
:key="groupIndex"
class="summary-group"
>
<div class="summary-options">
<label class="summary-option">Delimiter
<input
class="input summary-delimiter"
:value="group.delimiter"
@change="setSummaryGroupValue(groupIndex, 'delimiter', $event.target.value)"
>
</label>
<label class="summary-option">Brackets
<select
class="select summary-delimiter"
:value="group.bracket"
@change="setSummaryGroupValue(groupIndex, 'bracket', $event.target.value)"
>
<option
v-for="bracket in brackets"
:key="bracket"
>{{ bracket }}</option>
</select>
</label>
</div>
<ul class="summary-segments nolist">
<li
v-for="(segment, segmentIndex) in group.segments"
:key="segmentIndex"
class="summary-segment"
>
<select
class="select summary-prop"
:value="segment.prop"
@change="setSummarySegmentValue(groupIndex, segmentIndex, 'prop', $event.target.value)"
>
<option>channel</option>
<option>network</option>
<option>title</option>
<option>movie</option>
<option>tags</option>
<option>actors</option>
<option>date</option>
</select>
<input
v-if="delimitedProps.includes(segment.prop)"
class="input summary-delimiter"
:value="segment.delimiter"
@change="setSummarySegmentValue(groupIndex, segmentIndex, 'delimiter', $event.target.value)"
>
<select
v-if="segment.prop === 'date'"
class="select summary-format"
:value="segment.format"
@change="setSummarySegmentValue(groupIndex, segmentIndex, 'format', $event.target.value)"
>
<option>YYYY-MM-DD</option>
<option>DD-MM-YYYY</option>
<option>MM/DD/YYYY</option>
</select>
<Icon
icon="bin"
class="active"
@click="removeSummarySegment(groupIndex, segmentIndex)"
/>
</li>
<li class="summary-actions">
<button
type="button"
class="button button-secondary"
@click="addSummarySegment(groupIndex)"
>Add segment</button>
<button
type="button"
class="button button-secondary"
@click="removeSummaryGroup(groupIndex)"
>Remove group</button>
</li>
</ul>
</li>
<li class="summary summary-actions">
<button
type="button"
class="button button-secondary"
@click="addSummaryGroup"
>Add group</button>
<button
type="button"
class="button button-secondary"
@click="resetSummaryFormat"
>Reset to default</button>
</li>
</ul>
</div>
<div class="dialog-section">
<h3 class="form-heading">Preview</h3>
<input
class="input summary-preview"
:value="summary"
:title="summary"
disabled
>
</div>
</div>
</template>
<script>
import formatSummary from '../../js/utils/format-summary';
function summary() {
return formatSummary(this.scene, this.summaryFormat);
}
function summaryFormat() {
return this.$store.state.ui.summaryFormat;
}
function setSummaryGroupValue(targetGroupIndex, target, value) {
const newFormat = this.summaryFormat.map((group, groupIndex) => {
if (groupIndex === targetGroupIndex) {
return {
...group,
[target]: value,
};
}
return group;
});
this.$store.dispatch('setSummaryFormat', newFormat);
}
function setSummarySegmentValue(targetGroupIndex, targetSegmentIndex, target, value) {
const newFormat = this.summaryFormat.map((group, groupIndex) => {
if (groupIndex === targetGroupIndex) {
return {
...group,
segments: group.segments.map((segment, segmentIndex) => {
if (segmentIndex === targetSegmentIndex) {
return {
...segment,
[target]: value,
};
}
return segment;
}),
};
}
return group;
});
this.$store.dispatch('setSummaryFormat', newFormat);
}
function addSummarySegment(targetGroupIndex) {
const newFormat = this.summaryFormat.map((group, groupIndex) => {
if (groupIndex === targetGroupIndex) {
return {
...group,
segments: group.segments.concat({
prop: 'title',
delimiter: ',', // default delimiter for when prop is changed to iterabte like actors or tags
format: 'YYYY-MM-DD', // default format for when prop is changed to date
}),
};
}
return group;
});
this.$store.dispatch('setSummaryFormat', newFormat);
}
function addSummaryGroup() {
const newFormat = this.summaryFormat.concat({
delimiter: ' - ',
segments: [{ prop: 'title' }],
});
this.$store.dispatch('setSummaryFormat', newFormat);
}
function removeSummaryGroup(groupIndex) {
const newFormat = this.summaryFormat.filter((group, index) => index !== groupIndex);
this.$store.dispatch('setSummaryFormat', newFormat);
}
function removeSummarySegment(targetGroupIndex, targetSegmentIndex) {
const newFormat = this.summaryFormat.map((group, groupIndex) => ({
...group,
segments: groupIndex === targetGroupIndex
? group.segments.filter((segment, index) => index !== targetSegmentIndex)
: group.segments,
}));
this.$store.dispatch('setSummaryFormat', newFormat);
}
function resetSummaryFormat() {
this.$store.dispatch('setSummaryFormat', this.$store.state.ui.defaultSummaryFormat);
}
export default {
data() {
return {
delimiters: [null, '-', '_', ',', '.'],
brackets: [null, '()', '[]', '{}', '<>'],
delimitedProps: ['actors', 'tags'],
scene: {
channel: 'Channel',
network: 'Network',
title: 'Title',
movie: 'Movie',
scene: 1,
actors: ['Jane Doe', 'John Doe'],
date: new Date(),
},
};
},
computed: {
summary,
summaryFormat,
},
methods: {
addSummaryGroup,
setSummaryGroupValue,
addSummarySegment,
setSummarySegmentValue,
removeSummaryGroup,
removeSummarySegment,
resetSummaryFormat,
},
};
</script>
<style lang="scss" scoped>
.summary {
.icon {
padding: .5rem;
}
}
.summary-group,
.summary-segment {
display: flex;
}
.summary-group {
display: flex;
flex-direction: column;
margin-bottom: .5rem;
.button {
margin-top: .5rem;
}
&:not(:last-child) {
padding-bottom: .5rem;
border-bottom: solid 1px var(--shadow-hint);
margin-bottom: .5rem;
}
}
.summary-segments {
width: 100%;
display: flex;
flex-direction: column;
}
.summary-segment {
display: flex;
align-items: center;
margin-bottom: .25rem;
}
.summary-delimiter {
width: 5rem;
}
.summary-format {
width: 9rem;
}
.summary-prop {
flex-grow: 1;
}
.summary-options {
margin-bottom: .5rem;
}
.summary-option:not(:last-child) {
margin-right: 1rem;
}
.summary-actions {
display: flex;
justify-content: space-between;
align-items: center;
}
.summary-preview {
width: 100%;
margin-bottom: 1rem;
}
</style>

View File

@ -1,7 +1,7 @@
<template> <template>
<div <div
class="sidebar-container" class="sidebar-container"
@click="$emit('toggleSidebar', false)" @click="events.emit('toggleSidebar', false)"
> >
<div <div
class="sidebar" class="sidebar"
@ -9,10 +9,10 @@
> >
<div class="sidebar-section"> <div class="sidebar-section">
<div class="sidebar-header"> <div class="sidebar-header">
<router-link <RouterLink
to="/updates" to="/updates"
class="logo-link" class="logo-link"
@click="$emit('toggleSidebar', false)" @click="events.emit('toggleSidebar', false)"
> >
<h1 class="sidebar-logo"> <h1 class="sidebar-logo">
<div <div
@ -20,27 +20,27 @@
v-html="logo" v-html="logo"
/> />
</h1> </h1>
</router-link> </RouterLink>
<Icon <Icon
icon="cross2" icon="cross2"
class="sidebar-close noselect" class="sidebar-close noselect"
@click.native="$emit('toggleSidebar', false)" @click.native="events.emit('toggleSidebar', false)"
/> />
</div> </div>
<Search <Search
class="search" class="search"
@search="$emit('toggleSidebar', false)" @search="events.emit('toggleSidebar', false)"
/> />
<nav class="nav"> <nav class="nav">
<ul class="nolist"> <ul class="nolist">
<li <li
class="nav-item" class="nav-item"
@click="$emit('toggleSidebar', false)" @click="events.emit('toggleSidebar', false)"
> >
<router-link <RouterLink
v-slot="{ href, isActive, navigate }" v-slot="{ href, isActive, navigate }"
to="/updates" to="/updates"
custom custom
@ -51,14 +51,14 @@
:class="{ active: isActive }" :class="{ active: isActive }"
@click="navigate" @click="navigate"
>Home</a> >Home</a>
</router-link> </RouterLink>
</li> </li>
<li <li
class="nav-item" class="nav-item"
@click="$emit('toggleSidebar', false)" @click="events.emit('toggleSidebar', false)"
> >
<router-link <RouterLink
v-slot="{ href, isActive, navigate }" v-slot="{ href, isActive, navigate }"
to="/actors" to="/actors"
custom custom
@ -69,14 +69,14 @@
:class="{ active: isActive }" :class="{ active: isActive }"
@click="navigate" @click="navigate"
>Actors</a> >Actors</a>
</router-link> </RouterLink>
</li> </li>
<li <li
class="nav-item" class="nav-item"
@click="$emit('toggleSidebar', false)" @click="events.emit('toggleSidebar', false)"
> >
<router-link <RouterLink
v-slot="{ href, isActive, navigate }" v-slot="{ href, isActive, navigate }"
to="/channels" to="/channels"
custom custom
@ -87,14 +87,14 @@
:class="{ active: isActive }" :class="{ active: isActive }"
@click="navigate" @click="navigate"
>Channels</a> >Channels</a>
</router-link> </RouterLink>
</li> </li>
<li <li
class="nav-item" class="nav-item"
@click="$emit('toggleSidebar', false)" @click="events.emit('toggleSidebar', false)"
> >
<router-link <RouterLink
v-slot="{ href, isActive, navigate }" v-slot="{ href, isActive, navigate }"
to="/movies" to="/movies"
custom custom
@ -105,14 +105,14 @@
:class="{ active: isActive }" :class="{ active: isActive }"
@click="navigate" @click="navigate"
>Movies</a> >Movies</a>
</router-link> </RouterLink>
</li> </li>
<li <li
class="nav-item" class="nav-item"
@click="$emit('toggleSidebar', false)" @click="events.emit('toggleSidebar', false)"
> >
<router-link <RouterLink
v-slot="{ href, isActive, navigate }" v-slot="{ href, isActive, navigate }"
to="/tags" to="/tags"
custom custom
@ -123,7 +123,7 @@
:class="{ active: isActive }" :class="{ active: isActive }"
@click="navigate" @click="navigate"
>Tags</a> >Tags</a>
</router-link> </RouterLink>
</li> </li>
</ul> </ul>
</nav> </nav>
@ -132,23 +132,23 @@
<div class="sidebar-section controls noselect"> <div class="sidebar-section controls noselect">
<label <label
v-if="login && me" v-if="login && me"
@click="$emit('toggleSidebar', false)" @click="events.emit('toggleSidebar', false)"
> >
<router-link <RouterLink
:to="{ name: 'user', params: { username: me.username } }" :to="{ name: 'user', params: { username: me.username } }"
class="toggle username nolink" class="toggle username nolink"
>{{ me.username }}</router-link> >{{ me.username }}</RouterLink>
</label> </label>
<div class="toggles noselect"> <div class="toggles noselect">
<label <label
v-if="login && !me" v-if="login && !me"
@click="$emit('toggleSidebar', false)" @click="events.emit('toggleSidebar', false)"
> >
<router-link <RouterLink
:to="{ name: 'login', query: { ref: $route.path } }" :to="{ name: 'login', query: { ref: $route.path } }"
class="toggle nolink" class="toggle nolink"
><Icon icon="enter2" />Log in</router-link> ><Icon icon="enter2" />Log in</RouterLink>
</label> </label>
<label <label
@ -183,8 +183,8 @@
<label <label
class="toggle" class="toggle"
@click="$emit('showFilters', true)" @click="events.emit('toggleSettings', true)"
><Icon icon="filter" />Filters</label> ><Icon icon="cog" />Settings</label>
</div> </div>
</div> </div>
</div> </div>
@ -230,7 +230,6 @@ export default {
components: { components: {
Search, Search,
}, },
emits: ['toggleSidebar', 'showFilters'],
data() { data() {
return { return {
logo, logo,

View File

@ -54,10 +54,15 @@
.button-secondary { .button-secondary {
color: var(--primary); color: var(--primary);
&:hover { &:hover:not(:disabled) {
color: var(--highlight-strong); color: var(--text-light);
background: var(--primary); background: var(--primary);
} }
&:disabled {
color: var(--shadow-strong);
cursor: default;
}
} }
.album-toggle { .album-toggle {

View File

@ -10,6 +10,11 @@ function initUiActions(store, _router) {
localStorage.setItem('tagFilter', tagFilter); localStorage.setItem('tagFilter', tagFilter);
} }
function setSummaryFormat({ commit }, summaryFormat) {
commit('setSummaryFormat', summaryFormat);
localStorage.setItem('summaryFormat', JSON.stringify(summaryFormat));
}
function setRange({ commit }, range) { function setRange({ commit }, range) {
commit('setRange', range); commit('setRange', range);
} }
@ -309,6 +314,7 @@ function initUiActions(store, _router) {
checkNotifications, checkNotifications,
search, search,
setTagFilter, setTagFilter,
setSummaryFormat,
setRange, setRange,
setBatch, setBatch,
setSfw, setSfw,

View File

@ -2,6 +2,10 @@ function setTagFilter(state, tagFilter) {
state.tagFilter = tagFilter; state.tagFilter = tagFilter;
} }
function setSummaryFormat(state, summaryFormat) {
state.summaryFormat = summaryFormat;
}
function setRange(state, range) { function setRange(state, range) {
state.range = range; state.range = range;
} }
@ -20,6 +24,7 @@ function setTheme(state, theme) {
export default { export default {
setTagFilter, setTagFilter,
setSummaryFormat,
setRange, setRange,
setBatch, setBatch,
setSfw, setSfw,

View File

@ -1,12 +1,43 @@
const storedTagFilter = localStorage.getItem('tagFilter'); const storedTagFilter = localStorage.getItem('tagFilter');
const storedSummaryFormat = localStorage.getItem('summaryFormat');
const storedBatch = localStorage.getItem('batch'); const storedBatch = localStorage.getItem('batch');
const storedSfw = localStorage.getItem('sfw'); const storedSfw = localStorage.getItem('sfw');
const storedTheme = localStorage.getItem('theme'); const storedTheme = localStorage.getItem('theme');
const deviceTheme = window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'; const deviceTheme = window.matchMedia?.('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
const defaultSummaryFormat = [
{
delimiter: ' - ',
segments: [
{
prop: 'channel',
},
{
prop: 'title',
},
],
},
{
delimiter: ', ',
bracket: '()',
segments: [
{
prop: 'actors',
delimiter: ', ',
},
{
prop: 'date',
format: 'DD-MM-YYYY',
},
],
},
];
export default { export default {
tagFilter: storedTagFilter ? storedTagFilter.split(',') : [], tagFilter: storedTagFilter ? storedTagFilter.split(',') : [],
summaryFormat: storedSummaryFormat ? JSON.parse(storedSummaryFormat) : defaultSummaryFormat,
defaultSummaryFormat,
range: 'latest', range: 'latest',
batch: storedBatch || 'all', batch: storedBatch || 'all',
sfw: storedSfw === 'true' || false, sfw: storedSfw === 'true' || false,

View File

@ -0,0 +1,36 @@
import { formatDate } from '../format';
function formatSummary(release, summaryFormat) {
return summaryFormat
.map((group) => ({
...group,
segments: group.segments
.filter((segment) => {
if (!release[segment.prop]) {
return false;
}
if (Array.isArray(release[segment.prop]) && release[segment.prop].length === 0) {
return false;
}
return true;
})
.map((segment) => {
if (Array.isArray(release[segment.prop])) {
return release[segment.prop].join(segment.delimiter || ', ');
}
if (segment.prop === 'date') {
return formatDate(release[segment.prop], segment.format || 'YYYY-MM-DD');
}
return release[segment.prop];
}),
}))
.filter((group) => group.segments.length > 0)
.map((group) => `${group.bracket?.[0] || ''}${group.segments.join(group.delimiter || ' - ')}${group.bracket?.[1] || ''}`)
.join(' ');
}
export default formatSummary;

View File

@ -419,7 +419,7 @@ async function scrapeScene({ query }, url, channel, baseRelease, mobileItem, opt
return release; return release;
} }
async function scrapeReleaseApi(data, site, options) { async function scrapeReleaseApi(data, site, options, movieScenes) {
const release = {}; const release = {};
release.entryId = data.clip_id || data.movie_id; release.entryId = data.clip_id || data.movie_id;
@ -470,6 +470,10 @@ async function scrapeReleaseApi(data, site, options) {
}; };
} }
if (movieScenes?.length > 0) {
release.scenes = await Promise.all(movieScenes.map((movieScene) => scrapeReleaseApi(movieScene, site, options)));
}
release.channel = data.sitename; release.channel = data.sitename;
release.qualities = data.download_sizes; release.qualities = data.download_sizes;
@ -706,10 +710,16 @@ async function fetchMovieApi(url, site, baseRelease, options) {
indexName: 'all_movies', indexName: 'all_movies',
params: `query=&page=0&facets=[]&tagFilters=&facetFilters=[["movie_id:${entryId}"]]`, params: `query=&page=0&facets=[]&tagFilters=&facetFilters=[["movie_id:${entryId}"]]`,
}, },
{
indexName: 'all_scenes_latest_desc',
params: `query=&page=0&facets=[]&tagFilters=&facetFilters=[["movie_id:${entryId}"]]`,
},
/*
{ {
indexName: 'all_movies', indexName: 'all_movies',
params: 'query=&page=0&hitsPerPage=1&attributesToRetrieve=[]&attributesToHighlight=[]&attributesToSnippet=[]&tagFilters=&analytics=false&clickAnalytics=false&facets=clip_id', params: 'query=&page=0&hitsPerPage=1&attributesToRetrieve=[]&attributesToHighlight=[]&attributesToSnippet=[]&tagFilters=&analytics=false&clickAnalytics=false&facets=clip_id',
}, },
*/
], ],
}, { }, {
headers: { headers: {
@ -720,7 +730,7 @@ async function fetchMovieApi(url, site, baseRelease, options) {
}); });
if (res.status === 200 && res.body.results?.[0]?.hits.length > 0) { if (res.status === 200 && res.body.results?.[0]?.hits.length > 0) {
return scrapeReleaseApi(res.body.results[0].hits[0], site, options); return scrapeReleaseApi(res.body.results[0].hits[0], site, options, res.body.results[1]?.hits);
} }
if (res.status === 200) { if (res.status === 200) {