Commit 48685b14 authored by Yoran Hillion's avatar Yoran Hillion
Browse files

feat: Adds search tab in the authenticated project page

parent 9d669097
......@@ -233,7 +233,7 @@ export const en_US = {
item: {
backToSet: "Back to set",
copyOf: "Copy of",
count: "no item | 1 item | {count} items",
count: "No item | 1 item | {count} items",
create: "Add item",
createdBy: "Created by:",
description: "Presentation",
......@@ -282,6 +282,12 @@ export const en_US = {
label: "Publication year",
empty: "Unknown",
},
resourceTypeGeneral: {
label: "Resource type general",
},
resourceType: {
label: "Resource type",
},
language: {
label: "Language",
empty: "Unknown",
......@@ -587,6 +593,9 @@ export const en_US = {
managers: "{label} : {managers}",
},
contributor: "Contributor",
search: "@:search.title in the @.lower:project.label",
searchInfo: "These criteria are only applied to items",
moreCriteria: "More criteria",
},
set: {
title: "Title",
......
......@@ -240,7 +240,7 @@ export const fr_FR = {
item: {
backToSet: "Revenir à l'ensemble",
copyOf: "Copie de",
count: "aucun élément | 1 élément | {count} éléments",
count: "Aucun élément | 1 élément | {count} éléments",
create: "Nouvel élément",
createdBy: "Créé par :",
description: "Présentation",
......@@ -289,6 +289,12 @@ export const fr_FR = {
label: "Année de publication",
empty: "Non renseignée",
},
resourceTypeGeneral: {
label: "Type de ressource général",
},
resourceType: {
label: "Type de ressource",
},
language: {
label: "Langue",
empty: "Non renseignée",
......@@ -601,6 +607,9 @@ export const fr_FR = {
managers: "{label} : {managers}",
},
contributor: "Contributeur",
search: "@:search.title dans le @.lower:project.label",
searchInfo: "Ces critères de recherche ne s'appliquent qu'aux éléments",
moreCriteria: "Plus de critères",
},
set: {
title: "Titre",
......
......@@ -104,19 +104,19 @@ export const routes = [
path: '/p/projects/:projectId',
name: 'projectPublic',
props: true,
component: () => import(/* webpackCunkName: "public" */ '@/views/projects/ProjectPublic'),
component: () => import(/* webpackChunkName: "public" */ '@/views/projects/ProjectPublic'),
},
{
path: '/p/sets/:setId',
name: 'setPublic',
props: true,
component: () => import(/* webpackCunkName: "public" */ '@/views/sets/SetPublic'),
component: () => import(/* webpackChunkName: "public" */ '@/views/sets/SetPublic'),
},
{
path: '/p/items/:itemId',
name: 'itemPublic',
props: true,
component: () => import(/* webpackCunkName: "public" */ '@/views/items/ItemPublic'),
component: () => import(/* webpackChunkName: "public" */ '@/views/items/ItemPublic'),
},
{
path: '/embed/sets/:setId',
......@@ -249,6 +249,11 @@ export const routes = [
props: true,
component: () => import(/* webpackChunkName: "project" */ '@/views/groups/GroupEdit'),
},
{
path: 'search',
name: 'projectSearch',
component: () => import(/* webpackChunkName: "project" */ '@/views/projects/ProjectSearch'),
},
],
},
{
......
......@@ -16,6 +16,7 @@
<v-tabs
v-if="!isMobile"
class="mt-2"
:hide-slider="this.$route.name === 'projectSearch'"
>
<v-tab
v-for="tab in tabs"
......@@ -24,6 +25,16 @@
>
{{ tab.label }}
</v-tab>
<v-btn
:ripple="false"
class="no-hover"
active-class="primary--text no-active"
icon
height="48"
:to="{ name: 'projectSearch' }"
>
<v-icon >mdi-magnify</v-icon>
</v-btn>
</v-tabs>
<router-view />
</v-container>
......@@ -63,6 +74,7 @@ export default {
data() {
return {
removing: false,
searching: false,
}
},
computed: {
......@@ -140,3 +152,12 @@ export default {
},
};
</script>
<style scoped>
.v-btn--active.no-active::before {
opacity: 0 !important;
}
.v-btn.no-hover:hover::before {
opacity: 0 !important;
}
</style>
<template>
<div class="mt-2">
<v-toolbar flat>
<v-toolbar-title>{{ $t('project.search') }}</v-toolbar-title>
</v-toolbar>
<v-text-field
v-model="query"
class="mr-1"
:label="$t('search.label')"
prepend-inner-icon="mdi-magnify"
@change="onSearch"
/>
<div
class="d-flex flex-column"
>
<div
v-show="addingCriteria"
id="criteria"
>
<v-alert
border="left"
color="info"
dark
>
{{ $t('project.searchInfo') }}
</v-alert>
<div class="d-flex">
<v-text-field
v-model="creator"
class="mr-1"
:label="$t('item.fields.creator.label')"
/>
<v-text-field
v-model="publisher"
class="ml-1"
:label="$t('item.fields.publisher.label')"
/>
</div>
<div class="d-flex">
<v-text-field
v-model="publicationYear"
class="mr-1"
type="number"
:label="$t('item.fields.publicationYear.label')"
/>
<v-select
v-model="resourceTypeGeneral"
class="mx-1"
:label="$t('item.fields.resourceTypeGeneral.label')"
:items="resourceTypeGeneralOptions"
/>
<v-text-field
v-model="resourceType"
class="ml-1"
:label="$t('item.fields.resourceType.label')"
/>
</div>
<div class="d-flex">
<v-spacer />
<v-btn
tile
elevation="0"
color="primary"
@click="onAddCriteria"
>
{{ $t('btn.validate') }}
</v-btn>
</div>
</div>
<div class="d-flex mt-2">
<v-divider class="align-self-center" />
<v-btn
class="mx-2"
tile
elevation="0"
color="secondary"
@click="addingCriteria = !addingCriteria"
>
{{ $t('project.moreCriteria') }}
<v-icon>{{ addingCriteria ? 'mdi-chevron-up' : 'mdi-chevron-down' }}</v-icon>
</v-btn>
<v-divider class="align-self-center" />
</div>
</div>
<v-tabs v-model="activeTab">
<v-tab
v-for="tab in tabsRoot"
:key="tabs[tab].id"
>
{{ tabs[tab].label }}<v-chip v-if="tabs[tab].serverItemsLength" class="ml-1" x-small>{{ tabs[tab].serverItemsLength }}</v-chip>
</v-tab>
</v-tabs>
<v-tabs-items v-model="activeTab">
<v-tab-item
v-for="tab in tabs"
:key="tab.id"
eager
>
<v-toolbar flat>
<v-select
v-model="tab.sort"
:items="sortOptions"
item-text="label"
item-value="value"
:label="$t('search.sortOptions')"
hide-details
>
<template #append-outer>
<v-btn
icon
small
@click="tab.isDescending = !tab.isDescending"
>
<v-icon>{{ tab.isDescending ? 'mdi-sort-descending' : 'mdi-sort-ascending' }}</v-icon>
</v-btn>
</template>
</v-select>
</v-toolbar>
<v-divider />
<v-data-table
:items="tab.results"
:footer-props="{ itemsPerPageOptions: [2, 5, 10, -1] }"
:options.sync="tab.options"
:sort-by.sync="tab.sort"
:sort-desc="tab.isDescending"
:server-items-length="tab.serverItemsLength"
>
<template #item="{ item }">
<tr>
<v-card>
<div class="d-flex">
<avatar
:name="item.name"
:image="item.thumbnail"
class="ml-4 mt-4"
/>
<div class="d-flex flex-column">
<v-card-title>
<router-link :to="item.to">{{ item.name }}</router-link>
</v-card-title>
<v-card-subtitle class="pb-2">
<div>
<span
v-for="(subtitle, index) in item.subtitles"
:key="index"
>
<span v-if="index"> - </span>
{{ subtitle }}
</span>
</div>
</v-card-subtitle>
<v-card-text v-html="item.highlight.description ? item.highlight.description.join('[...]') : item.description">
</v-card-text>
</div>
</div>
</v-card>
</tr>
</template>
</v-data-table>
</v-tab-item>
</v-tabs-items>
</div>
</template>
<script>
// TODO: Refacto with /src/views/Search.vue
import authority from '@/assets/authAxios';
import TextHelpersMixin from '@/mixins/TextHelpersMixin';
import Avatar from '@/components/common/Avatar';
export default {
name: 'ProjectSearch',
mixins: [
TextHelpersMixin,
],
components: {
Avatar,
},
data: function() {
return {
query: '',
addingCriteria: false,
creator: '',
publisher: '',
publicationYear: '',
resourceTypeGeneral: '',
resourceTypeGeneralOptions: [
'Audiovisual',
'Book',
'BookChapter',
'Collection',
'ComputationalNotebook',
'ConferencePaper',
'ConferenceProceeding',
'DataPaper',
'Dataset',
'Dissertation',
'Event',
'Image',
'InteractiveResource',
'Journal',
'JournalArticle',
'Model',
'OutputManagementPlan',
'PeerReview',
'PhysicalObject',
'Preprint',
'Report',
'Service',
'Software',
'Sound',
'Standard',
'Text',
'Workflow',
'Other',
],
resourceType: '',
tabsRoot: ['set', 'item'],
tabs: {
set: {
id: 'set',
label: this.$t('set.plural'),
options: {},
serverItemsLength: 0,
sort: 'score',
isDescending: true,
results: [],
},
item: {
id: 'item',
label: this.$t('item.plural'),
options: {},
serverItemsLength: 0,
sort: 'score',
isDescending: true,
results: [],
},
},
activeTab: 0,
};
},
computed: {
axios() {
return authority;
},
criteria() {
const criteriaRoot = [
'creator',
'publisher',
'publicationYear',
'resourceTypeGeneral',
'resourceType'
];
return criteriaRoot
.filter((criteria) => !!this[criteria] || this[criteria].length)
.map((criteria) => `metadata__${criteria}=${this[criteria]}`)
.join('&');
},
sortOptions() {
return [
{ label: this.$t('search.relevance'), value: 'score' },
{ label: this.$t('form.field.title'), value: 'title' },
];
},
setOptions() {
return this.tabs.set.options;
},
itemOptions() {
return this.tabs.item.options;
},
},
watch: {
setOptions: {
handler: function(newValue, oldValue) {
if(Object.keys(oldValue).length) this.search('set');
},
deep: true,
},
itemOptions: {
handler: function(newValue, oldValue) {
if(Object.keys(oldValue).length) this.search('item');
},
deep: true,
},
},
methods: {
onSearch() {
this.tabsRoot.forEach((tab) => this.search(tab));
},
search(mode) {
const pageSize = this.tabs[`${mode}`].options.itemsPerPage;
const page = this.tabs[`${mode}`].options.page;
const ordering = `${this.tabs[`${mode}`].isDescending ? '-' : ''}${this.tabs[`${mode}`].sort}`;
if(this.query.length) return this.axios.get(`${mode}s/search/?query=${this.query}&page_size=${pageSize}&page=${page}&ordering=${ordering}${this.criteria.length ? '&' : ''}${this.criteria}`)
.then((response) => {
this.tabs[`${mode}`].results = this.formatResults(response.data.results, mode);
this.tabs[`${mode}`].serverItemsLength = response.data.count;
});
},
formatResults(results, mode) {
const that = this;
return results.map((result) => ({
id: result.id,
name: this.textTruncate(result.title || result.name, 50),
subtitles: function() {
let subtitles;
if (mode === 'set') {
subtitles = [
`${that.$t('item.fields.creator.label')} : ${result.creator.name}`,
`${that.$tc('item.count', result.itemsCount )}`,
];
} else if(mode === 'item') {
subtitles = [
`${that.$t('item.createdBy')} ${result.creator.name}`,
`${that.$t('item.fields.resourceTypeGeneral.label')} : ${result.metadata.resourceTypeGeneral}`,
`${that.$t('item.fields.resourceType.label')} : ${result.metadata.resourceType}`,
];
}
return subtitles;
}(),
description: this.textTruncate(this.stripHtml(result.description), 300),
thumbnail: result.thumbnail || null,
to: { name: `${mode === 'set' ? 'setItems' : 'itemViewer' }`, params: { [`${mode}Id`]: result.id } },
highlight: result.highlight,
}));
},
onAddCriteria() {
this.search('item');
this.activeTab=1;
},
},
};
</script>
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment