diff options
author | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-08-04 10:00:27 +0200 |
---|---|---|
committer | John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com> | 2020-08-04 21:36:22 +0200 |
commit | 71b62c4203a25beefeab73f73668919c813e3a50 (patch) | |
tree | e75b6b0338ed800ddf88bfe27ce6703045c18e48 /core/src | |
parent | 6eced42b7a40f5b0ea0489244583219d0ee2e7af (diff) | |
download | nextcloud-server-71b62c4203a25beefeab73f73668919c813e3a50.tar.gz nextcloud-server-71b62c4203a25beefeab73f73668919c813e3a50.zip |
Show mime icon, bump bundles, make the SearchResultEntry class non-abstract, Fix header search icon, various fixes
Signed-off-by: John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
Diffstat (limited to 'core/src')
-rw-r--r-- | core/src/components/UnifiedSearch/SearchResult.vue | 35 | ||||
-rw-r--r-- | core/src/components/UnifiedSearch/SearchResultPlaceholder.vue | 68 | ||||
-rw-r--r-- | core/src/services/UnifiedSearchService.js | 28 | ||||
-rw-r--r-- | core/src/views/UnifiedSearch.vue | 73 |
4 files changed, 184 insertions, 20 deletions
diff --git a/core/src/components/UnifiedSearch/SearchResult.vue b/core/src/components/UnifiedSearch/SearchResult.vue index 832770c9abe..ffd4e2bf2f2 100644 --- a/core/src/components/UnifiedSearch/SearchResult.vue +++ b/core/src/components/UnifiedSearch/SearchResult.vue @@ -2,22 +2,28 @@ <a :href="resourceUrl || '#'" class="unified-search__result" :class="{ - 'unified-search__result--focused': focused + 'unified-search__result--focused': focused, }" @click="reEmitEvent" @focus="reEmitEvent"> + <!-- Icon describing the result --> <div class="unified-search__result-icon" :class="{ 'unified-search__result-icon--rounded': rounded, 'unified-search__result-icon--no-preview': !hasValidThumbnail && !loaded, 'unified-search__result-icon--with-thumbnail': hasValidThumbnail && loaded, - [iconClass]: true + [icon]: !loaded && !isIconUrl, + }" + :style="{ + backgroundImage: isIconUrl ? `url(${icon})` : '', }" role="img"> + <img v-if="hasValidThumbnail" + v-show="loaded" :src="thumbnailUrl" - :alt="t('core', 'Thumbnail for {result}', {result: title})" + alt="" @error="onError" @load="onLoad"> </div> @@ -59,7 +65,7 @@ export default { type: String, default: null, }, - iconClass: { + icon: { type: String, default: '', }, @@ -90,6 +96,24 @@ export default { } }, + computed: { + isIconUrl() { + // If we're facing an absolute url + if (this.icon.startsWith('/')) { + return true + } + + // Otherwise, let's check if this is a valid url + try { + // eslint-disable-next-line no-new + new URL(this.icon) + } catch { + return false + } + return true + }, + }, + watch: { // Make sure to reset state on change even when vue recycle the component thumbnailUrl() { @@ -148,6 +172,7 @@ $margin: 10px; width: $clickable-area; height: $clickable-area; border-radius: var(--border-radius); + background-repeat: no-repeat; background-position: center center; background-size: 32px; &--rounded { @@ -195,7 +220,7 @@ $margin: 10px; &-line-two { overflow: hidden; flex: 1 1 100%; - margin: 0; + margin: 1px 0; white-space: nowrap; text-overflow: ellipsis; // Use the same color as the `a` diff --git a/core/src/components/UnifiedSearch/SearchResultPlaceholder.vue b/core/src/components/UnifiedSearch/SearchResultPlaceholder.vue new file mode 100644 index 00000000000..7eb0915b359 --- /dev/null +++ b/core/src/components/UnifiedSearch/SearchResultPlaceholder.vue @@ -0,0 +1,68 @@ +<template> + <svg + class="unified-search__result-placeholder" + xmlns="http://www.w3.org/2000/svg" + fill="url(#unified-search__result-placeholder-gradient)"> + <defs> + <linearGradient id="unified-search__result-placeholder-gradient"> + <stop offset="0%" stop-color="#ededed"><animate attributeName="stop-color" + values="#ededed; #ededed; #cccccc; #cccccc; #ededed" + dur="2s" + repeatCount="indefinite" /></stop> + <stop offset="100%" stop-color="#cccccc"><animate attributeName="stop-color" + values="#cccccc; #ededed; #ededed; #cccccc; #cccccc" + dur="2s" + repeatCount="indefinite" /></stop> + </linearGradient> + </defs> + <rect class="unified-search__result-placeholder-icon" /> + <rect class="unified-search__result-placeholder-line-one" /> + <rect class="unified-search__result-placeholder-line-two" :style="{width: `calc(${randWidth}%)`}" /> + </svg> +</template> + +<script> +export default { + name: 'SearchResultPlaceholder', + + data() { + return { + randWidth: Math.floor(Math.random() * 20) + 30, + } + }, +} +</script> + +<style lang="scss" scoped> +$clickable-area: 44px; +$margin: 10px; + +.unified-search__result-placeholder { + width: calc(100% - 2 * #{$margin}); + height: $clickable-area; + margin: $margin; + + &-icon { + width: $clickable-area; + height: $clickable-area; + rx: var(--border-radius); + ry: var(--border-radius); + } + + &-line-one, + &-line-two { + width: calc(100% - #{$margin + $clickable-area}); + height: 1em; + x: $margin + $clickable-area; + } + + &-line-one { + y: 5px; + } + + &-line-two { + y: 25px; + } +} + +</style> diff --git a/core/src/services/UnifiedSearchService.js b/core/src/services/UnifiedSearchService.js index 2e63f19767d..ff9a4aebe2a 100644 --- a/core/src/services/UnifiedSearchService.js +++ b/core/src/services/UnifiedSearchService.js @@ -24,15 +24,18 @@ import { loadState } from '@nextcloud/initial-state' import axios from '@nextcloud/axios' export const defaultLimit = loadState('unified-search', 'limit-default') +export const activeApp = loadState('core', 'active-app') /** * Get the list of available search providers + * + * @returns {Array} */ export async function getTypes() { try { const { data } = await axios.get(generateUrl('/search/providers')) if (Array.isArray(data) && data.length > 0) { - return data + return sortProviders(data) } } catch (error) { console.error(error) @@ -41,6 +44,29 @@ export async function getTypes() { } /** + * Sort the providers by the current active app + * + * @param {Array} providers the providers list + * @returns {Array} + */ +export function sortProviders(providers) { + providers.sort((a, b) => { + if (a.id.startsWith(activeApp) && b.id.startsWith(activeApp)) { + return a.order - b.order + } + + if (a.id.startsWith(activeApp)) { + return -1 + } + if (b.id.startsWith(activeApp)) { + return 1 + } + return 0 + }) + return providers +} + +/** * Get the list of available search providers * * @param {string} type the type to search diff --git a/core/src/views/UnifiedSearch.vue b/core/src/views/UnifiedSearch.vue index 4535e1fde5a..d7ad58ae202 100644 --- a/core/src/views/UnifiedSearch.vue +++ b/core/src/views/UnifiedSearch.vue @@ -27,7 +27,7 @@ @close="onClose"> <!-- Header icon --> <template #trigger> - <span class="icon-search-white" /> + <Magnify class="unified-search__trigger" :size="20" fill-color="var(--color-primary-text)" /> </template> <!-- Search input --> @@ -36,17 +36,20 @@ v-model="query" class="unified-search__input" type="search" - :placeholder="t('core', 'Search for {types} …', { types: typesNames.join(', ') })" + :placeholder="t('core', 'Search {types} …', { types: typesNames.join(', ').toLowerCase() })" @input="onInputDebounced" @keypress.enter.prevent.stop="onInputEnter"> </div> - <EmptyContent v-if="isLoading" icon="icon-loading"> - {{ t('core', 'Searching …') }} - </EmptyContent> + <template v-if="!hasResults"> + <!-- Loading placeholders --> + <ul v-if="isLoading"> + <li v-for="placeholder in [1, 2, 3]" :key="placeholder"> + <SearchResultPlaceholder /> + </li> + </ul> - <template v-else-if="!hasResults"> - <EmptyContent v-if="isValidQuery && isDoneSearching" icon="icon-search"> + <EmptyContent v-else-if="isValidQuery && isDoneSearching" icon="icon-search"> {{ t('core', 'No results for {query}', {query}) }} </EmptyContent> @@ -64,7 +67,7 @@ <!-- Grouped search results --> <template v-else> - <ul v-for="(list, type, typesIndex) in results" + <ul v-for="(list, type, typesIndex) in orderedResults" :key="type" class="unified-search__results" :class="`unified-search__results-${type}`" @@ -94,13 +97,14 @@ </template> <script> -import { getTypes, search, defaultLimit } from '../services/UnifiedSearchService' +import { getTypes, search, defaultLimit, activeApp } from '../services/UnifiedSearchService' import EmptyContent from '@nextcloud/vue/dist/Components/EmptyContent' - +import Magnify from 'vue-material-design-icons/Magnify' import debounce from 'debounce' import HeaderMenu from '../components/HeaderMenu' import SearchResult from '../components/UnifiedSearch/SearchResult' +import SearchResultPlaceholder from '../components/UnifiedSearch/SearchResultPlaceholder' const minSearchLength = 2 @@ -110,7 +114,9 @@ export default { components: { EmptyContent, HeaderMenu, + Magnify, SearchResult, + SearchResultPlaceholder, }, data() { @@ -126,6 +132,7 @@ export default { query: '', focused: null, + activeApp, defaultLimit, minSearchLength, @@ -156,6 +163,32 @@ export default { }, /** + * Order results by putting the active app first + * @returns {Object} + */ + orderedResults() { + const ordered = {} + Object.keys(this.results) + .sort((a, b) => { + if (a.startsWith(activeApp) && b.startsWith(activeApp)) { + return this.typesMap[a].order - this.typesMap[b].order + } + if (a.startsWith(activeApp)) { + return -1 + } + if (b.startsWith(activeApp)) { + return 1 + } + return 0 + }) + .forEach(type => { + ordered[type] = this.results[type] + }) + + return ordered + }, + + /** * Is the current search too short * @returns {boolean} */ @@ -176,7 +209,7 @@ export default { * @returns {boolean} */ isDoneSearching() { - return Object.values(this.reached).indexOf(false) === -1 + return Object.values(this.reached).every(state => state === false) }, /** @@ -184,7 +217,7 @@ export default { * @returns {boolean} */ isLoading() { - return Object.values(this.loading).indexOf(true) !== -1 + return Object.values(this.loading).some(state => state === true) }, }, @@ -465,6 +498,11 @@ $margin: 10px; $input-padding: 6px; .unified-search { + &__trigger { + width: 20px; + height: 20px; + } + &__input-wrapper { position: sticky; // above search results @@ -479,7 +517,14 @@ $input-padding: 6px; height: 34px; margin: $margin; padding: $input-padding; - text-overflow: ellipsis; + &, + &[placeholder], + &::placeholder { + overflow: hidden; + text-overflow:ellipsis; + white-space: nowrap; + } + } &__results { @@ -488,7 +533,7 @@ $input-padding: 6px; margin: $margin; margin-left: $margin + $input-padding; content: attr(aria-label); - color: var(--color-primary); + color: var(--color-primary-element); } } |