mirror of
https://github.com/scinote-eln/scinote-web.git
synced 2024-12-26 17:51:13 +08:00
Implement skeleton loading for object groups in global search page [SCI-10480]
This commit is contained in:
parent
ed3701e5ec
commit
cc90078219
17 changed files with 295 additions and 149 deletions
|
@ -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 %>
|
||||
};
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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: {
|
||||
|
|
43
app/javascript/vue/global_search/loader.vue
Normal file
43
app/javascript/vue/global_search/loader.vue
Normal 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>
|
|
@ -485,6 +485,7 @@ en:
|
|||
created_asc: "Created first"
|
||||
atoz: "Name A to Z"
|
||||
ztoa: "Name Z to A"
|
||||
reached_end: "You’ve reached the end of the list"
|
||||
comments:
|
||||
save_changes: "Save changes"
|
||||
empty_state:
|
||||
|
|
Loading…
Reference in a new issue