mirror of https://github.com/FreeTubeApp/FreeTube
Merge 8ab7e3f727
into a94910df27
This commit is contained in:
commit
66cf8e2058
|
@ -64,6 +64,7 @@
|
|||
"lodash.debounce": "^4.0.8",
|
||||
"marked": "^12.0.2",
|
||||
"path-browserify": "^1.0.1",
|
||||
"portal-vue": "^2.1.7",
|
||||
"process": "^0.11.10",
|
||||
"swiper": "^11.1.1",
|
||||
"video.js": "7.21.5",
|
||||
|
|
|
@ -11,6 +11,7 @@ import FtToast from './components/ft-toast/ft-toast.vue'
|
|||
import FtProgressBar from './components/ft-progress-bar/ft-progress-bar.vue'
|
||||
import FtPlaylistAddVideoPrompt from './components/ft-playlist-add-video-prompt/ft-playlist-add-video-prompt.vue'
|
||||
import FtCreatePlaylistPrompt from './components/ft-create-playlist-prompt/ft-create-playlist-prompt.vue'
|
||||
import FtSearchFilters from './components/ft-search-filters/ft-search-filters.vue'
|
||||
import { marked } from 'marked'
|
||||
import { IpcChannels } from '../constants'
|
||||
import packageDetails from '../../package.json'
|
||||
|
@ -34,6 +35,7 @@ export default defineComponent({
|
|||
FtProgressBar,
|
||||
FtPlaylistAddVideoPrompt,
|
||||
FtCreatePlaylistPrompt,
|
||||
FtSearchFilters
|
||||
},
|
||||
data: function () {
|
||||
return {
|
||||
|
@ -46,6 +48,7 @@ export default defineComponent({
|
|||
latestBlogUrl: '',
|
||||
updateChangelog: '',
|
||||
changeLogTitle: '',
|
||||
isPromptOpen: false,
|
||||
lastExternalLinkToBeOpened: '',
|
||||
showExternalLinkOpeningPrompt: false,
|
||||
externalLinkOpeningPromptValues: [
|
||||
|
@ -77,6 +80,9 @@ export default defineComponent({
|
|||
showCreatePlaylistPrompt: function () {
|
||||
return this.$store.getters.getShowCreatePlaylistPrompt
|
||||
},
|
||||
showSearchFilters: function () {
|
||||
return this.$store.getters.getShowSearchFilters
|
||||
},
|
||||
windowTitle: function () {
|
||||
const routePath = this.$route.path
|
||||
if (!routePath.startsWith('/channel/') && !routePath.startsWith('/watch/') && !routePath.startsWith('/hashtag/')) {
|
||||
|
@ -143,12 +149,6 @@ export default defineComponent({
|
|||
secColor: 'checkThemeSettings',
|
||||
|
||||
locale: 'setLocale',
|
||||
|
||||
$route () {
|
||||
// react to route changes...
|
||||
// Hide top nav filter panel on page change
|
||||
this.$refs.topNav?.hideFilters()
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.checkThemeSettings()
|
||||
|
@ -295,6 +295,10 @@ export default defineComponent({
|
|||
this.showBlogBanner = false
|
||||
},
|
||||
|
||||
handlePromptPortalUpdate: function(newVal) {
|
||||
this.isPromptOpen = newVal
|
||||
},
|
||||
|
||||
openDownloadsPage: function () {
|
||||
const url = 'https://freetubeapp.io#download'
|
||||
openExternalLink(url)
|
||||
|
@ -544,7 +548,7 @@ export default defineComponent({
|
|||
'updateMainColor',
|
||||
'updateSecColor',
|
||||
'showOutlines',
|
||||
'hideOutlines'
|
||||
'hideOutlines',
|
||||
])
|
||||
}
|
||||
})
|
||||
|
|
|
@ -8,11 +8,63 @@
|
|||
isLocaleRightToLeft: isLocaleRightToLeft
|
||||
}"
|
||||
>
|
||||
<top-nav ref="topNav" />
|
||||
<side-nav ref="sideNav" />
|
||||
<portal-target
|
||||
name="promptPortal"
|
||||
@change="handlePromptPortalUpdate"
|
||||
/>
|
||||
<ft-prompt
|
||||
v-if="showReleaseNotes"
|
||||
:label="changeLogTitle"
|
||||
@click="showReleaseNotes = !showReleaseNotes"
|
||||
>
|
||||
<span
|
||||
class="changeLogText"
|
||||
v-html="updateChangelog"
|
||||
/>
|
||||
<ft-flex-box>
|
||||
<ft-button
|
||||
:label="$t('Download From Site')"
|
||||
@click="openDownloadsPage"
|
||||
/>
|
||||
<ft-button
|
||||
:label="$t('Close')"
|
||||
@click="showReleaseNotes = !showReleaseNotes"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
</ft-prompt>
|
||||
<ft-prompt
|
||||
v-if="showExternalLinkOpeningPrompt"
|
||||
:label="$t('Are you sure you want to open this link?')"
|
||||
:extra-labels="[lastExternalLinkToBeOpened]"
|
||||
:option-names="externalLinkOpeningPromptNames"
|
||||
:option-values="externalLinkOpeningPromptValues"
|
||||
@click="handleExternalLinkOpeningPromptAnswer"
|
||||
/>
|
||||
<ft-search-filters
|
||||
v-if="showSearchFilters"
|
||||
/>
|
||||
<ft-playlist-add-video-prompt
|
||||
v-if="showAddToPlaylistPrompt"
|
||||
/>
|
||||
<ft-create-playlist-prompt
|
||||
v-if="showCreatePlaylistPrompt"
|
||||
/>
|
||||
<ft-toast />
|
||||
<ft-progress-bar
|
||||
v-if="showProgressBar"
|
||||
/>
|
||||
<top-nav
|
||||
ref="topNav"
|
||||
:inert="isPromptOpen"
|
||||
/>
|
||||
<side-nav
|
||||
ref="sideNav"
|
||||
:inert="isPromptOpen"
|
||||
/>
|
||||
<ft-flex-box
|
||||
class="flexBox routerView"
|
||||
role="main"
|
||||
:inert="isPromptOpen"
|
||||
>
|
||||
<div
|
||||
v-if="showUpdatesBanner || showBlogBanner"
|
||||
|
@ -43,48 +95,9 @@
|
|||
ref="router"
|
||||
class="routerView"
|
||||
/>
|
||||
<!-- </keep-alive> -->
|
||||
<!-- </keep-alive> -->
|
||||
</transition>
|
||||
</ft-flex-box>
|
||||
|
||||
<ft-prompt
|
||||
v-if="showReleaseNotes"
|
||||
:label="changeLogTitle"
|
||||
@click="showReleaseNotes = !showReleaseNotes"
|
||||
>
|
||||
<span
|
||||
class="changeLogText"
|
||||
v-html="updateChangelog"
|
||||
/>
|
||||
<ft-flex-box>
|
||||
<ft-button
|
||||
:label="$t('Download From Site')"
|
||||
@click="openDownloadsPage"
|
||||
/>
|
||||
<ft-button
|
||||
:label="$t('Close')"
|
||||
@click="showReleaseNotes = !showReleaseNotes"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
</ft-prompt>
|
||||
<ft-prompt
|
||||
v-if="showExternalLinkOpeningPrompt"
|
||||
:label="$t('Are you sure you want to open this link?')"
|
||||
:extra-labels="[lastExternalLinkToBeOpened]"
|
||||
:option-names="externalLinkOpeningPromptNames"
|
||||
:option-values="externalLinkOpeningPromptValues"
|
||||
@click="handleExternalLinkOpeningPromptAnswer"
|
||||
/>
|
||||
<ft-playlist-add-video-prompt
|
||||
v-if="showAddToPlaylistPrompt"
|
||||
/>
|
||||
<ft-create-playlist-prompt
|
||||
v-if="showCreatePlaylistPrompt"
|
||||
/>
|
||||
<ft-toast />
|
||||
<ft-progress-bar
|
||||
v-if="showProgressBar"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { defineComponent } from 'vue'
|
||||
import { defineComponent, nextTick } from 'vue'
|
||||
import { mapActions } from 'vuex'
|
||||
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
|
||||
import FtPrompt from '../ft-prompt/ft-prompt.vue'
|
||||
|
@ -24,6 +24,9 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
title: function () {
|
||||
return this.$t('User Playlists.CreatePlaylistPrompt.New Playlist Name')
|
||||
},
|
||||
allPlaylists: function () {
|
||||
return this.$store.getters.getAllPlaylists
|
||||
},
|
||||
|
@ -34,7 +37,7 @@ export default defineComponent({
|
|||
mounted: function () {
|
||||
this.playlistName = this.newPlaylistVideoObject.title
|
||||
// Faster to input required playlist name
|
||||
this.$refs.playlistNameInput.focus()
|
||||
nextTick(() => this.$refs.playlistNameInput.focus())
|
||||
},
|
||||
methods: {
|
||||
createNewPlaylist: function () {
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
<template>
|
||||
<ft-prompt
|
||||
:label="title"
|
||||
@click="hideCreatePlaylistPrompt"
|
||||
>
|
||||
<h2 class="center">
|
||||
{{ $t('User Playlists.CreatePlaylistPrompt.New Playlist Name') }}
|
||||
{{ title }}
|
||||
</h2>
|
||||
<ft-flex-box>
|
||||
<ft-input
|
||||
|
|
|
@ -6,16 +6,6 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
/* Style for `ft-prompt` */
|
||||
:deep(.promptCard) {
|
||||
/* Currently only this prompt has enough content to make prompt too high */
|
||||
max-block-size: 95%;
|
||||
|
||||
/* Some child(s) will grow vertically */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.searchInputsRow {
|
||||
display: grid;
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { defineComponent } from 'vue'
|
||||
import { defineComponent, nextTick } from 'vue'
|
||||
import { mapActions } from 'vuex'
|
||||
import debounce from 'lodash.debounce'
|
||||
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
|
||||
|
@ -47,6 +47,12 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
title: function () {
|
||||
return this.$tc('User Playlists.AddVideoPrompt.Select a playlist to add your N videos to', this.toBeAddedToPlaylistVideoCount, {
|
||||
videoCount: this.toBeAddedToPlaylistVideoCount,
|
||||
})
|
||||
},
|
||||
|
||||
showingCreatePlaylistPrompt: function () {
|
||||
return this.$store.getters.getShowCreatePlaylistPrompt
|
||||
},
|
||||
|
@ -183,7 +189,7 @@ export default defineComponent({
|
|||
if (val > oldVal) {
|
||||
// Focus back to search input only when playlist added
|
||||
// Allow search and easier deselecting new created playlist
|
||||
this.$refs.searchBar.focus()
|
||||
nextTick(() => this.$refs.searchBar.focus())
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -193,7 +199,7 @@ export default defineComponent({
|
|||
// Only care when CreatePlaylistPrompt hidden
|
||||
// Shift focus from button to prevent unwanted click event
|
||||
// due to enter key press in CreatePlaylistPrompt
|
||||
this.$refs.searchBar.focus()
|
||||
nextTick(() => this.$refs.searchBar.focus())
|
||||
},
|
||||
},
|
||||
mounted: function () {
|
||||
|
@ -201,10 +207,10 @@ export default defineComponent({
|
|||
|
||||
this.updateQueryDebounce = debounce(this.updateQuery, 500)
|
||||
// User might want to search first if they have many playlists
|
||||
this.$refs.searchBar.focus()
|
||||
nextTick(() => this.$refs.searchBar.focus())
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.lastActiveElement?.focus()
|
||||
nextTick(() => this.lastActiveElement?.focus())
|
||||
},
|
||||
methods: {
|
||||
hide: function () {
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<ft-prompt
|
||||
theme="flex-column"
|
||||
:label="title"
|
||||
@click="hide"
|
||||
>
|
||||
<h2 class="heading">
|
||||
{{ $tc('User Playlists.AddVideoPrompt.Select a playlist to add your N videos to', toBeAddedToPlaylistVideoCount, {
|
||||
videoCount: toBeAddedToPlaylistVideoCount,
|
||||
}) }}
|
||||
{{ title }}
|
||||
</h2>
|
||||
<p class="selected-count">
|
||||
{{ $tc('User Playlists.AddVideoPrompt.N playlists selected', selectedPlaylistCount, {
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
background-color: rgb(0 0 0 / 70%);
|
||||
|
||||
/* Higher than components like playlist info */
|
||||
z-index: 200;
|
||||
z-index: 201;
|
||||
padding: 15px;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
|
@ -17,6 +17,7 @@
|
|||
|
||||
.promptCard {
|
||||
overflow-y: scroll;
|
||||
max-block-size: 95%;
|
||||
}
|
||||
|
||||
.promptCard.autosize {
|
||||
|
@ -34,6 +35,31 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.promptCard.flex-column {
|
||||
/* Some child(ren) will grow vertically */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.promptCard.slim {
|
||||
max-inline-size: 70%;
|
||||
inset-inline-start: 15%;
|
||||
padding-block: 10px;
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media only screen and (width <= 680px) {
|
||||
.promptCard.slim {
|
||||
padding-block: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (width <= 500px) {
|
||||
.promptCard.slim {
|
||||
max-inline-size: 80%;
|
||||
inset-inline-start: 10%;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ export default defineComponent({
|
|||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
required: true
|
||||
},
|
||||
extraLabels: {
|
||||
type: Array,
|
||||
|
@ -36,6 +36,10 @@ export default defineComponent({
|
|||
autosize: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
theme: {
|
||||
type: String,
|
||||
default: 'base'
|
||||
}
|
||||
},
|
||||
emits: ['click'],
|
||||
|
@ -56,15 +60,16 @@ export default defineComponent({
|
|||
},
|
||||
mounted: function () {
|
||||
this.lastActiveElement = document.activeElement
|
||||
|
||||
document.addEventListener('keydown', this.closeEventFunction, true)
|
||||
document.querySelector('.prompt').addEventListener('keydown', this.arrowKeys, true)
|
||||
this.promptButtons = Array.from(
|
||||
document.querySelector('.prompt .promptCard .ft-flex-box').childNodes
|
||||
).filter((e) => {
|
||||
return e.id && e.id.startsWith('prompt')
|
||||
this.$nextTick(() => {
|
||||
document.addEventListener('keydown', this.closeEventFunction, true)
|
||||
document.querySelector('.prompt').addEventListener('keydown', this.arrowKeys, true)
|
||||
this.promptButtons = Array.from(
|
||||
document.querySelector('.prompt .promptCard .ft-flex-box').childNodes
|
||||
).filter((e) => {
|
||||
return e.id && e.id.startsWith('prompt')
|
||||
})
|
||||
this.focusItem(0)
|
||||
})
|
||||
this.focusItem(0)
|
||||
},
|
||||
methods: {
|
||||
click: function (value) {
|
||||
|
|
|
@ -1,52 +1,57 @@
|
|||
<template>
|
||||
<div
|
||||
class="prompt"
|
||||
tabindex="-1"
|
||||
@click="handleHide"
|
||||
@keydown.enter="handleHide"
|
||||
>
|
||||
<ft-card
|
||||
class="promptCard"
|
||||
:class="{ autosize }"
|
||||
:aria-labelledby="('dialog-' + sanitizedLabel)"
|
||||
<portal to="promptPortal">
|
||||
<div
|
||||
ref="openPrompt"
|
||||
class="prompt"
|
||||
tabindex="-1"
|
||||
@click="handleHide"
|
||||
@keydown.enter="handleHide"
|
||||
>
|
||||
<slot>
|
||||
<h2
|
||||
:id="'dialog-' + sanitizedLabel"
|
||||
class="center"
|
||||
>
|
||||
{{ label }}
|
||||
</h2>
|
||||
<p
|
||||
v-for="extraLabel in extraLabels"
|
||||
:key="extraLabel"
|
||||
class="center"
|
||||
>
|
||||
<strong>
|
||||
{{ extraLabel }}
|
||||
</strong>
|
||||
</p>
|
||||
<ft-flex-box>
|
||||
<ft-button
|
||||
v-for="(option, index) in optionNames"
|
||||
:id="'prompt-' + sanitizedLabel + '-' + index"
|
||||
:key="index"
|
||||
:label="option"
|
||||
@click="click(optionValues[index])"
|
||||
/>
|
||||
<ft-button
|
||||
v-if="showClose"
|
||||
:id="'prompt-' + sanitizedLabel + '-close'"
|
||||
:label="$t('Close')"
|
||||
:tabindex="0"
|
||||
text-color="'var(--accent-color)'"
|
||||
background-color="'var(--text-with-accent-color)'"
|
||||
@click="hide"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
</slot>
|
||||
</ft-card>
|
||||
</div>
|
||||
<ft-card
|
||||
class="promptCard"
|
||||
:class="{ autosize, [theme]: true }"
|
||||
role="dialog"
|
||||
aria-modal="true"
|
||||
:aria-labelledby="('dialog-' + sanitizedLabel)"
|
||||
>
|
||||
<slot>
|
||||
<h2
|
||||
:id="'dialog-' + sanitizedLabel"
|
||||
class="center"
|
||||
>
|
||||
{{ label }}
|
||||
</h2>
|
||||
<p
|
||||
v-for="extraLabel in extraLabels"
|
||||
:key="extraLabel"
|
||||
class="center"
|
||||
>
|
||||
<strong>
|
||||
{{ extraLabel }}
|
||||
</strong>
|
||||
</p>
|
||||
<ft-flex-box>
|
||||
<ft-button
|
||||
v-for="(option, index) in optionNames"
|
||||
:id="'prompt-' + sanitizedLabel + '-' + index"
|
||||
:key="index"
|
||||
:label="option"
|
||||
@click="click(optionValues[index])"
|
||||
/>
|
||||
<ft-button
|
||||
v-if="showClose"
|
||||
:id="'prompt-' + sanitizedLabel + '-close'"
|
||||
:label="$t('Close')"
|
||||
:tabindex="0"
|
||||
text-color="'var(--accent-color)'"
|
||||
background-color="'var(--text-with-accent-color)'"
|
||||
@click="hide"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
</slot>
|
||||
</ft-card>
|
||||
</div>
|
||||
</portal>
|
||||
</template>
|
||||
|
||||
<script src="./ft-prompt.js" />
|
||||
|
|
|
@ -11,7 +11,7 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"],
|
|||
}
|
||||
|
||||
.pure-checkbox input[type="checkbox"]:focus + label::before, .pure-radiobutton input[type="checkbox"]:focus + label::before, .pure-checkbox input[type="radio"]:focus + label::before, .pure-radiobutton input[type="radio"]:focus + label::before, .pure-checkbox input[type="checkbox"]:hover + label::before, .pure-radiobutton input[type="checkbox"]:hover + label::before, .pure-checkbox input[type="radio"]:hover + label::before, .pure-radiobutton input[type="radio"]:hover + label::before {
|
||||
border-color: var(--primary-color);
|
||||
border: 2px solid var(--primary-color);
|
||||
}
|
||||
|
||||
.pure-checkbox input[type="checkbox"]:active + label::before, .pure-radiobutton input[type="checkbox"]:active + label::before, .pure-checkbox input[type="radio"]:active + label::before, .pure-radiobutton input[type="radio"]:active + label::before { transition-duration: 0s; }
|
||||
|
@ -22,20 +22,20 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"],
|
|||
user-select: none;
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin-block-end: -20px;
|
||||
margin-block-start: 10px;
|
||||
}
|
||||
|
||||
.pure-checkbox input[type="checkbox"] + label::before, .pure-radiobutton input[type="checkbox"] + label::before, .pure-checkbox input[type="radio"] + label::before, .pure-radiobutton input[type="radio"] + label::before {
|
||||
box-sizing: content-box;
|
||||
content: '';
|
||||
color: var(--primary-color);
|
||||
color: var(--primary-text-color);
|
||||
position: absolute;
|
||||
inset-block-start: 50%;
|
||||
inset-inline-start: 0;
|
||||
inline-size: 14px;
|
||||
block-size: 14px;
|
||||
margin-block-start: -9px;
|
||||
border: 2px solid var(--primary-color);
|
||||
border: 2px solid var(--primary-text-color);
|
||||
text-align: center;
|
||||
transition: all 0.4s ease;
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"],
|
|||
.pure-checkbox input[type="checkbox"] + label::after, .pure-radiobutton input[type="checkbox"] + label::after, .pure-checkbox input[type="radio"] + label::after, .pure-radiobutton input[type="radio"] + label::after {
|
||||
box-sizing: content-box;
|
||||
content: '';
|
||||
background-color: var(--primary-color);
|
||||
background-color: var(--primary-text-color);
|
||||
position: absolute;
|
||||
inset-block-start: 50%;
|
||||
inset-inline-start: 4px;
|
||||
|
@ -84,23 +84,33 @@ pure-checkbox input[type="checkbox"], .pure-radiobutton input[type="checkbox"],
|
|||
animation: borderscale 300ms ease-in;
|
||||
}
|
||||
|
||||
.pure-radiobutton input[type="radio"]:focus + label::after{
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.pure-checkbox input[type="radio"]:checked + label::after, .pure-radiobutton input[type="radio"]:checked + label::after { transform: scale(1); }
|
||||
|
||||
.pure-checkbox input[type="radio"] + label::before, .pure-radiobutton input[type="radio"] + label::before, .pure-checkbox input[type="radio"] + label::after, .pure-radiobutton input[type="radio"] + label::after { border-radius: 50%; }
|
||||
|
||||
.pure-checkbox input[type="checkbox"]:checked + label::before, .pure-radiobutton input[type="checkbox"]:checked + label::before {
|
||||
animation: borderscale 200ms ease-in;
|
||||
background: var(--primary-color);
|
||||
background: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.pure-radiobutton input[type="checkbox"]:checked + label::after { transform: rotate(-45deg) scale(1); }
|
||||
|
||||
@keyframes
|
||||
borderscale { 50% {
|
||||
box-shadow: 0 0 0 2px var(--primary-color);
|
||||
box-shadow: 0 0 0 2px var(--primary-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
.radioTitle {
|
||||
margin-block-end: -20px;
|
||||
margin-block: 0;
|
||||
}
|
||||
|
||||
@media only screen and (width <= 680px) {
|
||||
.pure-checkbox input[type="checkbox"] + label, .pure-radiobutton input[type="checkbox"] + label, .pure-checkbox input[type="radio"] + label, .pure-radiobutton input[type="radio"] + label {
|
||||
margin-block-start: 3px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,10 @@ export default defineComponent({
|
|||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
initialValueIndex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
emits: ['change'],
|
||||
|
@ -35,7 +39,7 @@ export default defineComponent({
|
|||
},
|
||||
mounted: function () {
|
||||
this.id = this._uid
|
||||
this.selectedValue = this.values[0]
|
||||
this.selectedValue = this.values[this.initialValueIndex]
|
||||
},
|
||||
methods: {
|
||||
updateSelectedValue: function (value) {
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
v-model="selectedValue"
|
||||
:name="inputName"
|
||||
:value="values[index]"
|
||||
:checked="index === 0"
|
||||
:checked="index === initialValueIndex"
|
||||
:disabled="disabled"
|
||||
class="radio"
|
||||
type="radio"
|
||||
|
|
|
@ -1,17 +1,7 @@
|
|||
.searchFilterInner {
|
||||
max-inline-size: 800px;
|
||||
margin-inline: auto;
|
||||
padding-block: 20px 70px;
|
||||
padding-inline: 20px;
|
||||
max-block-size: 410px;
|
||||
overflow-y: auto;
|
||||
background-color: var(--card-bg-color);
|
||||
box-shadow: 0 1px 2px rgb(0 0 0 / 10%);
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.center {
|
||||
margin-block: 5px 10px;
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.searchRadio {
|
||||
|
@ -19,13 +9,22 @@
|
|||
}
|
||||
|
||||
.radioFlexBox {
|
||||
max-inline-size: 1000px;
|
||||
margin-block: 0;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
@media only screen and (width <= 600px) {
|
||||
.searchFilterCloseButtonContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media only screen and (width <= 680px) {
|
||||
.searchRadio {
|
||||
border-inline-end: 0;
|
||||
padding-block-start: 0;
|
||||
}
|
||||
|
||||
.radioFlexBox {
|
||||
flex-flow: column;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,24 @@
|
|||
import { defineComponent } from 'vue'
|
||||
import { mapActions } from 'vuex'
|
||||
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
|
||||
import FtRadioButton from '../ft-radio-button/ft-radio-button.vue'
|
||||
import FtPrompt from '../ft-prompt/ft-prompt.vue'
|
||||
import FtButton from '../ft-button/ft-button.vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'FtSearchFilters',
|
||||
components: {
|
||||
'ft-flex-box': FtFlexBox,
|
||||
'ft-radio-button': FtRadioButton
|
||||
'ft-radio-button': FtRadioButton,
|
||||
'ft-prompt': FtPrompt,
|
||||
'ft-button': FtButton
|
||||
},
|
||||
emits: ['filterValueUpdated'],
|
||||
data: function () {
|
||||
return {
|
||||
searchSortByStartIndex: 0,
|
||||
searchTimeStartIndex: 0,
|
||||
searchTypeStartIndex: 0,
|
||||
searchDurationStartIndex: 0,
|
||||
sortByValues: [
|
||||
'relevance',
|
||||
'rating',
|
||||
|
@ -41,11 +49,15 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
computed: {
|
||||
title: function () {
|
||||
return this.$t('Search Filters.Search Filters')
|
||||
},
|
||||
|
||||
searchSettings: function () {
|
||||
return this.$store.getters.getSearchSettings
|
||||
},
|
||||
|
||||
filterValueChanged: function() {
|
||||
searchFilterValueChanged: function() {
|
||||
return [
|
||||
this.$refs.sortByRadio.selectedValue !== this.sortByValues[0],
|
||||
this.$refs.timeRadio.selectedValue !== this.timeValues[0],
|
||||
|
@ -93,6 +105,12 @@ export default defineComponent({
|
|||
]
|
||||
}
|
||||
},
|
||||
created: function () {
|
||||
this.searchSortByStartIndex = this.sortByValues.indexOf(this.searchSettings.sortBy)
|
||||
this.searchTimeStartIndex = this.timeValues.indexOf(this.searchSettings.time)
|
||||
this.searchTypeStartIndex = this.typeValues.indexOf(this.searchSettings.type)
|
||||
this.searchDurationStartIndex = this.durationValues.indexOf(this.searchSettings.duration)
|
||||
},
|
||||
methods: {
|
||||
isVideoOrMovieOrAll(type) {
|
||||
return type === 'video' || type === 'movie' || type === 'all'
|
||||
|
@ -100,7 +118,7 @@ export default defineComponent({
|
|||
|
||||
updateSortBy: function (value) {
|
||||
this.$store.commit('setSearchSortBy', value)
|
||||
this.$emit('filterValueUpdated', this.filterValueChanged)
|
||||
this.$store.commit('setSearchFilterValueChanged', this.searchFilterValueChanged)
|
||||
},
|
||||
|
||||
updateTime: function (value) {
|
||||
|
@ -110,7 +128,7 @@ export default defineComponent({
|
|||
this.$store.commit('setSearchType', 'all')
|
||||
}
|
||||
this.$store.commit('setSearchTime', value)
|
||||
this.$emit('filterValueUpdated', this.filterValueChanged)
|
||||
this.$store.commit('setSearchFilterValueChanged', this.searchFilterValueChanged)
|
||||
},
|
||||
|
||||
updateType: function (value) {
|
||||
|
@ -126,7 +144,7 @@ export default defineComponent({
|
|||
this.$store.commit('setSearchSortBy', this.sortByValues[0])
|
||||
}
|
||||
this.$store.commit('setSearchType', value)
|
||||
this.$emit('filterValueUpdated', this.filterValueChanged)
|
||||
this.$store.commit('setSearchFilterValueChanged', this.searchFilterValueChanged)
|
||||
},
|
||||
|
||||
updateDuration: function (value) {
|
||||
|
@ -136,7 +154,11 @@ export default defineComponent({
|
|||
this.updateType('all')
|
||||
}
|
||||
this.$store.commit('setSearchDuration', value)
|
||||
this.$emit('filterValueUpdated', this.filterValueChanged)
|
||||
}
|
||||
this.$store.commit('setSearchFilterValueChanged', this.searchFilterValueChanged)
|
||||
},
|
||||
|
||||
...mapActions([
|
||||
'hideSearchFilters'
|
||||
])
|
||||
}
|
||||
})
|
||||
|
|
|
@ -1,8 +1,15 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="searchFilterInner">
|
||||
<h2 class="center">
|
||||
{{ $t("Search Filters.Search Filters") }}
|
||||
<ft-prompt
|
||||
theme="slim"
|
||||
:label="title"
|
||||
@click="hideSearchFilters"
|
||||
>
|
||||
<div>
|
||||
<h2
|
||||
class="center"
|
||||
name="title"
|
||||
>
|
||||
{{ title }}
|
||||
</h2>
|
||||
<ft-flex-box class="radioFlexBox">
|
||||
<ft-radio-button
|
||||
|
@ -10,6 +17,7 @@
|
|||
:title="$t('Search Filters.Sort By.Sort By')"
|
||||
:labels="sortByLabels"
|
||||
:values="sortByValues"
|
||||
:initial-value-index="searchSortByStartIndex"
|
||||
class="searchRadio"
|
||||
@change="updateSortBy"
|
||||
/>
|
||||
|
@ -18,6 +26,7 @@
|
|||
:title="$t('Search Filters.Time.Time')"
|
||||
:labels="timeLabels"
|
||||
:values="timeValues"
|
||||
:initial-value-index="searchTimeStartIndex"
|
||||
class="searchRadio"
|
||||
@change="updateTime"
|
||||
/>
|
||||
|
@ -26,6 +35,7 @@
|
|||
:title="$t('Search Filters.Type.Type')"
|
||||
:labels="typeLabels"
|
||||
:values="typeValues"
|
||||
:initial-value-index="searchTypeStartIndex"
|
||||
class="searchRadio"
|
||||
@change="updateType"
|
||||
/>
|
||||
|
@ -34,12 +44,21 @@
|
|||
:title="$t('Search Filters.Duration.Duration')"
|
||||
:labels="durationLabels"
|
||||
:values="durationValues"
|
||||
:initial-value-index="searchDurationStartIndex"
|
||||
class="searchRadio"
|
||||
@change="updateDuration"
|
||||
/>
|
||||
</ft-flex-box>
|
||||
<div class="searchFilterCloseButtonContainer">
|
||||
<ft-button
|
||||
:label="$t('Close')"
|
||||
background-color="var(--primary-color)"
|
||||
text-color="var(--text-with-main-color)"
|
||||
@click="hideSearchFilters"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ft-prompt>
|
||||
</template>
|
||||
|
||||
<script src="./ft-search-filters.js" />
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { defineComponent } from 'vue'
|
||||
import { mapActions } from 'vuex'
|
||||
import FtInput from '../ft-input/ft-input.vue'
|
||||
import FtSearchFilters from '../ft-search-filters/ft-search-filters.vue'
|
||||
import FtProfileSelector from '../ft-profile-selector/ft-profile-selector.vue'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
|
@ -15,15 +14,12 @@ export default defineComponent({
|
|||
name: 'TopNav',
|
||||
components: {
|
||||
FtInput,
|
||||
FtSearchFilters,
|
||||
FtProfileSelector
|
||||
},
|
||||
data: () => {
|
||||
return {
|
||||
component: this,
|
||||
showSearchContainer: true,
|
||||
showFilters: false,
|
||||
searchFilterValueChanged: false,
|
||||
historyIndex: 1,
|
||||
isForwardOrBack: false,
|
||||
isArrowBackwardDisabled: true,
|
||||
|
@ -84,6 +80,10 @@ export default defineComponent({
|
|||
return this.$store.getters.getExpandSideBar
|
||||
},
|
||||
|
||||
searchFilterValueChanged: function () {
|
||||
return this.$store.getters.getSearchFilterValueChanged
|
||||
},
|
||||
|
||||
forwardText: function () {
|
||||
return this.$t('Forward')
|
||||
},
|
||||
|
@ -216,9 +216,6 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Close the filter panel
|
||||
this.showFilters = false
|
||||
},
|
||||
|
||||
focusSearch: function () {
|
||||
|
@ -295,11 +292,6 @@ export default defineComponent({
|
|||
|
||||
toggleSearchContainer: function () {
|
||||
this.showSearchContainer = !this.showSearchContainer
|
||||
this.showFilters = false
|
||||
},
|
||||
|
||||
handleSearchFilterValueChanged: function (filterValueChanged) {
|
||||
this.searchFilterValueChanged = filterValueChanged
|
||||
},
|
||||
|
||||
navigateHistory: function () {
|
||||
|
@ -354,14 +346,12 @@ export default defineComponent({
|
|||
navigate: function (route) {
|
||||
this.$router.push('/' + route)
|
||||
},
|
||||
hideFilters: function () {
|
||||
this.showFilters = false
|
||||
},
|
||||
updateSearchInputText: function (text) {
|
||||
this.$refs.searchInput.updateInputData(text)
|
||||
},
|
||||
...mapActions([
|
||||
'getYoutubeUrlInfo',
|
||||
'showSearchFilters'
|
||||
])
|
||||
}
|
||||
})
|
||||
|
|
|
@ -89,15 +89,17 @@
|
|||
}
|
||||
|
||||
.navFilterIcon {
|
||||
$effect-distance: 10px;
|
||||
$effect-distance: 20px;
|
||||
|
||||
margin-inline-start: $effect-distance;
|
||||
|
||||
&.filterChanged {
|
||||
box-shadow: 0 0 $effect-distance var(--primary-color);
|
||||
color: var(--primary-color);
|
||||
|
||||
@include top-nav-is-colored {
|
||||
box-shadow: 0 0 $effect-distance var(--text-with-main-color);
|
||||
color: var(--text-with-main-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -209,18 +211,4 @@
|
|||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.searchFilters {
|
||||
inset-inline: 0;
|
||||
margin-block: 10px 20px;
|
||||
margin-inline: 220px 20px;
|
||||
position: absolute;
|
||||
transition: margin 150ms ease-in-out;
|
||||
|
||||
@media only screen and (width <= 680px) {
|
||||
inset-inline: 0;
|
||||
margin-block: 95px 0;
|
||||
margin-inline: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,18 +94,13 @@
|
|||
class="navFilterIcon navIcon"
|
||||
:class="{ filterChanged: searchFilterValueChanged }"
|
||||
:icon="['fas', 'filter']"
|
||||
:title="$t('Search Filters.Search Filters')"
|
||||
role="button"
|
||||
tabindex="0"
|
||||
@click="showFilters = !showFilters"
|
||||
@keydown.enter.prevent="showFilters = !showFilters"
|
||||
@click="showSearchFilters"
|
||||
@keydown.enter.prevent="showSearchFilters"
|
||||
/>
|
||||
</div>
|
||||
<ft-search-filters
|
||||
v-if="!hideSearchBar"
|
||||
v-show="showFilters"
|
||||
class="searchFilters"
|
||||
@filterValueUpdated="handleSearchFilterValueChanged"
|
||||
/>
|
||||
</div>
|
||||
<ft-profile-selector class="side profiles" />
|
||||
</div>
|
||||
|
|
|
@ -81,6 +81,7 @@ import {
|
|||
faMonero
|
||||
} from '@fortawesome/free-brands-svg-icons'
|
||||
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome'
|
||||
import PortalVue from 'portal-vue'
|
||||
|
||||
Vue.config.devtools = process.env.NODE_ENV === 'development'
|
||||
Vue.config.performance = process.env.NODE_ENV === 'development'
|
||||
|
@ -172,6 +173,7 @@ new Vue({
|
|||
i18n,
|
||||
render: h => h(App)
|
||||
})
|
||||
Vue.use(PortalVue)
|
||||
|
||||
// to avoid accessing electron api from web app build
|
||||
if (process.env.IS_ELECTRON) {
|
||||
|
|
|
@ -33,6 +33,8 @@ const state = {
|
|||
showProgressBar: false,
|
||||
showAddToPlaylistPrompt: false,
|
||||
showCreatePlaylistPrompt: false,
|
||||
showSearchFilters: false,
|
||||
searchFilterValueChanged: false,
|
||||
progressBarPercentage: 0,
|
||||
toBeAddedToPlaylistVideoList: [],
|
||||
newPlaylistDefaultProperties: {},
|
||||
|
@ -94,6 +96,10 @@ const getters = {
|
|||
return state.searchSettings
|
||||
},
|
||||
|
||||
getSearchFilterValueChanged () {
|
||||
return state.searchFilterValueChanged
|
||||
},
|
||||
|
||||
getShowAddToPlaylistPrompt () {
|
||||
return state.showAddToPlaylistPrompt
|
||||
},
|
||||
|
@ -102,6 +108,10 @@ const getters = {
|
|||
return state.showCreatePlaylistPrompt
|
||||
},
|
||||
|
||||
getShowSearchFilters () {
|
||||
return state.showSearchFilters
|
||||
},
|
||||
|
||||
getToBeAddedToPlaylistVideoList () {
|
||||
return state.toBeAddedToPlaylistVideoList
|
||||
},
|
||||
|
@ -168,7 +178,7 @@ const getters = {
|
|||
|
||||
getLastVideoRefreshTimestampByProfile: (state) => (profileId) => {
|
||||
return state.lastVideoRefreshTimestampByProfile[profileId]
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
const actions = {
|
||||
|
@ -378,6 +388,14 @@ const actions = {
|
|||
commit('setShowCreatePlaylistPrompt', false)
|
||||
},
|
||||
|
||||
showSearchFilters ({ commit }) {
|
||||
commit('setShowSearchFilters', true)
|
||||
},
|
||||
|
||||
hideSearchFilters ({ commit }) {
|
||||
commit('setShowSearchFilters', false)
|
||||
},
|
||||
|
||||
updateShowProgressBar ({ commit }, value) {
|
||||
commit('setShowProgressBar', value)
|
||||
},
|
||||
|
@ -839,6 +857,10 @@ const mutations = {
|
|||
state.showCreatePlaylistPrompt = payload
|
||||
},
|
||||
|
||||
setShowSearchFilters (state, payload) {
|
||||
state.showSearchFilters = payload
|
||||
},
|
||||
|
||||
setToBeAddedToPlaylistVideoList (state, payload) {
|
||||
state.toBeAddedToPlaylistVideoList = payload
|
||||
},
|
||||
|
@ -899,6 +921,10 @@ const mutations = {
|
|||
state.cachedPlaylist = value
|
||||
},
|
||||
|
||||
setSearchFilterValueChanged (state, value) {
|
||||
state.searchFilterValueChanged = value
|
||||
},
|
||||
|
||||
setSearchSortBy (state, value) {
|
||||
state.searchSettings.sortBy = value
|
||||
},
|
||||
|
|
|
@ -6681,6 +6681,11 @@ pluralize@^8.0.0:
|
|||
resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1"
|
||||
integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==
|
||||
|
||||
portal-vue@^2.1.7:
|
||||
version "2.1.7"
|
||||
resolved "https://registry.yarnpkg.com/portal-vue/-/portal-vue-2.1.7.tgz#ea08069b25b640ca08a5b86f67c612f15f4e4ad4"
|
||||
integrity sha512-+yCno2oB3xA7irTt0EU5Ezw22L2J51uKAacE/6hMPMoO/mx3h4rXFkkBkT4GFsMDv/vEe8TNKC3ujJJ0PTwb6g==
|
||||
|
||||
postcss-calc@^9.0.1:
|
||||
version "9.0.1"
|
||||
resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-9.0.1.tgz#a744fd592438a93d6de0f1434c572670361eb6c6"
|
||||
|
|
Loading…
Reference in New Issue