Implement skeleton loading for object groups in global search page [SCI-10480]

This commit is contained in:
wandji20 2024-04-07 19:36:02 +01:00
parent ed3701e5ec
commit cc90078219
17 changed files with 295 additions and 149 deletions

View file

@ -18,5 +18,6 @@ const GLOBAL_CONSTANTS = {
SLOW_STATUS_POLLING_INTERVAL: <%= Constants::SLOW_STATUS_POLLING_INTERVAL %>,
ASSET_POLLING_INTERVAL: <%= Constants::ASSET_POLLING_INTERVAL %>,
ASSET_SYNC_URL: '<%= Constants::ASSET_SYNC_URL %>',
GLOBAL_SEARCH_PREVIEW_LIMIT: <%= Constants::GLOBAL_SEARCH_PREVIEW_LIMIT %>
GLOBAL_SEARCH_PREVIEW_LIMIT: <%= Constants::GLOBAL_SEARCH_PREVIEW_LIMIT %>,
SEARCH_LIMIT: <%= Constants::SEARCH_LIMIT %>
};

View file

@ -60,3 +60,15 @@ html {
.ag-theme-alpine {
--ag-font-family: "SN Inter", "Open Sans", Arial, Helvetica, sans-serif !important;
}
.animate-skeleton {
background-image: linear-gradient(90deg, #ddd 0px, #e8e8e8 40px, #ddd 80px);
background-size: 500px;
animation: shine-lines 1.6s infinite linear
}
@keyframes shine-lines {
0% { background-position: -150px }
40%, 100% { background-position: 320px }
}

View file

@ -453,13 +453,13 @@ class SearchController < ApplicationController
end
def search_protocols
@protocol_results = []
@protocol_results = Protocol.none
@protocol_results = search_by_name(Protocol) if @protocol_search_count.positive?
@search_count = @protocol_search_count
end
def search_label_templates
@label_template_results = []
@label_template_results = LabelTemplate.none
@label_template_results = search_by_name(LabelTemplate) if @label_template_search_count.positive?
@search_count = @label_template_search_count
end
@ -494,7 +494,7 @@ class SearchController < ApplicationController
end
def search_repository_rows
@repository_row_results = []
@repository_row_results = RepositoryRow.none
@repository_row_results = search_by_name(RepositoryRow) if @repository_row_search_count.positive?
@search_count = @repository_row_search_count
end

View file

@ -1,14 +1,14 @@
<template>
<div v-if="total" class="bg-white rounded p-4 mb-4">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-files"></i>
{{ i18n.t('search.index.files') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div>
<div ref="content" class="bg-white rounded" :class="{ 'p-4 mb-4': total || loading }">
<template v-if="total">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-files"></i>
{{ i18n.t('search.index.files') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div class="grid grid-cols-[auto_auto_auto_auto_auto_auto] items-center">
<template v-for="row in preparedResults" :key="row.id">
<a target="_blank" :href="row.attributes.parent.url"
@ -48,16 +48,22 @@
<div v-if="viewAll" class="mt-4">
<button class="btn btn-light" @click="$emit('selectGroup', 'AssetsComponent')">View all</button>
</div>
</div>
</template>
<Loader v-if="loading" :total="total" :loaderRows="loaderRows" :loaderYPadding="loaderYPadding"
:loaderHeight="loaderHeight" :loaderGap="loaderGap" :reachedEnd="reachedEnd" />
</div>
</template>
<script>
import Loader from '../loader.vue';
import searchMixin from './search_mixin';
export default {
name: 'AssetsComponent',
mixins: [searchMixin],
components: {
Loader
},
data() {
return {
group: 'assets'

View file

@ -1,14 +1,14 @@
<template>
<div v-if="total" class="bg-white rounded p-4 mb-4">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-experiment"></i>
{{ i18n.t('search.index.experiments') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div>
<div ref="content" class="bg-white rounded" :class="{ 'p-4 mb-4': total || loading }">
<template v-if="total">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-experiment"></i>
{{ i18n.t('search.index.experiments') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div class="grid grid-cols-[auto_80px_auto_auto_auto] items-center">
<template v-for="row in preparedResults" :key="row.id">
<a :href="row.attributes.url" target="_blank" class="h-full py-2 px-4 overflow-hidden font-bold border-0 border-b border-solid border-sn-light-grey">
@ -41,15 +41,21 @@
<div v-if="viewAll" class="mt-4">
<button class="btn btn-light" @click="$emit('selectGroup', 'ExperimentsComponent')">View all</button>
</div>
</div>
</template>
<Loader v-if="loading" :total="total" :loaderRows="loaderRows" :loaderYPadding="loaderYPadding"
:loaderHeight="loaderHeight" :loaderGap="loaderGap" :reachedEnd="reachedEnd" />
</div>
</template>
<script>
import Loader from '../loader.vue';
import searchMixin from './search_mixin';
export default {
name: 'ExperimentsComponent',
components: {
Loader
},
mixins: [searchMixin],
data() {
return {

View file

@ -1,14 +1,14 @@
<template>
<div v-if="total" class="bg-white rounded p-4 mb-4">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-folder"></i>
{{ i18n.t('search.index.folders') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div>
<div ref="content" class="bg-white rounded" :class="{ 'p-4 mb-4': total || loading }">
<template v-if="total">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-folder"></i>
{{ i18n.t('search.index.folders') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div class="grid grid-cols-[auto_auto_auto_auto] items-center">
<template v-for="row in preparedResults" :key="row.id" class="hover:bg-sn-super-light-grey">
<a target="_blank" :href="row.attributes.url" class="h-full py-2 px-4 overflow-hidden font-bold border-0 border-b border-solid border-sn-light-grey">
@ -39,16 +39,22 @@
<div v-if="viewAll" class="mt-4">
<button class="btn btn-light" @click="$emit('selectGroup', 'FoldersComponent')">View all</button>
</div>
</div>
</template>
<Loader v-if="loading" :total="total" :loaderRows="loaderRows" :loaderYPadding="loaderYPadding"
:loaderHeight="loaderHeight" :loaderGap="loaderGap" :reachedEnd="reachedEnd" />
</div>
</template>
<script>
import Loader from '../loader.vue';
import searchMixin from './search_mixin';
export default {
name: 'FoldersComponent',
mixins: [searchMixin],
components: {
Loader
},
data() {
return {
group: 'project_folders'

View file

@ -1,14 +1,14 @@
<template>
<div class="bg-white rounded p-4 mb-4" v-if="total">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-label-templates"></i>
{{ i18n.t('search.index.label_templates') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div>
<div ref="content" class="bg-white rounded" :class="{ 'p-4 mb-4': total || loading }">
<template v-if="!disabled && total">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-label-templates"></i>
{{ i18n.t('search.index.label_templates') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div class="grid grid-cols-[auto_110px_auto_auto_auto_auto] items-center">
<template v-for="row in preparedResults" :key="row.id">
<a :href="row.attributes.url" class="h-full py-2 px-4 overflow-hidden font-bold border-0 border-b border-solid border-sn-light-grey">
@ -39,19 +39,25 @@
</div>
</template>
</div>
<div v-if="!selected && total > 4" class="mt-4">
<div v-if="viewAll" class="mt-4">
<button class="btn btn-light" @click="$emit('selectGroup', 'LabelTemplatesComponent')">View all</button>
</div>
</div>
</template>
<Loader v-if="loading" :total="total" :loaderRows="loaderRows" :loaderYPadding="loaderYPadding"
:loaderHeight="loaderHeight" :loaderGap="loaderGap" :reachedEnd="reachedEnd" />
</div>
</template>
<script>
import Loader from '../loader.vue';
import searchMixin from './search_mixin';
export default {
name: 'LabelTemplatesComponent',
mixins: [searchMixin],
components: {
Loader
},
data() {
return {
group: 'label_templates'

View file

@ -1,6 +1,7 @@
<template>
<div v-if="total" class="bg-white rounded p-4 mb-4">
<div class="flex items-center">
<div ref="content" class="bg-white rounded" :class="{ 'p-4 mb-4': total || loading }">
<template v-if="total">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-protocols-templates"></i>
{{ i18n.t('search.index.task_protocols') }}
@ -8,7 +9,6 @@
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div>
<div class="grid grid-cols-[auto_80px_auto_auto_auto_auto_auto] items-center">
<template v-for="row in preparedResults" :key="row.id">
<a :href="row.attributes.url" target="_blank" class="h-full py-2 px-4 overflow-hidden font-bold border-0 border-b border-solid border-sn-light-grey">
@ -51,16 +51,22 @@
<div v-if="viewAll" class="mt-4">
<button class="btn btn-light" @click="$emit('selectGroup', 'MyModuleProtocolsComponent')">View all</button>
</div>
</div>
</template>
<Loader v-if="loading" :total="total" :loaderRows="loaderRows" :loaderYPadding="loaderYPadding"
:loaderHeight="loaderHeight" :loaderGap="loaderGap" :reachedEnd="reachedEnd" />
</div>
</template>
<script>
import Loader from '../loader.vue';
import searchMixin from './search_mixin';
export default {
name: 'MyModuleProtocolsComponent',
mixins: [searchMixin],
components: {
Loader
},
data() {
return {
group: 'module_protocols'

View file

@ -1,6 +1,7 @@
<template>
<div v-if="total" class="bg-white rounded p-4 mb-4">
<div class="flex items-center">
<div ref="content" class="bg-white rounded" :class="{ 'p-4 mb-4': total || loading }">
<template v-if="total">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-task"></i>
{{ i18n.t('search.index.tasks') }}
@ -8,7 +9,6 @@
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div>
<div class="grid grid-cols-[auto_80px_auto_auto_auto_auto] items-center">
<template v-for="row in preparedResults" :key="row.id">
<a :href="row.attributes.url" target="_blank" class="h-full py-2 px-4 overflow-hidden font-bold border-0 border-b border-solid border-sn-light-grey">
@ -45,15 +45,21 @@
<div v-if="viewAll" class="mt-4">
<button class="btn btn-light" @click="$emit('selectGroup', 'MyModulesComponent')">View all</button>
</div>
</div>
</template>
<Loader v-if="loading" :total="total" :loaderRows="loaderRows" :loaderYPadding="loaderYPadding"
:loaderHeight="loaderHeight" :loaderGap="loaderGap" :reachedEnd="reachedEnd" />
</div>
</template>
<script>
import Loader from '../loader.vue';
import searchMixin from './search_mixin';
export default {
name: 'MyModulesComponent',
components: {
Loader
},
mixins: [searchMixin],
data() {
return {

View file

@ -1,14 +1,14 @@
<template>
<div v-if="total" class="bg-white rounded p-4 mb-4">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-projects"></i>
{{ i18n.t('search.index.projects') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div>
<div ref="content" class="bg-white rounded" :class="{ 'p-4 mb-4': total || loading }">
<template v-if="total">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-projects"></i>
{{ i18n.t('search.index.projects') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div class="grid grid-cols-[auto_80px_auto_auto_auto] items-center">
<template v-for="row in preparedResults" :key="row.id">
<a :href="row.attributes.url" class="h-full py-2 px-4 overflow-hidden font-bold border-0 border-b border-solid border-sn-light-grey">
@ -40,19 +40,25 @@
</div>
</template>
</div>
<div v-if="!selected && total > 4" class="mt-4">
<div v-if="viewAll" class="mt-4">
<button class="btn btn-light" @click="$emit('selectGroup', 'ProjectsComponent')">View all</button>
</div>
</div>
</template>
<Loader v-if="loading" :total="total" :loaderRows="loaderRows" :loaderYPadding="loaderYPadding"
:loaderHeight="loaderHeight" :loaderGap="loaderGap" :reachedEnd="reachedEnd" />
</div>
</template>
<script>
import Loader from '../loader.vue';
import searchMixin from './search_mixin';
export default {
name: 'ProjectsComponent',
mixins: [searchMixin],
components: {
Loader
},
data() {
return {
group: 'projects'

View file

@ -1,14 +1,14 @@
<template>
<div v-if="total" class="bg-white rounded p-4 mb-4">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-protocols-templates"></i>
{{ i18n.t('search.index.protocol_templates') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div>
<div ref="content" class="bg-white rounded" :class="{ 'p-4 mb-4': total || loading }">
<template v-if="total">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-protocols-templates"></i>
{{ i18n.t('search.index.protocol_templates') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div class="grid grid-cols-[auto_110px_auto_auto_auto_auto] items-center">
<template v-for="row in preparedResults" :key="row.id">
<a :href="row.attributes.url" class="h-full py-2 px-4 overflow-hidden font-bold border-0 border-b border-solid border-sn-light-grey">
@ -39,19 +39,25 @@
</div>
</template>
</div>
<div v-if="!selected && total > 4" class="mt-4">
<div v-if="viewAll" class="mt-4">
<button class="btn btn-light" @click="$emit('selectGroup', 'ProtocolsComponent')">View all</button>
</div>
</div>
</template>
<Loader v-if="loading" :total="total" :loaderRows="loaderRows" :loaderYPadding="loaderYPadding"
:loaderHeight="loaderHeight" :loaderGap="loaderGap" :reachedEnd="reachedEnd" />
</div>
</template>
<script>
import Loader from '../loader.vue';
import searchMixin from './search_mixin';
export default {
name: 'ProtocolsComponent',
mixins: [searchMixin],
components: {
Loader
},
data() {
return {
group: 'protocols'

View file

@ -1,14 +1,14 @@
<template>
<div v-if="total" class="bg-white rounded p-4 mb-4">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-reports"></i>
{{ i18n.t('search.index.reports') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div>
<div ref="content" class="bg-white rounded" :class="{ 'p-4 mb-4': total || loading }">
<template v-if="total">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-reports"></i>
{{ i18n.t('search.index.reports') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div class="grid grid-cols-[auto_110px_auto_auto_auto_auto_auto] items-center">
<template v-for="row in preparedResults" :key="row.id">
<a target="_blank" :href="row.attributes.url" class="h-full py-2 px-4 overflow-hidden font-bold border-0 border-b border-solid border-sn-light-grey">
@ -50,16 +50,22 @@
<div v-if="viewAll" class="mt-4">
<button class="btn btn-light" @click="$emit('selectGroup', 'ReportsComponent')">View all</button>
</div>
</div>
</template>
<Loader v-if="loading" :total="total" :loaderRows="loaderRows" :loaderYPadding="loaderYPadding"
:loaderHeight="loaderHeight" :loaderGap="loaderGap" :reachedEnd="reachedEnd" />
</div>
</template>
<script>
import Loader from '../loader.vue';
import searchMixin from './search_mixin';
export default {
name: 'ReportsComponent',
mixins: [searchMixin],
components: {
Loader
},
data() {
return {
group: 'reports'

View file

@ -1,14 +1,14 @@
<template>
<div class="bg-white rounded p-4 mb-4">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-inventory"></i>
{{ i18n.t('search.index.inventory_items') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div>
<div ref="content" class="bg-white rounded" :class="{ 'p-4 mb-4': total || loading }">
<template v-if="total">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-inventory"></i>
{{ i18n.t('search.index.inventory_items') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div class="grid grid-cols-[auto_110px_auto_auto_auto_auto] items-center">
<template v-for="row in preparedResults" :key="row.id">
<a :href="row.attributes.url" class="h-full py-2 px-4 overflow-hidden font-bold border-0 border-b border-solid border-sn-light-grey">
@ -41,19 +41,25 @@
</div>
</template>
</div>
<div v-if="!selected && total > 4" class="mt-4">
<div v-if="viewAll" class="mt-4">
<button class="btn btn-light" @click="$emit('selectGroup', 'RepositoryRowsComponent')">View all</button>
</div>
</div>
</template>
<Loader v-if="loading" :total="total" :loaderRows="loaderRows" :loaderYPadding="loaderYPadding"
:loaderHeight="loaderHeight" :loaderGap="loaderGap" :reachedEnd="reachedEnd" />
</div>
</template>
<script>
import Loader from '../loader.vue';
import searchMixin from './search_mixin';
export default {
name: 'RepositoryRowsComponent',
mixins: [searchMixin],
components: {
Loader
},
data() {
return {
group: 'repository_rows'

View file

@ -1,60 +1,68 @@
<template>
<div v-if="total" class="bg-white rounded p-4 mb-4">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-results"></i>
{{ i18n.t('search.index.task_results') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div class="grid grid-cols-[auto_auto_auto_auto_auto_auto] items-center">
<template v-for="row in preparedResults" :key="row.id">
<a :href="row.attributes.url" target="_blank" class="h-full py-2 px-4 overflow-hidden font-bold border-0 border-b border-solid border-sn-light-grey">
<StringWithEllipsis class="w-full" :text="row.attributes.name"></StringWithEllipsis>
</a>
<div class="h-full py-2 px-4 flex items-center gap-1 text-xs border-0 border-b border-solid border-sn-light-grey max-w-[200px]">
<b class="shrink-0">{{ i18n.t('search.index.created_at') }}:</b>
<span class="truncate">{{ row.attributes.created_at }}</span>
</div>
<div class="h-full py-2 px-4 flex items-center gap-1 text-xs border-0 border-b border-solid border-sn-light-grey max-w-[200px]">
<b class="shrink-0">{{ i18n.t('search.index.updated_at') }}:</b>
<span class="truncate">{{ row.attributes.updated_at }}</span>
</div>
<div class="h-full py-2 px-4 grid grid-cols-[auto_1fr] items-center gap-1 text-xs border-0 border-b border-solid border-sn-light-grey">
<b class="shrink-0">{{ i18n.t('search.index.team') }}:</b>
<a :href="row.attributes.team.url" class="shrink-0 overflow-hidden" target="_blank">
<StringWithEllipsis class="w-full" :text="row.attributes.team.name"></StringWithEllipsis>
<div ref="content" class="bg-white rounded" :class="{ 'p-4 mb-4': total || loading }">
<template v-if="total">
<div class="flex items-center">
<h2 class="flex items-center gap-2 mt-0 mb-4">
<i class="sn-icon sn-icon-results"></i>
{{ i18n.t('search.index.task_results') }}
[{{ total }}]
</h2>
<SortFlyout v-if="selected" :sort="sort" @changeSort="changeSort"></SortFlyout>
</div>
<div class="grid grid-cols-[auto_auto_auto_auto_auto_auto] items-center">
<template v-for="row in preparedResults" :key="row.id">
<a :href="row.attributes.url" target="_blank" class="h-full py-2 px-4 overflow-hidden font-bold border-0 border-b border-solid border-sn-light-grey">
<StringWithEllipsis class="w-full" :text="row.attributes.name"></StringWithEllipsis>
</a>
</div>
<div class="h-full py-2 px-4 grid grid-cols-[auto_1fr] items-center gap-1 text-xs border-0 border-b border-solid border-sn-light-grey">
<b class="shrink-0">{{ i18n.t('search.index.task') }}:</b>
<a :href="row.attributes.my_module.url" class="shrink-0 overflow-hidden" target="_blank">
<StringWithEllipsis class="w-full" :text="row.attributes.my_module.name"></StringWithEllipsis>
</a>
</div>
<div class="h-full py-2 px-4 border-0 border-b border-solid border-sn-light-grey">
<div class="grid grid-cols-[auto_1fr] items-center gap-1 text-xs w-full">
<b class="shrink-0">{{ i18n.t('search.index.experiment') }}:</b>
<a :href="row.attributes.experiment.url" class="shrink-0 overflow-hidden" target="_blank">
<StringWithEllipsis class="w-full" :text="row.attributes.experiment.name"></StringWithEllipsis>
<div class="h-full py-2 px-4 flex items-center gap-1 text-xs border-0 border-b border-solid border-sn-light-grey max-w-[200px]">
<b class="shrink-0">{{ i18n.t('search.index.created_at') }}:</b>
<span class="truncate">{{ row.attributes.created_at }}</span>
</div>
<div class="h-full py-2 px-4 flex items-center gap-1 text-xs border-0 border-b border-solid border-sn-light-grey max-w-[200px]">
<b class="shrink-0">{{ i18n.t('search.index.updated_at') }}:</b>
<span class="truncate">{{ row.attributes.updated_at }}</span>
</div>
<div class="h-full py-2 px-4 grid grid-cols-[auto_1fr] items-center gap-1 text-xs border-0 border-b border-solid border-sn-light-grey">
<b class="shrink-0">{{ i18n.t('search.index.team') }}:</b>
<a :href="row.attributes.team.url" class="shrink-0 overflow-hidden" target="_blank">
<StringWithEllipsis class="w-full" :text="row.attributes.team.name"></StringWithEllipsis>
</a>
</div>
</div>
</template>
</div>
<div v-if="viewAll" class="mt-4">
<button class="btn btn-light" @click="$emit('selectGroup', 'ResultsComponent')">View all</button>
</div>
<div class="h-full py-2 px-4 grid grid-cols-[auto_1fr] items-center gap-1 text-xs border-0 border-b border-solid border-sn-light-grey">
<b class="shrink-0">{{ i18n.t('search.index.task') }}:</b>
<a :href="row.attributes.my_module.url" class="shrink-0 overflow-hidden" target="_blank">
<StringWithEllipsis class="w-full" :text="row.attributes.my_module.name"></StringWithEllipsis>
</a>
</div>
<div class="h-full py-2 px-4 border-0 border-b border-solid border-sn-light-grey">
<div class="grid grid-cols-[auto_1fr] items-center gap-1 text-xs w-full">
<b class="shrink-0">{{ i18n.t('search.index.experiment') }}:</b>
<a :href="row.attributes.experiment.url" class="shrink-0 overflow-hidden" target="_blank">
<StringWithEllipsis class="w-full" :text="row.attributes.experiment.name"></StringWithEllipsis>
</a>
</div>
</div>
</template>
</div>
<div v-if="viewAll" class="mt-4">
<button class="btn btn-light" @click="$emit('selectGroup', 'ResultsComponent')">View all</button>
</div>
</template>
<Loader v-if="loading" :total="total" :loaderRows="loaderRows" :loaderYPadding="loaderYPadding"
:loaderHeight="loaderHeight" :loaderGap="loaderGap" :reachedEnd="reachedEnd" />
</div>
</template>
<script>
import Loader from '../loader.vue';
import searchMixin from './search_mixin';
export default {
name: 'ResultsComponent',
mixins: [searchMixin],
components: {
Loader
},
data() {
return {
group: 'results'

View file

@ -22,13 +22,17 @@ export default {
loading: false,
page: 1,
disabled: false,
fullDataLoaded: false
fullDataLoaded: false,
loaderHeight: 24,
loaderGap: 10,
loaderYPadding: 10
};
},
watch: {
selected() {
if (this.selected) {
if (!this.fullDataLoaded) {
this.total = 0;
this.results = [];
this.loadData();
}
@ -37,6 +41,7 @@ export default {
query() {
this.results = [];
this.page = 1;
this.total = 0;
this.fullDataLoaded = false;
this.loadData();
}
@ -57,6 +62,22 @@ export default {
},
viewAll() {
return !this.selected && this.total > GLOBAL_CONSTANTS.GLOBAL_SEARCH_PREVIEW_LIMIT;
},
loaderRows() {
// h-[24px] gap-y-[10px] py-[10px]
if (this.loading && (!this.selected || this.total)) return GLOBAL_CONSTANTS.GLOBAL_SEARCH_PREVIEW_LIMIT;
if (this.selected && this.loading) {
const availableHeight = window.innerHeight - this.$refs.content.getBoundingClientRect().top;
// loaderHeight + loaderGap + headerLoaderHeight + headerContentGap
const offSet = this.loaderHeight + this.loaderGap + 32 + 20;
return Math.floor((availableHeight - offSet) / (this.loaderHeight + this.loaderGap + (2 * this.loaderYPadding)));
}
return 0;
},
reachedEnd() {
return Math.ceil(this.total / GLOBAL_CONSTANTS.SEARCH_LIMIT) === this.page;
}
},
methods: {

View file

@ -0,0 +1,43 @@
<template>
<div class="flex flex-col gap-6">
<div v-if="!total" class="grid grid-cols-[32px_auto] gap-2.5">
<div class="h-8 w-8 animate-skeleton rounded"></div>
<div class="h-8 w-40 animate-skeleton rounded"></div>
</div>
<div class="flex flex-col" :class="[`gap-[${loaderGap}px]`]">
<div v-for="_count in loaderRows"
class="flex items-center no-wrap border-0 border-b border-solid border-sn-light-grey gap-x-8"
:class="[`gap-y-[${loaderGap}px]`, `py-[${loaderYPadding}px]`, { 'first:border-t': total }]">
<div class="w-[500px] grow-1" :class="`h-[${loaderHeight}px]`">
<div class="h-full w-80 animate-skeleton rounded mr-auto"></div>
</div>
<div :class="`h-[${loaderHeight}px]`" class="w-24 max-w-24 animate-skeleton rounded"></div>
<div :class="`h-[${loaderHeight}px]`" class="w-44 max-w-44 animate-skeleton rounded"></div>
<div :class="`h-[${loaderHeight}px]`" class="w-44 max-w-44 animate-skeleton rounded"></div>
<div :class="`h-[${loaderHeight}px]`" class="w-56 max-w-56 animate-skeleton rounded"></div>
<div :class="`h-[${loaderHeight}px]`" class="w-96 max-w-96 animate-skeleton rounded"></div>
</div>
</div>
<div v-if="reachedEnd" class="flex items-center">
<p class="text-sm text-sn-blue flex items-center gap-3 m-auto">
<span class="sn-icon sn-icon-flag"></span>
<span>{{ i18n.t('search.index.reached_end') }}</span>
</p>
</div>
</div>
</template>
<script>
export default {
name: 'Loader',
props: {
total: { type: Number, default: 0 },
loaderRows: { type: Number, default: 0 },
loaderHeight: { type: Number, required: true },
loaderGap: { type: Number, required: true },
loaderYPadding: { type: Number, required: true },
reachedEnd: { type: Boolean }
}
};
</script>

View file

@ -485,6 +485,7 @@ en:
created_asc: "Created first"
atoz: "Name A to Z"
ztoa: "Name Z to A"
reached_end: "Youve reached the end of the list"
comments:
save_changes: "Save changes"
empty_state: