mirror of
https://github.com/directus/directus.git
synced 2026-04-03 03:00:39 -04:00
Add option to auto refresh collections (#4777)
* add auto refresh * add refresh_interval to DB * remove refresh_interval template * set refresh interval default to null * Fix typo in filename * Rename sidebar-auto-refresh to refresh-sidebar-detail * Rename import * Add badge on active refresh, change options * Fix refresh not working on refresh Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex) {
|
||||
await knex.schema.alterTable('directus_presets', (table) => {
|
||||
table.integer('refresh_interval');
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex) {
|
||||
await knex.schema.alterTable('directus_presets', (table) => {
|
||||
table.dropColumn('refresh_interval');
|
||||
});
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import { defineComponent } from '@vue/composition-api';
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: [String, Number],
|
||||
type: [Boolean, String, Number],
|
||||
default: null,
|
||||
},
|
||||
dot: {
|
||||
@@ -102,12 +102,21 @@ body {
|
||||
bottom: calc(var(--v-badge-size) / -2 + var(--v-badge-offset-y));
|
||||
}
|
||||
|
||||
&.dot * {
|
||||
display: none;
|
||||
&.bordered {
|
||||
filter: drop-shadow(1.5px 1.5px 0 var(--v-badge-border-color))
|
||||
drop-shadow(1.5px -1.5px 0 var(--v-badge-border-color)) drop-shadow(-1.5px 1.5px 0 var(--v-badge-border-color))
|
||||
drop-shadow(-1.5px -1.5px 0 var(--v-badge-border-color));
|
||||
}
|
||||
|
||||
&.bordered {
|
||||
border: 2px solid var(--v-badge-border-color);
|
||||
&.dot {
|
||||
width: var(--v-badge-size);
|
||||
min-width: 0;
|
||||
height: var(--v-badge-size);
|
||||
border: 0;
|
||||
|
||||
* {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +131,20 @@ export function usePreset(collection: Ref<string>, bookmark: Ref<number | null>
|
||||
},
|
||||
});
|
||||
|
||||
const refreshInterval = computed<number | null>({
|
||||
get() {
|
||||
return localPreset.value.refresh_interval || null;
|
||||
},
|
||||
set(val) {
|
||||
localPreset.value = {
|
||||
...localPreset.value,
|
||||
refresh_interval: val,
|
||||
};
|
||||
|
||||
handleChanges();
|
||||
},
|
||||
});
|
||||
|
||||
const searchQuery = computed<string | null>({
|
||||
get() {
|
||||
return localPreset.value.search || null;
|
||||
@@ -167,6 +181,7 @@ export function usePreset(collection: Ref<string>, bookmark: Ref<number | null>
|
||||
layoutQuery,
|
||||
filters,
|
||||
searchQuery,
|
||||
refreshInterval,
|
||||
savePreset,
|
||||
saveCurrentAsBookmark,
|
||||
bookmarkTitle,
|
||||
@@ -195,6 +210,7 @@ export function usePreset(collection: Ref<string>, bookmark: Ref<number | null>
|
||||
layout: 'tabular',
|
||||
filters: null,
|
||||
search: null,
|
||||
refresh_interval: null,
|
||||
};
|
||||
|
||||
await savePreset();
|
||||
|
||||
@@ -149,6 +149,11 @@ uuid: UUID
|
||||
hash: Hash
|
||||
not_available_for_type: Not Available for this Type
|
||||
create_translations: Create Translations
|
||||
auto_refresh: Auto Refresh
|
||||
refresh_interval: Refresh Interval
|
||||
no_refresh: Do not refresh
|
||||
refresh_interval_seconds: Refresh Instantly | Every Second | Every {seconds} Seconds
|
||||
refresh_interval_minutes: Every Minute | Every {minutes} Minutes
|
||||
auto_generate: Auto-Generate
|
||||
this_will_auto_setup_fields_relations: This will automatically setup all required fields and relations.
|
||||
click_here: Click here
|
||||
|
||||
@@ -222,6 +222,7 @@
|
||||
<layout-sidebar-detail @input="layout = $event" :value="layout" />
|
||||
<portal-target name="sidebar" />
|
||||
<export-sidebar-detail :layout-query="layoutQuery" :search-query="searchQuery" :collection="currentCollection" />
|
||||
<refresh-sidebar-detail @refresh="refresh" v-model="refreshInterval" />
|
||||
</template>
|
||||
|
||||
<v-dialog v-if="deleteError" active>
|
||||
@@ -249,6 +250,7 @@ import useCollection from '@/composables/use-collection';
|
||||
import usePreset from '@/composables/use-preset';
|
||||
import LayoutSidebarDetail from '@/views/private/components/layout-sidebar-detail';
|
||||
import ExportSidebarDetail from '@/views/private/components/export-sidebar-detail';
|
||||
import RefreshSidebarDetail from '@/views/private/components/refresh-sidebar-detail';
|
||||
import SearchInput from '@/views/private/components/search-input';
|
||||
import BookmarkAdd from '@/views/private/components/bookmark-add';
|
||||
import BookmarkEdit from '@/views/private/components/bookmark-edit';
|
||||
@@ -274,6 +276,7 @@ export default defineComponent({
|
||||
BookmarkAdd,
|
||||
BookmarkEdit,
|
||||
DrawerBatch,
|
||||
RefreshSidebarDetail,
|
||||
},
|
||||
props: {
|
||||
collection: {
|
||||
@@ -311,6 +314,7 @@ export default defineComponent({
|
||||
resetPreset,
|
||||
bookmarkSaved,
|
||||
bookmarkIsMine,
|
||||
refreshInterval,
|
||||
busy: bookmarkSaving,
|
||||
clearLocalSave,
|
||||
} = usePreset(collection, bookmarkID);
|
||||
@@ -344,7 +348,6 @@ export default defineComponent({
|
||||
addNewLink,
|
||||
batchDelete,
|
||||
batchEditActive,
|
||||
|
||||
confirmDelete,
|
||||
currentCollection,
|
||||
deleting,
|
||||
@@ -381,6 +384,7 @@ export default defineComponent({
|
||||
bookmarkSaving,
|
||||
clearLocalSave,
|
||||
refresh,
|
||||
refreshInterval,
|
||||
};
|
||||
|
||||
function refresh() {
|
||||
|
||||
@@ -14,6 +14,7 @@ const defaultPreset: Omit<Preset, 'collection'> = {
|
||||
layout: null,
|
||||
layout_query: null,
|
||||
layout_options: null,
|
||||
refresh_interval: null,
|
||||
};
|
||||
|
||||
const systemDefaults: Record<string, Partial<Preset>> = {
|
||||
|
||||
@@ -35,4 +35,5 @@ export type Preset = {
|
||||
layout: string | null;
|
||||
layout_query: { [layout: string]: any } | null;
|
||||
layout_options: { [layout: string]: any } | null;
|
||||
refresh_interval: number | null;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
import RefreshSidebarDetail from './refresh-sidebar-detail.vue';
|
||||
|
||||
export { RefreshSidebarDetail };
|
||||
export default RefreshSidebarDetail;
|
||||
@@ -0,0 +1,95 @@
|
||||
<template>
|
||||
<sidebar-detail :icon="active ? 'sync' : 'sync_disabled'" :title="$t('auto_refresh')" :badge="active">
|
||||
<div class="fields">
|
||||
<div class="field full">
|
||||
<p class="type-label">{{ $t('refresh_interval') }}</p>
|
||||
<v-select :items="items" v-model="interval" />
|
||||
</div>
|
||||
</div>
|
||||
</sidebar-detail>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import i18n from '@/lang';
|
||||
import { computed, defineComponent, ref, watch } from '@vue/composition-api';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
value: {
|
||||
type: Number,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const interval = computed<number | null>({
|
||||
get() {
|
||||
return props.value;
|
||||
},
|
||||
set(newVal) {
|
||||
emit('input', newVal);
|
||||
},
|
||||
});
|
||||
|
||||
const activeInterval = ref<NodeJS.Timeout | null>(null);
|
||||
|
||||
watch(
|
||||
interval,
|
||||
(newInterval) => {
|
||||
if (activeInterval.value !== null) {
|
||||
clearInterval(activeInterval.value);
|
||||
}
|
||||
if (newInterval !== null && newInterval > 0) {
|
||||
activeInterval.value = setInterval(() => {
|
||||
emit('refresh');
|
||||
}, newInterval * 1000);
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
const items = computed(() => {
|
||||
const intervals = [null, 10, 30, 60, 300];
|
||||
|
||||
return intervals.map((seconds) => {
|
||||
if (seconds === null)
|
||||
return {
|
||||
text: i18n.t('no_refresh'),
|
||||
value: null,
|
||||
};
|
||||
|
||||
return seconds >= 60 && seconds % 60 === 0
|
||||
? {
|
||||
text: i18n.tc('refresh_interval_minutes', seconds / 60, { minutes: seconds / 60 }),
|
||||
value: seconds,
|
||||
}
|
||||
: {
|
||||
text: i18n.tc('refresh_interval_seconds', seconds, { seconds }),
|
||||
value: seconds,
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
const active = computed(() => interval.value !== null);
|
||||
|
||||
return { active, interval, items };
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/styles/mixins/form-grid';
|
||||
|
||||
.fields {
|
||||
--form-vertical-gap: 24px;
|
||||
|
||||
@include form-grid;
|
||||
|
||||
.type-label {
|
||||
font-size: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.v-checkbox {
|
||||
margin-top: 8px;
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="sidebar-detail" :class="{ open: sidebarOpen }">
|
||||
<button class="toggle" @click="toggle" :class="{ open: active }">
|
||||
<div class="icon">
|
||||
<v-badge bordered :value="badge" :disabled="!badge">
|
||||
<v-badge :dot="badge === true" bordered :value="badge" :disabled="!badge">
|
||||
<v-icon :name="icon" outline />
|
||||
</v-badge>
|
||||
</div>
|
||||
@@ -42,7 +42,7 @@ export default defineComponent({
|
||||
required: true,
|
||||
},
|
||||
badge: {
|
||||
type: [String, Number],
|
||||
type: [Boolean, String, Number],
|
||||
default: null,
|
||||
},
|
||||
close: {
|
||||
|
||||
Reference in New Issue
Block a user