mirror of
https://github.com/directus/directus.git
synced 2026-01-27 20:38:21 -05:00
Use items (#354)
* Extract tabular items logic into composition * Fix racing condition in watchers * Remove empty test * Use v-select in detail sidebar tabular * Fix pagination styles * Reset to page 1 on sort / limit changes * Cast limit to string * Fix button test * Add content padding to tabular layout
This commit is contained in:
@@ -41,7 +41,7 @@ describe('Button', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(component.find('.button').classes()).toContain('full-width');
|
||||
expect(component.classes()).toContain('full-width');
|
||||
});
|
||||
|
||||
it('Adds the rounded class for rounded buttons', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="v-button">
|
||||
<div class="v-button" :class="{ secondary, 'full-width': fullWidth }">
|
||||
<slot name="prepend-outer" />
|
||||
<component
|
||||
:is="component"
|
||||
@@ -10,12 +10,10 @@
|
||||
sizeClass,
|
||||
`align-${align}`,
|
||||
{
|
||||
'full-width': fullWidth,
|
||||
rounded,
|
||||
icon,
|
||||
outlined,
|
||||
loading,
|
||||
secondary,
|
||||
active,
|
||||
dashed,
|
||||
tile,
|
||||
@@ -139,6 +137,20 @@ export default defineComponent({
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
&.secondary {
|
||||
--v-button-color: var(--foreground-color);
|
||||
--v-button-color-hover: var(--foreground-color);
|
||||
--v-button-color-activated: var(--foreground-color);
|
||||
--v-button-background-color: var(--background-normal-alt);
|
||||
--v-button-background-color-hover: var(--background-normal-alt);
|
||||
--v-button-background-color-activated: var(--background-normal-alt);
|
||||
}
|
||||
|
||||
&.full-width {
|
||||
display: flex;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.button {
|
||||
position: relative;
|
||||
display: flex;
|
||||
@@ -170,15 +182,6 @@ export default defineComponent({
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
--v-button-color: var(--foreground-color);
|
||||
--v-button-color-hover: var(--foreground-color);
|
||||
--v-button-color-activated: var(--foreground-color);
|
||||
--v-button-background-color: var(--background-normal-alt);
|
||||
--v-button-background-color-hover: var(--background-normal-alt);
|
||||
--v-button-background-color-activated: var(--background-normal-alt);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.98);
|
||||
}
|
||||
@@ -253,11 +256,6 @@ export default defineComponent({
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.full-width {
|
||||
display: flex;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.content,
|
||||
.spinner {
|
||||
max-width: 100%;
|
||||
|
||||
@@ -122,6 +122,7 @@ export default defineComponent({
|
||||
|
||||
input {
|
||||
flex-grow: 1;
|
||||
width: 20px; // auto grows
|
||||
height: 100%;
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
|
||||
@@ -170,6 +170,7 @@ export default defineComponent({
|
||||
|
||||
&.active {
|
||||
--v-button-background-color: var(--v-pagination-active-color);
|
||||
--v-button-color: var(--foreground-inverted);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,12 +16,7 @@ export function useItem(collection: Ref<string>, primaryKey: Ref<string | number
|
||||
const edits = ref({});
|
||||
const isNew = computed(() => primaryKey.value === '+');
|
||||
|
||||
if (isNew.value === false) {
|
||||
getItem();
|
||||
}
|
||||
|
||||
watch(collection, refresh);
|
||||
watch(primaryKey, refresh);
|
||||
watch([collection, primaryKey], refresh);
|
||||
|
||||
return {
|
||||
edits,
|
||||
|
||||
4
src/compositions/use-items/index.ts
Normal file
4
src/compositions/use-items/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { useItems } from './use-items';
|
||||
|
||||
export { useItems };
|
||||
export default useItems;
|
||||
0
src/compositions/use-items/readme.md
Normal file
0
src/compositions/use-items/readme.md
Normal file
138
src/compositions/use-items/use-items.ts
Normal file
138
src/compositions/use-items/use-items.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
import { computed, ref, Ref, watch } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import useCollection from '@/compositions/use-collection';
|
||||
import Vue from 'vue';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
type Options = {
|
||||
limit: Ref<number>;
|
||||
fields: Ref<readonly string[]>;
|
||||
sort: Ref<string>;
|
||||
page: Ref<number>;
|
||||
};
|
||||
|
||||
export function useItems(collection: Ref<string>, options: Options) {
|
||||
const projectsStore = useProjectsStore();
|
||||
const { primaryKeyField } = useCollection(collection);
|
||||
|
||||
const { limit, fields, sort, page } = options;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const items = ref<any>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
const itemCount = ref<number>(null);
|
||||
|
||||
const totalPages = computed(() => {
|
||||
if (itemCount.value === null) return 1;
|
||||
if (itemCount.value < limit.value) return 1;
|
||||
return Math.ceil(itemCount.value / limit.value);
|
||||
});
|
||||
|
||||
getItems();
|
||||
|
||||
watch(collection, async (after, before) => {
|
||||
if (!before || isEqual(after, before)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Waiting for the tick here makes sure the options have been adjusted for the new
|
||||
// collection
|
||||
await Vue.nextTick();
|
||||
reset();
|
||||
getItems();
|
||||
});
|
||||
|
||||
watch([page, limit, fields], async (after, before) => {
|
||||
if (!before || isEqual(after, before)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Vue.nextTick();
|
||||
if (loading.value === false) {
|
||||
getItems();
|
||||
}
|
||||
});
|
||||
|
||||
watch(sort, async (after, before) => {
|
||||
if (!before || isEqual(after, before)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (limit.value > (itemCount.value || 0)) return;
|
||||
await Vue.nextTick();
|
||||
if (loading.value === false) {
|
||||
getItems();
|
||||
}
|
||||
});
|
||||
|
||||
return { itemCount, items, totalPages, loading, error };
|
||||
|
||||
async function getItems() {
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
loading.value = true;
|
||||
|
||||
const fieldsToFetch = [...fields.value];
|
||||
|
||||
if (
|
||||
fields.value !== ['*'] &&
|
||||
primaryKeyField.value &&
|
||||
fieldsToFetch.includes(primaryKeyField.value.field) === false
|
||||
) {
|
||||
fieldsToFetch.push(primaryKeyField.value.field);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.get(`/${currentProjectKey}/items/${collection.value}`, {
|
||||
params: {
|
||||
limit: limit.value,
|
||||
fields: fieldsToFetch,
|
||||
sort: sort.value,
|
||||
page: page.value,
|
||||
},
|
||||
});
|
||||
|
||||
items.value = response.data.data;
|
||||
|
||||
if (itemCount.value === null) {
|
||||
if (response.data.data.length === +limit.value) {
|
||||
// Requesting the page filter count in the actual request every time slows
|
||||
// the request down by like 600ms-1s. This makes sure we only fetch the count
|
||||
// once if needed.
|
||||
getTotalCount();
|
||||
} else {
|
||||
// If the response includes less items than the limit, it's safe to assume
|
||||
// it's all the data in the DB
|
||||
itemCount.value = response.data.data.length;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
error.value = err;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function getTotalCount() {
|
||||
if (!primaryKeyField.value) return;
|
||||
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
const response = await api.get(`/${currentProjectKey}/items/${collection.value}`, {
|
||||
params: {
|
||||
limit: 0,
|
||||
fields: primaryKeyField.value.field,
|
||||
meta: 'filter_count',
|
||||
},
|
||||
});
|
||||
|
||||
itemCount.value = response.data.meta.filter_count;
|
||||
}
|
||||
|
||||
function reset() {
|
||||
items.value = [];
|
||||
itemCount.value = null;
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
<draggable v-model="activeFields">
|
||||
<v-checkbox
|
||||
v-for="field in activeFields"
|
||||
v-model="activeFieldKeys"
|
||||
v-model="fields"
|
||||
:key="field.field"
|
||||
:value="field.field"
|
||||
:label="field.name"
|
||||
@@ -13,10 +13,10 @@
|
||||
</draggable>
|
||||
|
||||
<v-checkbox
|
||||
v-for="field in visibleFields.filter(
|
||||
(field) => activeFieldKeys.includes(field.field) === false
|
||||
v-for="field in availableFields.filter(
|
||||
(field) => fields.includes(field.field) === false
|
||||
)"
|
||||
v-model="activeFieldKeys"
|
||||
v-model="fields"
|
||||
:key="field.field"
|
||||
:value="field.field"
|
||||
:label="field.name"
|
||||
@@ -24,19 +24,28 @@
|
||||
</drawer-detail>
|
||||
|
||||
<drawer-detail icon="line_weight" :title="$t('layouts.tabular.spacing')">
|
||||
<select v-model="spacing">
|
||||
<option value="compact">{{ $t('layouts.tabular.compact') }}</option>
|
||||
<option value="cozy">{{ $t('layouts.tabular.cozy') }}</option>
|
||||
<option value="comfortable">{{ $t('layouts.tabular.comfortable') }}</option>
|
||||
</select>
|
||||
<v-select
|
||||
full-width
|
||||
v-model="tableSpacing"
|
||||
:items="[
|
||||
{
|
||||
text: $t('layouts.tabular.compact'),
|
||||
value: 'compact',
|
||||
},
|
||||
{
|
||||
text: $t('layouts.tabular.cozy'),
|
||||
value: 'cozy',
|
||||
},
|
||||
{
|
||||
text: $t('layouts.tabular.comfortable'),
|
||||
value: 'comfortable',
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</drawer-detail>
|
||||
|
||||
<drawer-detail icon="format_list_numbered" :title="$t('layouts.tabular.per_page')">
|
||||
<select v-model="perPage">
|
||||
<option v-for="amount in [10, 25, 50, 100, 250]" :key="amount" :value="amount">
|
||||
{{ amount }}
|
||||
</option>
|
||||
</select>
|
||||
<v-select full-width v-model="limit" :items="['10', '25', '50', '100', '250']" />
|
||||
</drawer-detail>
|
||||
</portal>
|
||||
|
||||
@@ -50,19 +59,19 @@
|
||||
:sort="tableSort"
|
||||
:items="items"
|
||||
:loading="loading"
|
||||
:headers.sync="headers"
|
||||
:row-height="rowHeight"
|
||||
:server-sort="isBigCollection"
|
||||
:headers.sync="tableHeaders"
|
||||
:row-height="tableRowHeight"
|
||||
:server-sort="totalPages > 1"
|
||||
@click:row="onRowClick"
|
||||
@update:sort="onSortChange"
|
||||
>
|
||||
<template #footer>
|
||||
<div class="pagination" v-if="isBigCollection">
|
||||
<div class="pagination" v-if="totalPages > 1">
|
||||
<v-pagination
|
||||
:length="pages"
|
||||
:length="totalPages"
|
||||
:total-visible="5"
|
||||
show-first-last
|
||||
:value="currentPage"
|
||||
:value="page"
|
||||
@input="toPage"
|
||||
/>
|
||||
</div>
|
||||
@@ -73,27 +82,27 @@
|
||||
|
||||
<script lang="ts">
|
||||
import Vue from 'vue';
|
||||
import { defineComponent, PropType, ref, watch, computed, inject } from '@vue/composition-api';
|
||||
import api from '@/api';
|
||||
import { defineComponent, PropType, ref, computed, inject, toRefs } from '@vue/composition-api';
|
||||
import useProjectsStore from '@/stores/projects';
|
||||
import useFieldsStore from '@/stores/fields';
|
||||
import { HeaderRaw, Item } from '@/components/v-table/types';
|
||||
import { Field } from '@/stores/fields/types';
|
||||
import router from '@/router';
|
||||
import useSync from '@/compositions/use-sync';
|
||||
import { debounce } from 'lodash';
|
||||
import Draggable from 'vuedraggable';
|
||||
import useCollection from '@/compositions/use-collection';
|
||||
import useItems from '@/compositions/use-items';
|
||||
|
||||
type ViewOptions = {
|
||||
widths?: {
|
||||
[field: string]: number;
|
||||
};
|
||||
perPage?: number;
|
||||
limit?: number;
|
||||
spacing?: 'comfortable' | 'cozy' | 'compact';
|
||||
};
|
||||
|
||||
export type ViewQuery = {
|
||||
fields?: string;
|
||||
fields?: string[];
|
||||
sort?: string;
|
||||
};
|
||||
|
||||
@@ -122,70 +131,136 @@ export default defineComponent({
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const projectsStore = useProjectsStore();
|
||||
|
||||
const table = ref<Vue>(null);
|
||||
const mainElement = inject('main-element', ref<Element>(null));
|
||||
|
||||
const projectsStore = useProjectsStore();
|
||||
const fieldsStore = useFieldsStore();
|
||||
const { currentProjectKey } = projectsStore.state;
|
||||
|
||||
const _selection = useSync(props, 'selection', emit);
|
||||
const _viewOptions = useSync(props, 'viewOptions', emit);
|
||||
const _viewQuery = useSync(props, 'viewQuery', emit);
|
||||
|
||||
const { visibleFields, primaryKeyField } = useCollectionInfo();
|
||||
const { collection } = toRefs(props);
|
||||
const { primaryKeyField, fields: fieldsInCollection } = useCollection(collection);
|
||||
|
||||
const availableFields = computed(() =>
|
||||
fieldsInCollection.value.filter(({ hidden_browse }) => hidden_browse === false)
|
||||
);
|
||||
|
||||
const { sort, limit, page, fields } = useItemOptions();
|
||||
|
||||
const { items, loading, error, totalPages, itemCount } = useItems(collection, {
|
||||
sort,
|
||||
limit,
|
||||
page,
|
||||
fields,
|
||||
});
|
||||
|
||||
const {
|
||||
isBigCollection,
|
||||
pages,
|
||||
getItems,
|
||||
error,
|
||||
items,
|
||||
loading,
|
||||
itemCount,
|
||||
currentPage,
|
||||
toPage,
|
||||
sort,
|
||||
perPage,
|
||||
activeFieldKeys,
|
||||
tableSort,
|
||||
tableHeaders,
|
||||
tableRowHeight,
|
||||
onRowClick,
|
||||
onSortChange,
|
||||
activeFields,
|
||||
} = useItems();
|
||||
|
||||
const { headers, rowHeight, spacing, onRowClick, tableSort, onSortChange } = useTable();
|
||||
|
||||
getItems();
|
||||
tableSpacing,
|
||||
} = useTable();
|
||||
|
||||
return {
|
||||
error,
|
||||
_selection,
|
||||
table,
|
||||
tableHeaders,
|
||||
items,
|
||||
loading,
|
||||
headers,
|
||||
onRowClick,
|
||||
primaryKeyField,
|
||||
_selection,
|
||||
refresh,
|
||||
table,
|
||||
itemCount,
|
||||
pages,
|
||||
toPage,
|
||||
currentPage,
|
||||
isBigCollection,
|
||||
onSortChange,
|
||||
rowHeight,
|
||||
_viewOptions,
|
||||
spacing,
|
||||
error,
|
||||
totalPages,
|
||||
tableSort,
|
||||
perPage,
|
||||
visibleFields,
|
||||
activeFieldKeys,
|
||||
onRowClick,
|
||||
onSortChange,
|
||||
tableRowHeight,
|
||||
page,
|
||||
toPage,
|
||||
itemCount,
|
||||
availableFields,
|
||||
fields,
|
||||
limit,
|
||||
activeFields,
|
||||
tableSpacing,
|
||||
};
|
||||
|
||||
async function refresh() {
|
||||
await getItems();
|
||||
function toPage(newPage: number) {
|
||||
page.value = newPage;
|
||||
mainElement.value?.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
|
||||
function useItemOptions() {
|
||||
const page = ref(1);
|
||||
|
||||
const sort = computed({
|
||||
get() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return _viewQuery.value?.sort || primaryKeyField.value!.field;
|
||||
},
|
||||
set(newSort: string) {
|
||||
page.value = 1;
|
||||
_viewQuery.value = {
|
||||
..._viewQuery.value,
|
||||
sort: newSort,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const limit = computed({
|
||||
get() {
|
||||
return _viewOptions.value?.limit || 25;
|
||||
},
|
||||
set(newLimit: number) {
|
||||
page.value = 1;
|
||||
_viewOptions.value = {
|
||||
..._viewOptions.value,
|
||||
limit: newLimit,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const fields = computed({
|
||||
get() {
|
||||
if (_viewQuery.value?.fields) {
|
||||
// This shouldn't be the case, but double check just in case it's stored
|
||||
// differently in the DB from previous versions
|
||||
if (typeof _viewQuery.value.fields === 'string') {
|
||||
return (_viewQuery.value.fields as string).split(',');
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
_viewQuery.value?.fields ||
|
||||
availableFields.value.slice(0, 4).map(({ field }) => field)
|
||||
);
|
||||
},
|
||||
set(newFields: string[]) {
|
||||
_viewQuery.value = {
|
||||
..._viewQuery.value,
|
||||
fields: newFields,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
return { sort, limit, page, fields };
|
||||
}
|
||||
|
||||
function useTable() {
|
||||
const tableSort = computed(() => {
|
||||
if (sort.value.startsWith('-')) {
|
||||
return { by: sort.value.substring(1), desc: true };
|
||||
} else {
|
||||
return { by: sort.value, desc: false };
|
||||
}
|
||||
});
|
||||
|
||||
const localWidths = ref<{ [field: string]: number }>({});
|
||||
|
||||
const saveWidthsToViewOptions = debounce(() => {
|
||||
@@ -195,7 +270,18 @@ export default defineComponent({
|
||||
};
|
||||
}, 350);
|
||||
|
||||
const headers = computed<HeaderRaw[]>({
|
||||
const activeFields = computed<Field[]>({
|
||||
get() {
|
||||
return fields.value
|
||||
.map((key) => availableFields.value.find((field) => field.field === key))
|
||||
.filter((f) => f) as Field[];
|
||||
},
|
||||
set(val) {
|
||||
fields.value = val.map((field) => field.field);
|
||||
},
|
||||
});
|
||||
|
||||
const tableHeaders = computed<HeaderRaw[]>({
|
||||
get() {
|
||||
return activeFields.value.map((field) => ({
|
||||
text: field.name,
|
||||
@@ -221,21 +307,7 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
|
||||
const rowHeight = computed<number>(() => {
|
||||
const spacing = props.viewOptions?.spacing || 'comfortable';
|
||||
|
||||
switch (spacing) {
|
||||
case 'compact':
|
||||
return 32;
|
||||
case 'cozy':
|
||||
default:
|
||||
return 48;
|
||||
case 'comfortable':
|
||||
return 64;
|
||||
}
|
||||
});
|
||||
|
||||
const spacing = computed({
|
||||
const tableSpacing = computed({
|
||||
get() {
|
||||
return _viewOptions.value?.spacing || 'cozy';
|
||||
},
|
||||
@@ -247,21 +319,26 @@ export default defineComponent({
|
||||
},
|
||||
});
|
||||
|
||||
const tableSort = computed(() => {
|
||||
if (sort.value.startsWith('-')) {
|
||||
return { by: sort.value.substring(1), desc: true };
|
||||
} else {
|
||||
return { by: sort.value, desc: false };
|
||||
const tableRowHeight = computed<number>(() => {
|
||||
switch (tableSpacing.value) {
|
||||
case 'compact':
|
||||
return 32;
|
||||
case 'cozy':
|
||||
default:
|
||||
return 48;
|
||||
case 'comfortable':
|
||||
return 64;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
headers,
|
||||
rowHeight,
|
||||
spacing,
|
||||
tableSort,
|
||||
tableHeaders,
|
||||
tableSpacing,
|
||||
tableRowHeight,
|
||||
onRowClick,
|
||||
onSortChange,
|
||||
tableSort,
|
||||
activeFields,
|
||||
};
|
||||
|
||||
function onRowClick(item: Item) {
|
||||
@@ -272,9 +349,10 @@ export default defineComponent({
|
||||
value: _selection.value.includes(item) === false,
|
||||
});
|
||||
} else {
|
||||
const primaryKey = item[primaryKeyField.value.field];
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const primaryKey = item[primaryKeyField.value!.field];
|
||||
router.push(
|
||||
`/${currentProjectKey}/collections/${props.collection}/${primaryKey}`
|
||||
`/${projectsStore.state.currentProjectKey}/collections/${props.collection}/${primaryKey}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -286,212 +364,21 @@ export default defineComponent({
|
||||
sort.value = sortString;
|
||||
}
|
||||
}
|
||||
|
||||
function useCollectionInfo() {
|
||||
const fieldsInCurrentCollection = computed<Field[]>(() => {
|
||||
return fieldsStore.state.fields.filter(
|
||||
(field) => field.collection === props.collection
|
||||
);
|
||||
});
|
||||
|
||||
const visibleFields = computed<Field[]>(() => {
|
||||
return fieldsInCurrentCollection.value.filter(
|
||||
(field) => field.hidden_browse === false
|
||||
);
|
||||
});
|
||||
|
||||
const primaryKeyField = computed<Field>(() => {
|
||||
// It's safe to assume that every collection has a primary key.
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return fieldsInCurrentCollection.value.find((field) => field.primary_key === true)!;
|
||||
});
|
||||
|
||||
return { fieldsInCurrentCollection, visibleFields, primaryKeyField };
|
||||
}
|
||||
|
||||
function useItems() {
|
||||
const error = ref(null);
|
||||
const items = ref([]);
|
||||
const loading = ref(true);
|
||||
const itemCount = ref<number>(null);
|
||||
const currentPage = ref(1);
|
||||
const perPage = computed<number>({
|
||||
get() {
|
||||
return props.viewOptions?.perPage || 25;
|
||||
},
|
||||
set(val) {
|
||||
_viewOptions.value = {
|
||||
..._viewOptions.value,
|
||||
perPage: val,
|
||||
};
|
||||
|
||||
currentPage.value = 1;
|
||||
|
||||
Vue.nextTick().then(() => {
|
||||
getItems();
|
||||
});
|
||||
},
|
||||
});
|
||||
const pages = computed<number>(() => Math.ceil(itemCount.value || 0 / perPage.value));
|
||||
const isBigCollection = computed<boolean>(() => (itemCount.value || 0) > perPage.value);
|
||||
|
||||
const sort = computed<string>({
|
||||
get() {
|
||||
return _viewQuery.value?.sort || primaryKeyField.value.field;
|
||||
},
|
||||
set(newSort) {
|
||||
_viewQuery.value = {
|
||||
..._viewQuery.value,
|
||||
sort: newSort,
|
||||
};
|
||||
|
||||
// Let the table component handle the sorting for small datasets
|
||||
if (isBigCollection.value === false) return;
|
||||
|
||||
currentPage.value = 1;
|
||||
|
||||
Vue.nextTick().then(() => {
|
||||
getItems();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const activeFieldKeys = computed<string[]>({
|
||||
get() {
|
||||
return (
|
||||
_viewQuery.value?.fields?.split(',') ||
|
||||
visibleFields.value.map((field) => field.field)
|
||||
);
|
||||
},
|
||||
set(newFields: string[]) {
|
||||
_viewQuery.value = {
|
||||
..._viewQuery.value,
|
||||
fields: newFields.join(','),
|
||||
};
|
||||
|
||||
Vue.nextTick().then(() => {
|
||||
getItems();
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const activeFields = computed<Field[]>({
|
||||
get() {
|
||||
return activeFieldKeys.value
|
||||
.map((key) => visibleFields.value.find((field) => field.field === key))
|
||||
.filter((f) => f) as Field[];
|
||||
},
|
||||
set(val) {
|
||||
activeFieldKeys.value = val.map((field) => field.field);
|
||||
},
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.collection,
|
||||
() => {
|
||||
items.value = [];
|
||||
itemCount.value = null;
|
||||
currentPage.value = 1;
|
||||
getItems();
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
isBigCollection,
|
||||
pages,
|
||||
getItems,
|
||||
getTotalCount,
|
||||
error,
|
||||
items,
|
||||
loading,
|
||||
itemCount,
|
||||
sort,
|
||||
toPage,
|
||||
currentPage,
|
||||
perPage,
|
||||
activeFieldKeys,
|
||||
activeFields,
|
||||
};
|
||||
|
||||
async function getTotalCount() {
|
||||
const response = await api.get(`/${currentProjectKey}/items/${props.collection}`, {
|
||||
params: {
|
||||
limit: 0,
|
||||
fields: primaryKeyField.value.field,
|
||||
meta: 'filter_count',
|
||||
},
|
||||
});
|
||||
|
||||
itemCount.value = response.data.meta.filter_count;
|
||||
}
|
||||
|
||||
async function getItems() {
|
||||
error.value = null;
|
||||
loading.value = true;
|
||||
|
||||
const fieldsToFetch = [...activeFieldKeys.value];
|
||||
|
||||
if (fieldsToFetch.includes(primaryKeyField.value.field) === false) {
|
||||
fieldsToFetch.push(primaryKeyField.value.field);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await api.get(
|
||||
`/${currentProjectKey}/items/${props.collection}`,
|
||||
{
|
||||
params: {
|
||||
fields: fieldsToFetch,
|
||||
limit: perPage.value,
|
||||
page: currentPage.value,
|
||||
sort: sort.value,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
items.value = response.data.data;
|
||||
|
||||
if (itemCount.value === null) {
|
||||
if (response.data.data.length === perPage.value) {
|
||||
// Requesting the page filter count in the actual request every time slows
|
||||
// the request down by like 600ms-1s. This makes sure we only fetch the count
|
||||
// once if needed.
|
||||
getTotalCount();
|
||||
} else {
|
||||
// If the response includes less items than the limit, it's safe to assume
|
||||
// it's all the data in the DB
|
||||
itemCount.value = response.data.data.length;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
error.value = error;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function toPage(page: number) {
|
||||
currentPage.value = page;
|
||||
getItems();
|
||||
mainElement.value?.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.layout-tabular {
|
||||
display: contents;
|
||||
padding: var(--content-padding);
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.v-table {
|
||||
--v-table-sticky-offset-top: var(--layout-offset-top);
|
||||
|
||||
display: contents;
|
||||
padding: 0 32px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
|
||||
@@ -294,10 +294,6 @@ export default defineComponent({
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.private-view {
|
||||
--content-padding: 0 !important;
|
||||
}
|
||||
|
||||
.action-delete {
|
||||
--v-button-background-color: var(--danger);
|
||||
--v-button-background-color-hover: var(--danger-dark);
|
||||
|
||||
Reference in New Issue
Block a user