Skip to content

Commit

Permalink
Implement search history display logic and UI
Browse files Browse the repository at this point in the history
  • Loading branch information
kommunarr committed Dec 19, 2024
1 parent cb18afd commit 7b3206a
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 71 deletions.
2 changes: 1 addition & 1 deletion src/datastores/handlers/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ class SearchHistory {
}

static find() {
return db.searchHistory.findAsync({})
return db.searchHistory.findAsync({}).sort({ lastUpdatedAt: -1 })
}

static upsert(searchHistoryEntry) {
Expand Down
2 changes: 2 additions & 0 deletions src/renderer/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ export default defineComponent({
this.grabHistory()
this.grabAllPlaylists()
this.grabAllSubscriptions()
this.grabSearchHistoryEntries()

if (process.env.IS_ELECTRON) {
ipcRenderer = require('electron').ipcRenderer
Expand Down Expand Up @@ -570,6 +571,7 @@ export default defineComponent({
'grabHistory',
'grabAllPlaylists',
'grabAllSubscriptions',
'grabSearchHistoryEntries',
'getYoutubeUrlInfo',
'getExternalPlayerCmdArgumentsData',
'fetchInvidiousInstances',
Expand Down
16 changes: 15 additions & 1 deletion src/renderer/components/ft-input/ft-input.css
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,22 @@ body[dir='rtl'] .ft-input-component.search.showClearTextButton:focus-within .inp
.list li {
display: block;
padding-block: 0;
padding-inline: 15px;
line-height: 2rem;
padding-inline: 15px;
text-overflow: ellipsis;
overflow-x: hidden;
white-space: nowrap;
}

.bookmarkStarIcon {
color: var(--favorite-icon-color);
}

.searchResultIcon {
opacity: 0.6;
padding-inline-end: 10px;
inline-size: 16px;
block-size: 16px;
}

.hover {
Expand Down
30 changes: 22 additions & 8 deletions src/renderer/components/ft-input/ft-input.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ export default defineComponent({
type: Array,
default: () => { return [] }
},
showDataWhenEmpty: {
type: Boolean,
default: false
},
tooltip: {
type: String,
default: ''
Expand Down Expand Up @@ -116,8 +120,7 @@ export default defineComponent({

searchStateKeyboardSelectedOptionValue() {
if (this.searchState.keyboardSelectedOptionIndex === -1) { return null }

return this.visibleDataList[this.searchState.keyboardSelectedOptionIndex]
return this.getTextForArrayAtIndex(this.visibleDataList, this.searchState.keyboardSelectedOptionIndex)
},
},
watch: {
Expand All @@ -143,6 +146,9 @@ export default defineComponent({
this.updateVisibleDataList()
},
methods: {
getTextForArrayAtIndex: function (array, index) {
return array[index].name ?? array[index]
},
handleClick: function (e) {
// No action if no input text
if (!this.inputDataPresent) {
Expand All @@ -166,7 +172,7 @@ export default defineComponent({
this.$emit('input', val)
},

handleClearTextClick: function () {
handleClearTextClick: function ({ programmaticallyTriggered = false }) {
// No action if no input text
if (!this.inputDataPresent) { return }

Expand All @@ -177,7 +183,9 @@ export default defineComponent({
this.$refs.input.value = ''

// Focus on input element after text is clear for better UX
this.$refs.input.focus()
if (!programmaticallyTriggered) {
this.$refs.input.focus()
}

this.$emit('clear')
},
Expand Down Expand Up @@ -234,7 +242,11 @@ export default defineComponent({

handleOptionClick: function (index) {
this.searchState.showOptions = false
this.inputData = this.visibleDataList[index]
if (this.visibleDataList[index].route) {
this.inputData = `ft:${this.visibleDataList[index].route}`
} else {
this.inputData = this.visibleDataList[index]
}
this.$emit('input', this.inputData)
this.handleClick()
},
Expand All @@ -248,9 +260,11 @@ export default defineComponent({
if (this.searchState.selectedOption !== -1) {
this.searchState.showOptions = false
event.preventDefault()
this.inputData = this.visibleDataList[this.searchState.selectedOption]
this.inputData = this.getTextForArrayAtIndex(this.visibleDataList, this.searchState.selectedOption)
this.handleOptionClick(this.searchState.selectedOption)
} else {
this.handleClick(event)
}
this.handleClick(event)
// Early return
return
}
Expand Down Expand Up @@ -291,7 +305,7 @@ export default defineComponent({
if (!this.searchState.isPointerInList) { this.searchState.showOptions = false }
},

handleFocus: function(e) {
handleFocus: function () {
this.searchState.showOptions = true
},

Expand Down
18 changes: 14 additions & 4 deletions src/renderer/components/ft-input/ft-input.vue
Original file line number Diff line number Diff line change
Expand Up @@ -68,21 +68,31 @@
</span>
<div class="options">
<ul
v-if="inputData !== '' && visibleDataList.length > 0 && searchState.showOptions"
v-if="(inputData !== '' || showDataWhenEmpty) && visibleDataList.length > 0 && searchState.showOptions"
class="list"
@mouseenter="searchState.isPointerInList = true"
@mouseleave="searchState.isPointerInList = false"
>
<!-- eslint-disable vuejs-accessibility/click-events-have-key-events -->
<li
v-for="(list, index) in visibleDataList"
v-for="(entry, index) in visibleDataList"
:key="index"
:class="searchState.selectedOption === index ? 'hover': ''"
:class="{ hover: searchState.selectedOption === index }"
@click="handleOptionClick(index)"
@mouseenter="searchState.selectedOption = index"
@mouseleave="searchState.selectedOption = -1"
>
{{ list }}
<font-awesome-icon
v-if="entry.name"
:icon="['fas', 'clock-rotate-left']"
class="searchResultIcon bookmarkStarIcon"
/>
<font-awesome-icon
v-else-if="isSearch"
:icon="['fas', 'magnifying-glass']"
class="searchResultIcon"
/>
{{ entry.name ?? entry }}
</li>
<!-- skipped -->
</ul>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
:show-label="true"
:value="currentInvidiousInstance"
:data-list="invidiousInstancesList"
:show-data-when-empty="true"
:tooltip="$t('Tooltips.General Settings.Invidious Instance')"
@input="handleInvidiousInstanceInput"
/>
Expand Down
34 changes: 28 additions & 6 deletions src/renderer/components/top-nav/top-nav.js
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,15 @@ export default defineComponent({
this.$t('Open New Window'),
KeyboardShortcuts.APP.GENERAL.NEW_WINDOW
)
}
},

// show latest search history when the search bar is empty
activeDataList: function () {
if (!this.enableSearchSuggestions) {
return
}
return this.lastSuggestionQuery === '' ? this.$store.getters.getLatestUniqueSearchHistoryEntries : this.searchSuggestionsDataList
},
},
watch: {
$route: function () {
Expand Down Expand Up @@ -168,6 +176,17 @@ export default defineComponent({

clearLocalSearchSuggestionsSession()

if (queryText.startsWith('ft:')) {
this.$refs.searchInput.handleClearTextClick({ programmaticallyTriggered: true })
const adjustedQuery = queryText.substring(3)
openInternalPath({
path: adjustedQuery,
adjustedQuery,
doCreateNewWindow
})
return
}

this.getYoutubeUrlInfo(queryText).then((result) => {
switch (result.urlType) {
case 'video': {
Expand Down Expand Up @@ -289,12 +308,15 @@ export default defineComponent({
},

getSearchSuggestionsDebounce: function (query) {
const trimmedQuery = query.trim()
if (trimmedQuery === this.lastSuggestionQuery || trimmedQuery.startsWith('ft:')) {
return
}

this.lastSuggestionQuery = trimmedQuery

if (this.enableSearchSuggestions) {
const trimmedQuery = query.trim()
if (trimmedQuery !== this.lastSuggestionQuery) {
this.lastSuggestionQuery = trimmedQuery
this.debounceSearchResults(trimmedQuery)
}
this.debounceSearchResults(trimmedQuery)
}
},

Expand Down
3 changes: 2 additions & 1 deletion src/renderer/components/top-nav/top-nav.vue
Original file line number Diff line number Diff line change
Expand Up @@ -92,9 +92,10 @@
:placeholder="$t('Search / Go to URL')"
class="searchInput"
:is-search="true"
:data-list="searchSuggestionsDataList"
:data-list="activeDataList"
:spellcheck="false"
:show-clear-text-button="true"
:show-data-when-empty="true"
@input="getSearchSuggestionsDebounce"
@click="goToSearch"
/>
Expand Down
4 changes: 3 additions & 1 deletion src/renderer/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {
faCircleUser,
faClapperboard,
faClock,
faClockRotateLeft,
faClone,
faComment,
faCommentDots,
Expand Down Expand Up @@ -110,7 +111,7 @@ import {
faUserLock,
faUsers,
faUsersSlash,
faWifi
faWifi,
} from '@fortawesome/free-solid-svg-icons'
import {
faBookmark as farBookmark,
Expand Down Expand Up @@ -151,6 +152,7 @@ library.add(
faCircleUser,
faClapperboard,
faClock,
faClockRotateLeft,
faClone,
faComment,
faCommentDots,
Expand Down
66 changes: 19 additions & 47 deletions src/renderer/store/modules/search-history.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { DBSearchHistoryHandlers } from '../../../datastores/handlers/index'

// matches # of results we show for search suggestions
const SEARCH_HISTORY_ENTRIES_DISPLAY_LIMIT = 14

const state = {
searchHistoryEntries: []
}
Expand All @@ -9,40 +12,22 @@ const getters = {
return state.searchHistoryEntries
},

getLatestUniqueSearchHistoryEntries: (state) => {
const nameSet = new Set()
return state.searchHistoryEntries.filter((entry) => {
if (nameSet.has(entry.name)) {
return false
}

nameSet.add(entry.name)
return true
}).slice(0, SEARCH_HISTORY_ENTRIES_DISPLAY_LIMIT)
},

getSearchHistoryEntryWithRoute: (state) => (route) => {
const searchHistoryEntry = state.searchHistoryEntries.find(p => p.route === route)
return searchHistoryEntry
},

getSearchHistoryEntriesMatchingQuery: (state) => (query, routeToExclude) => {
if (query === '') {
return []
}
const queryToLower = query.toLowerCase()
return state.searchHistoryEntries.filter((searchHistoryEntry) =>
searchHistoryEntry.name.toLowerCase().includes(queryToLower) && searchHistoryEntry.route !== routeToExclude
)
},

getSearchHistoryIdsForMatchingUserPlaylistIds: (state) => (playlistIds) => {
const searchHistoryIds = []
const allSearchHistoryEntries = state.searchHistoryEntries
const searchHistoryEntryLimitedRoutesMap = new Map()
allSearchHistoryEntries.forEach((searchHistoryEntry) => {
searchHistoryEntryLimitedRoutesMap.set(searchHistoryEntry.route, searchHistoryEntry._id)
})

playlistIds.forEach((playlistId) => {
const route = `/playlist/${playlistId}?playlistType=user&searchQueryText=`
if (!searchHistoryEntryLimitedRoutesMap.has(route)) {
return
}

searchHistoryIds.push(searchHistoryEntryLimitedRoutesMap.get(route))
})

return searchHistoryIds
}
}
const actions = {
async grabSearchHistoryEntries({ commit }) {
Expand Down Expand Up @@ -90,15 +75,6 @@ const actions = {
}
},

async removeUserPlaylistSearchHistoryEntries({ dispatch, getters }, userPlaylistIds) {
const searchHistoryIds = getters.getSearchHistoryIdsForMatchingUserPlaylistIds(userPlaylistIds)
if (searchHistoryIds.length === 0) {
return
}

dispatch('removeSearchHistoryEntries', searchHistoryIds)
},

async removeAllSearchHistoryEntries({ commit }) {
try {
await DBSearchHistoryHandlers.deleteAll()
Expand All @@ -111,23 +87,19 @@ const actions = {

const mutations = {
addSearchHistoryEntryToList(state, searchHistoryEntry) {
state.searchHistoryEntries.push(searchHistoryEntry)
state.searchHistoryEntries.unshift(searchHistoryEntry)
},

setSearchHistoryEntries(state, searchHistoryEntries) {
state.searchHistoryEntries = searchHistoryEntries
},

upsertSearchHistoryEntryToList(state, updatedSearchHistoryEntry) {
const i = state.searchHistoryEntries.findIndex((p) => {
return p.route === updatedSearchHistoryEntry.route
state.searchHistoryEntries = state.searchHistoryEntries.filter((p) => {
return p.route !== updatedSearchHistoryEntry.route
})

if (i === -1) {
state.searchHistoryEntries.push(updatedSearchHistoryEntry)
} else {
state.searchHistoryEntries.splice(i, 1, updatedSearchHistoryEntry)
}
state.searchHistoryEntries.unshift(updatedSearchHistoryEntry)
},

removeSearchHistoryEntryFromList(state, _id) {
Expand Down
Loading

0 comments on commit 7b3206a

Please sign in to comment.