Add Search Suggestions and Custom Invidious Instance

This commit is contained in:
Preston 2020-06-01 22:42:29 -04:00
parent 2b476f4dc6
commit c8da6fec3d
12 changed files with 183 additions and 26 deletions

11
package-lock.json generated
View File

@ -12940,6 +12940,11 @@
"resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz",
"integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU="
}, },
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168="
},
"lodash.defaults": { "lodash.defaults": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
@ -16268,8 +16273,7 @@
}, },
"minimist": { "minimist": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "resolved": "",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true "dev": true
}, },
"schema-utils": { "schema-utils": {
@ -18870,8 +18874,7 @@
}, },
"minimist": { "minimist": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "resolved": "",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true "dev": true
}, },
"string-width": { "string-width": {

View File

@ -17,6 +17,7 @@
"dateformat": "^3.0.3", "dateformat": "^3.0.3",
"electron-context-menu": "^2.0.1", "electron-context-menu": "^2.0.1",
"jquery": "^3.5.1", "jquery": "^3.5.1",
"lodash.debounce": "^4.0.8",
"lodash.isequal": "^4.5.0", "lodash.isequal": "^4.5.0",
"material-design-icons": "^3.0.1", "material-design-icons": "^3.0.1",
"mediaelement": "^4.2.16", "mediaelement": "^4.2.16",

View File

@ -22,27 +22,35 @@
color: var(--tertiary-text-color); color: var(--tertiary-text-color);
} }
.search .ft-input { .forceTextColor .ft-input {
color: var(--text-with-main-color); color: var(--text-with-main-color);
border-bottom: 1px solid var(--text-with-main-color); border-bottom: 1px solid var(--text-with-main-color);
} }
.search ::-webkit-input-placeholder { .forceTextColor ::-webkit-input-placeholder {
color: var(--text-with-main-color); color: var(--text-with-main-color);
} }
.inputAction { .inputAction {
position: absolute; position: absolute;
padding: 10px; padding: 10px;
top: 10px; top: 5px;
right: 0px; right: 0px;
cursor: pointer; cursor: pointer;
border-radius: 200px 200px 200px 200px; border-radius: 200px 200px 200px 200px;
color: var(--primary-text-color); color: var(--primary-text-color);
} }
.search ::-webkit-calendar-picker-indicator {
display: none;
}
.search .inputAction { .search .inputAction {
color: var(--text-with-main-color) top: 12px;
}
.forceTextColor .inputAction {
color: var(--text-with-main-color);
} }
.inputAction:hover { .inputAction:hover {
@ -52,7 +60,7 @@
transition: background 0.2s ease-in; transition: background 0.2s ease-in;
} }
.search .inputAction:hover { .forceTextColor .inputAction:hover {
background-color: var(--primary-color-hover); background-color: var(--primary-color-hover);
} }
@ -63,6 +71,6 @@
transition: background 0.2s ease-in; transition: background 0.2s ease-in;
} }
.search .inputAction:active { .forceTextColor .inputAction:active {
background-color: var(--primary-color-active); background-color: var(--primary-color-active);
} }

View File

@ -7,14 +7,26 @@ export default Vue.extend({
type: String, type: String,
required: true required: true
}, },
value: {
type: String,
default: ''
},
showArrow: { showArrow: {
type: Boolean, type: Boolean,
default: true default: true
}, },
showLabel: {
type: Boolean,
default: false
},
isSearch: { isSearch: {
type: Boolean, type: Boolean,
default: false default: false
} },
dataList: {
type: Array,
default: () => { return [] }
},
}, },
data: function () { data: function () {
return { return {
@ -29,6 +41,10 @@ export default Vue.extend({
forceTextColor: function () { forceTextColor: function () {
return this.isSearch && this.barColor return this.isSearch && this.barColor
},
idDataList: function () {
return `${this.id}_datalist`
} }
}, },
mounted: function () { mounted: function () {
@ -41,6 +57,11 @@ export default Vue.extend({
this.$emit('click', this.inputData) this.$emit('click', this.inputData)
}, },
handleInput: function (input) {
this.inputData = input
this.$emit('input', input)
},
addListener: function () { addListener: function () {
const inputElement = document.getElementById(this.id) const inputElement = document.getElementById(this.id)

View File

@ -1,14 +1,25 @@
<template> <template>
<div <div
class="ft-input-component" class="ft-input-component"
:class="{ search: forceTextColor }" :class="{
search: isSearch,
forceTextColor: forceTextColor
}"
> >
<label
v-if="showLabel"
:for="id"
>
{{ placeholder }}
</label>
<input <input
:id="id" :id="id"
:list="idDataList"
:value="value"
class="ft-input" class="ft-input"
type="text" type="text"
:placeholder="placeholder" :placeholder="placeholder"
@input="e => inputData = e.target.value" @input="e => handleInput(e.target.value)"
> >
<font-awesome-icon <font-awesome-icon
v-if="showArrow" v-if="showArrow"
@ -16,6 +27,16 @@
class="inputAction" class="inputAction"
@click="handleClick" @click="handleClick"
/> />
<datalist
v-if="dataList.length > 0"
:id="idDataList"
>
<option
v-for="(list, index) in dataList"
:key="index"
:value="list"
/>
</datalist>
</div> </div>
</template> </template>

View File

@ -3,14 +3,18 @@ import $ from 'jquery'
import { mapActions } from 'vuex' import { mapActions } from 'vuex'
import FtCard from '../ft-card/ft-card.vue' import FtCard from '../ft-card/ft-card.vue'
import FtSelect from '../ft-select/ft-select.vue' import FtSelect from '../ft-select/ft-select.vue'
import FtInput from '../ft-input/ft-input.vue'
import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue' import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue'
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue' import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
import debounce from 'lodash.debounce'
export default Vue.extend({ export default Vue.extend({
name: 'GeneralSettings', name: 'GeneralSettings',
components: { components: {
'ft-card': FtCard, 'ft-card': FtCard,
'ft-select': FtSelect, 'ft-select': FtSelect,
'ft-input': FtInput,
'ft-toggle-switch': FtToggleSwitch, 'ft-toggle-switch': FtToggleSwitch,
'ft-flex-box': FtFlexBox 'ft-flex-box': FtFlexBox
}, },
@ -597,8 +601,20 @@ export default Vue.extend({
console.log(requestUrl) console.log(requestUrl)
console.log(error) console.log(error)
}) })
this.updateInvidiousInstanceBounce = debounce(this.updateInvidiousInstance, 500)
},
beforeDestroy: function () {
if (this.invidiousInstance === '') {
this.updateInvidiousInstance('https://invidio.us')
}
}, },
methods: { methods: {
handleInvidiousInstanceInput: function (input) {
const invidiousInstance = input.replace(/\/$/, '')
this.updateInvidiousInstanceBounce(invidiousInstance)
},
...mapActions([ ...mapActions([
'updateBackendFallback', 'updateBackendFallback',
'updateCheckForUpdates', 'updateCheckForUpdates',

View File

@ -57,15 +57,17 @@
:select-values="thumbnailTypeValues" :select-values="thumbnailTypeValues"
@change="updateThumbnailPreference" @change="updateThumbnailPreference"
/> />
<ft-select
v-if="showInvidiousInstances"
placeholder="Invidious Instance"
:value="invidiousInstance"
:select-names="instanceNames"
:select-values="instanceValues"
@change="updateInvidiousInstance"
/>
</div> </div>
<ft-flex-box class="generalSettingsFlexBox">
<ft-input
placeholder="Invidious Instance (Default is https://invidio.us)"
:show-arrow="false"
:show-label="true"
:value="invidiousInstance"
:data-list="instanceValues"
@input="handleInvidiousInstanceInput"
/>
</ft-flex-box>
</ft-card> </ft-card>
</template> </template>

View File

@ -3,6 +3,8 @@ import FtInput from '../ft-input/ft-input.vue'
import FtSearchFilters from '../ft-search-filters/ft-search-filters.vue' import FtSearchFilters from '../ft-search-filters/ft-search-filters.vue'
import $ from 'jquery' import $ from 'jquery'
import router from '../../router/index.js' import router from '../../router/index.js'
import debounce from 'lodash.debounce'
import ytSuggest from 'youtube-suggest'
export default Vue.extend({ export default Vue.extend({
name: 'TopNav', name: 'TopNav',
@ -14,7 +16,9 @@ export default Vue.extend({
return { return {
component: this, component: this,
windowWidth: 0, windowWidth: 0,
showFilters: false showFilters: false,
searchValue: '',
searchSuggestionsDataList: []
} }
}, },
computed: { computed: {
@ -28,7 +32,19 @@ export default Vue.extend({
barColor: function () { barColor: function () {
return this.$store.getters.getBarColor return this.$store.getters.getBarColor
} },
invidiousInstance: function () {
return this.$store.getters.getInvidiousInstance
},
backendFallback: function () {
return this.$store.getters.getBackendFallback
},
backendPreference: function () {
return this.$store.getters.getBackendPreference
},
}, },
mounted: function () { mounted: function () {
const appWidth = $(window).width() const appWidth = $(window).width()
@ -48,6 +64,8 @@ export default Vue.extend({
searchContainer.style.display = 'none' searchContainer.style.display = 'none'
} }
}) })
this.debounceSearchResults = debounce(this.getSearchSuggestions, 500)
}, },
methods: { methods: {
goToSearch: function (query) { goToSearch: function (query) {
@ -84,6 +102,55 @@ export default Vue.extend({
this.showFilters = false this.showFilters = false
}, },
getSearchSuggestionsDebounce: function (query) {
this.debounceSearchResults(query)
},
getSearchSuggestions: function (query) {
switch (this.backendPreference) {
case 'local':
this.getSearchSuggestionsLocal(query)
break
case 'invidious':
this.getSearchSuggestionsInvidious(query)
break
}
},
getSearchSuggestionsLocal: function (query) {
if (query === '') {
this.searchSuggestionsDataList = []
this.searchValue = ''
return
}
ytSuggest(query).then((results) => {
this.searchSuggestionsDataList = results
this.searchValue = query
})
},
getSearchSuggestionsInvidious: function (query) {
if (query === '') {
this.searchSuggestionsDataList = []
this.searchValue = ''
return
}
const searchPayload = {
resource: 'search/suggestions',
id: '',
params: {
q: query
}
}
this.$store.dispatch('invidiousAPICall', searchPayload).then((results) => {
this.searchSuggestionsDataList = results.suggestions
this.searchValue = query
})
},
toggleSearchContainer: function () { toggleSearchContainer: function () {
const searchContainer = $('.searchContainer').get(0) const searchContainer = $('.searchContainer').get(0)

View File

@ -39,6 +39,9 @@
placeholder="Search / Go to URL" placeholder="Search / Go to URL"
class="searchInput" class="searchInput"
:is-search="true" :is-search="true"
:data-list="searchSuggestionsDataList"
:value="searchValue"
@input="getSearchSuggestionsDebounce"
@click="goToSearch" @click="goToSearch"
/> />
<font-awesome-icon <font-awesome-icon

View File

@ -14,7 +14,7 @@
.channelInformation { .channelInformation {
position: absolute; position: absolute;
bottom: 10px; bottom: 10px;
width: 300px; width: 350px;
} }
.channelThumbnail { .channelThumbnail {

View File

@ -150,14 +150,18 @@ const getters = {
} }
const actions = { const actions = {
grabUserSettings ({ commit }) { grabUserSettings ({ dispatch, commit }) {
settingsDb.find({}, (err, results) => { settingsDb.find({}, (err, results) => {
if (!err) { if (!err) {
console.log(results) console.log(results)
results.forEach((result) => { results.forEach((result) => {
switch (result._id) { switch (result._id) {
case 'invidiousInstance': case 'invidiousInstance':
commit('setInvidiousInstance', result.value) if (result.value === '') {
dispatch('updateInvidiousInstance', 'https://invidio.us')
} else {
commit('setInvidiousInstance', result.value)
}
break break
case 'backendFallback': case 'backendFallback':
commit('setBackendFallback', result.value) commit('setBackendFallback', result.value)

View File

@ -137,6 +137,7 @@ export default Vue.extend({
this.videoId = this.$route.params.id this.videoId = this.$route.params.id
this.firstLoad = true this.firstLoad = true
this.activeFormat = this.defaultVideoFormat
this.checkIfPlaylist() this.checkIfPlaylist()
@ -206,6 +207,16 @@ export default Vue.extend({
this.videoDislikeCount = result.dislikes this.videoDislikeCount = result.dislikes
this.isLive = result.player_response.videoDetails.isLive this.isLive = result.player_response.videoDetails.isLive
const subCount = result.author.subscriber_count
if (subCount >= 1000000) {
this.channelSubscriptionCountText = `${subCount / 1000000}M`
} else if (subCount >= 10000) {
this.channelSubscriptionCountText = `${subCount / 1000}K`
} else {
this.channelSubscriptionCountText = subCount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',')
}
if (this.isLive) { if (this.isLive) {
this.showLegacyPlayer = true this.showLegacyPlayer = true
this.showDashPlayer = false this.showDashPlayer = false