App type improvements (#6151)

* Fix v-table interalItems type

* Fix useGroupable return type

* Fix useCollection return type

* Fix useCustomSelection return type

* Fix useElementSize return type

* Fix useFormFields return type

* Fix useItem return type

* Fix useItems return type

* Prepend composable return type name with "Usable"

* Fix usePreset return type

* Fix useScrollDistance return type

* Fix useTitle return type

* Fix useWindowSize return type

* Fix usePermissions return type

* Fix useTemplateData return type

* Fix a few type issues in field store

* Fix extension getter return types

* Fix hydrate store type issue and double-hydrating users store

* Fix code interface type issue

* Fix m2m composables return types

* Fix html editor composables return types

* Fix collections module composables return types

* Fix files module composable return type

* Fix settings module composable return type

* Fix settings module roles composables return types

* Fix settings module users composable return type

* Fix return type issues in utils and a nasty parameter overwrite

* Fix modelValue casing in template
This commit is contained in:
Nicola Krumschmidt
2021-06-09 17:18:21 +02:00
committed by GitHub
parent 70c8e08476
commit 12a3b22aa1
43 changed files with 359 additions and 95 deletions

View File

@@ -276,7 +276,7 @@ export default defineComponent({
if (internalSort.value.desc === true) return itemsSorted.reverse();
return itemsSorted;
},
set: (value: Record<string, any>) => {
set: (value: Item[]) => {
emit('update:items', value);
},
});

View File

@@ -18,7 +18,14 @@ type GroupableOptions = {
watch?: boolean;
};
export function useGroupable(options?: GroupableOptions): Record<string, any> {
type UsableGroupable = {
active: Ref<boolean>;
toggle: () => void;
activate: () => void;
deactivate: () => void;
};
export function useGroupable(options?: GroupableOptions): UsableGroupable {
// Injects the registration / toggle functions from the parent scope
const parentFunctions = inject(options?.group || 'item-group', null);
@@ -95,6 +102,14 @@ type GroupableParentOptions = {
multiple?: Ref<boolean>;
};
type UsableGroupableParent = {
items: Ref<GroupableInstance[]>;
selection: Ref<readonly (string | number)[]>;
internalSelection: Ref<(string | number)[]>;
getValueForItem: (item: GroupableInstance) => string | number;
updateChildren: () => void;
};
/**
* Used to make a component a group parent component. Provides the registration / toggle functions
* to its group children
@@ -103,7 +118,7 @@ export function useGroupableParent(
state: GroupableParentState = {},
options: GroupableParentOptions = {},
group = 'item-group'
): Record<string, any> {
): UsableGroupableParent {
// References to the active state and value of the individual child items
const items = shallowRef<GroupableInstance[]>([]);

View File

@@ -1,19 +1,19 @@
import { useCollectionsStore, useFieldsStore } from '@/stores/';
import { Collection, Field } from '@/types';
import { computed, Ref, ref } from 'vue';
import { computed, ref, Ref, ComputedRef } from 'vue';
type CollectionInfo = {
info: Ref<Collection | null>;
fields: Ref<Field[]>;
type UsableCollection = {
info: ComputedRef<Collection | null>;
fields: ComputedRef<Field[]>;
defaults: Record<string, any>;
primaryKeyField: Ref<Field | null>;
userCreatedField: Ref<Field | null>;
sortField: Ref<string | null>;
isSingleton: Ref<boolean>;
accountabilityScope: Ref<'all' | 'activity' | null>;
primaryKeyField: ComputedRef<Field | null>;
userCreatedField: ComputedRef<Field | null>;
sortField: ComputedRef<string | null>;
isSingleton: ComputedRef<boolean>;
accountabilityScope: ComputedRef<'all' | 'activity' | null>;
};
export function useCollection(collectionKey: string | Ref<string | null>): CollectionInfo {
export function useCollection(collectionKey: string | Ref<string | null>): UsableCollection {
const collectionsStore = useCollectionsStore();
const fieldsStore = useFieldsStore();

View File

@@ -1,11 +1,16 @@
import { nanoid } from 'nanoid';
import { computed, ComputedRef, Ref, ref, watch } from 'vue';
type UsableCustomSelection = {
otherValue: Ref<string | null>;
usesOtherValue: ComputedRef<boolean>;
};
export function useCustomSelection(
currentValue: Ref<string>,
items: Ref<any[]>,
emit: (event: string | null) => void
): Record<string, ComputedRef> {
): UsableCustomSelection {
const localOtherValue = ref('');
const otherValue = computed({
@@ -36,16 +41,22 @@ export function useCustomSelection(
return { otherValue, usesOtherValue };
}
type OtherValue = {
key: string;
value: string;
};
type UsableCustomSelectionMultiple = {
otherValues: Ref<OtherValue[]>;
addOtherValue: (value?: string) => void;
setOtherValue: (key: string, newValue: string | null) => void;
};
export function useCustomSelectionMultiple(
currentValues: Ref<string[]>,
items: Ref<any[]>,
emit: (event: string[] | null) => void
): Record<string, any> {
type OtherValue = {
key: string;
value: string;
};
): UsableCustomSelectionMultiple {
const otherValues = ref<OtherValue[]>([]);
watch(currentValues, (newValue) => {

View File

@@ -9,7 +9,10 @@ declare global {
export default function useElementSize<T extends Element>(
target: T | Ref<T> | Ref<undefined>
): Record<string, Ref<number>> {
): {
width: Ref<number>;
height: Ref<number>;
} {
const width = ref(0);
const height = ref(0);

View File

@@ -2,7 +2,7 @@ import { useFieldsStore, useRelationsStore } from '@/stores/';
import { Field, Relation } from '@/types';
import { getRelationType } from '@/utils/get-relation-type';
import { cloneDeep } from 'lodash';
import { computed, Ref } from 'vue';
import { computed, Ref, ComputedRef } from 'vue';
type FieldOption = { name: string; field: string; key: string; children?: FieldOption[] };
@@ -12,7 +12,7 @@ export default function useFieldTree(
strict = false,
inject?: Ref<{ fields: Field[]; relations: Relation[] } | null>,
filter: (field: Field) => boolean = () => true
): { tree: Ref<FieldOption[]> } {
): { tree: ComputedRef<FieldOption[]> } {
const fieldsStore = useFieldsStore();
const relationsStore = useRelationsStore();

View File

@@ -8,7 +8,7 @@ import { getDefaultInterfaceForType } from '@/utils/get-default-interface-for-ty
import { clone } from 'lodash';
import { computed, ComputedRef, Ref } from 'vue';
export default function useFormFields(fields: Ref<Field[]>): { formFields: ComputedRef } {
export default function useFormFields(fields: Ref<Field[]>): { formFields: ComputedRef<Field[]> } {
const { interfaces } = getInterfaces();
const formFields = computed(() => {

View File

@@ -6,14 +6,34 @@ import { APIError } from '@/types';
import { notify } from '@/utils/notify';
import { unexpectedError } from '@/utils/unexpected-error';
import { AxiosResponse } from 'axios';
import { computed, Ref, ref, watch } from 'vue';
import { computed, ComputedRef, Ref, ref, watch } from 'vue';
export function useItem(collection: Ref<string>, primaryKey: Ref<string | number | null>): Record<string, any> {
type UsableItem = {
edits: Ref<Record<string, any>>;
item: Ref<Record<string, any> | null>;
error: Ref<any>;
loading: Ref<boolean>;
saving: Ref<boolean>;
refresh: () => void;
save: () => Promise<any>;
isNew: ComputedRef<boolean>;
remove: () => Promise<void>;
deleting: Ref<boolean>;
archive: () => Promise<void>;
isArchived: ComputedRef<boolean | null>;
archiving: Ref<boolean>;
saveAsCopy: () => Promise<any>;
isBatch: ComputedRef<boolean>;
getItem: () => Promise<void>;
validationErrors: Ref<any[]>;
};
export function useItem(collection: Ref<string>, primaryKey: Ref<string | number | null>): UsableItem {
const { info: collectionInfo, primaryKeyField } = useCollection(collection);
const item = ref<Record<string, any> | null>(null);
const error = ref(null);
const validationErrors = ref([]);
const error = ref<any>(null);
const validationErrors = ref<any[]>([]);
const loading = ref(false);
const saving = ref(false);
const deleting = ref(false);

View File

@@ -4,7 +4,7 @@ import { Filter, Item } from '@/types/';
import filtersToQuery from '@/utils/filters-to-query';
import moveInArray from '@/utils/move-in-array';
import { isEqual, orderBy, throttle } from 'lodash';
import { computed, nextTick, ref, Ref, watch } from 'vue';
import { computed, ComputedRef, nextTick, ref, Ref, watch } from 'vue';
type Query = {
limit: Ref<number>;
@@ -20,18 +20,18 @@ type ManualSortData = {
to: string | number;
};
type ItemsInfo = {
type UsableItems = {
itemCount: Ref<number | null>;
totalCount: Ref<number | null>;
items: Ref<Item[]>;
totalPages: Ref<number>;
totalPages: ComputedRef<number>;
loading: Ref<boolean>;
error: Ref<any>;
changeManualSort: (data: ManualSortData) => Promise<void>;
getItems: () => Promise<void>;
};
export function useItems(collection: Ref<string | null>, query: Query, fetchOnInit = true): ItemsInfo {
export function useItems(collection: Ref<string | null>, query: Query, fetchOnInit = true): UsableItems {
const { primaryKeyField, sortField } = useCollection(collection);
let loadingTimeout: number | null = null;

View File

@@ -5,11 +5,16 @@ import { cloneDeep } from 'lodash';
import { isAllowed } from '../utils/is-allowed';
import { useCollection } from './use-collection';
export function usePermissions(
collection: Ref<string>,
item: Ref<any>,
isNew: Ref<boolean>
): Record<string, ComputedRef> {
type UsablePermissions = {
deleteAllowed: ComputedRef<boolean>;
saveAllowed: ComputedRef<boolean>;
archiveAllowed: ComputedRef<boolean>;
updateAllowed: ComputedRef<boolean>;
fields: ComputedRef<Field[]>;
revisionsAllowed: ComputedRef<boolean>;
};
export function usePermissions(collection: Ref<string>, item: Ref<any>, isNew: Ref<boolean>): UsablePermissions {
const userStore = useUserStore();
const permissionsStore = usePermissionsStore();

View File

@@ -2,13 +2,32 @@ import { useCollection } from '@/composables/use-collection';
import { usePresetsStore, useUserStore } from '@/stores';
import { Filter, Preset } from '@/types/';
import { debounce, isEqual } from 'lodash';
import { computed, ref, Ref, watch } from 'vue';
import { computed, ComputedRef, ref, Ref, watch } from 'vue';
type UsablePreset = {
bookmarkExists: ComputedRef<boolean>;
layout: Ref<string | null>;
layoutOptions: Ref<Record<string, any>>;
layoutQuery: Ref<Record<string, any>>;
filters: Ref<readonly Filter[]>;
searchQuery: Ref<string | null>;
refreshInterval: Ref<number | null>;
savePreset: (preset?: Partial<Preset> | undefined) => Promise<any>;
saveCurrentAsBookmark: (overrides: Partial<Preset>) => Promise<any>;
bookmarkTitle: Ref<string | null>;
resetPreset: () => Promise<void>;
bookmarkSaved: Ref<boolean>;
bookmarkIsMine: ComputedRef<boolean>;
busy: Ref<boolean>;
clearLocalSave: () => void;
localPreset: Ref<Partial<Preset>>;
};
export function usePreset(
collection: Ref<string>,
bookmark: Ref<number | null> = ref(null),
temporary = false
): Record<string, any> {
): UsablePreset {
const presetsStore = usePresetsStore();
const userStore = useUserStore();

View File

@@ -1,9 +1,15 @@
import { throttle } from 'lodash';
import { ComponentPublicInstance, computed, isRef, onMounted, onUnmounted, ref, Ref } from 'vue';
import { ComponentPublicInstance, computed, isRef, onMounted, onUnmounted, ref, Ref, ComputedRef } from 'vue';
type UsableScrollDistance = {
top: Ref<number | undefined>;
left: Ref<number | undefined>;
target: ComputedRef<Element | null>;
};
export default function useScrollDistance<T extends Element>(
t: T | Ref<T | null | ComponentPublicInstance>
): Record<string, Ref> {
): UsableScrollDistance {
const top = ref<number>();
const left = ref<number>();

View File

@@ -1,15 +1,21 @@
import api from '@/api';
import { Collection } from '@/types';
import { getFieldsFromTemplate } from '@/utils/get-fields-from-template';
import { computed, ComputedRef, Ref, ref, watch } from 'vue';
import { computed, Ref, ref, watch } from 'vue';
type UsableTemplateData = {
templateData: Ref<Record<string, any> | undefined>;
loading: Ref<boolean>;
error: Ref<any>;
};
export default function useTemplateData(
collection: ComputedRef<Collection | undefined>,
collection: Ref<Collection | null>,
primaryKey: Ref<string>
): Record<string, any> {
): UsableTemplateData {
const templateData = ref<Record<string, any>>();
const loading = ref(false);
const error = ref(null);
const error = ref<any>(null);
const fields = computed(() => {
if (!collection.value?.meta?.display_template) return null;

View File

@@ -1,6 +1,6 @@
import { ref, Ref, watch } from 'vue';
export function useTitle(newTitle: string | Ref<string>): Ref | undefined {
export function useTitle(newTitle: string | Ref<string>): Ref<string> | undefined {
if (newTitle === undefined || newTitle === null) return;
const titleRef = typeof newTitle === 'string' ? ref(newTitle) : newTitle;

View File

@@ -5,7 +5,10 @@ type WindowSizeOptions = {
throttle: number;
};
export default function useWindowSize(options: WindowSizeOptions = { throttle: 100 }): Record<string, Ref> {
export default function useWindowSize(options: WindowSizeOptions = { throttle: 100 }): {
width: Ref<number>;
height: Ref<number>;
} {
const width = ref(0);
const height = ref(0);

View File

@@ -4,6 +4,6 @@ import { DisplayConfig } from './types';
const displaysRaw: Ref<DisplayConfig[]> = shallowRef([]);
const displays: Ref<DisplayConfig[]> = shallowRef([]);
export function getDisplays(): Record<string, Ref<DisplayConfig[]>> {
export function getDisplays(): { displays: Ref<DisplayConfig[]>; displaysRaw: Ref<DisplayConfig[]> } {
return { displays, displaysRaw };
}

View File

@@ -16,7 +16,7 @@ import {
} from '@/stores';
type GenericStore = {
id: string;
$id: string;
hydrate?: () => Promise<void>;
dehydrate?: () => Promise<void>;
@@ -60,7 +60,7 @@ export async function hydrate(stores = useStores()): Promise<void> {
await userStore.hydrate();
if (userStore.currentUser?.role) {
await Promise.all(stores.filter(({ id }) => id !== 'userStore').map((store) => store.hydrate?.()));
await Promise.all(stores.filter(({ $id }) => $id !== 'userStore').map((store) => store.hydrate?.()));
await registerModules();
await setLanguage((userStore.currentUser?.language as Language) || 'en-US');
}

View File

@@ -4,6 +4,6 @@ import { InterfaceConfig } from './types';
const interfacesRaw: Ref<InterfaceConfig[]> = shallowRef([]);
const interfaces: Ref<InterfaceConfig[]> = shallowRef([]);
export function getInterfaces(): Record<string, Ref<InterfaceConfig[]>> {
export function getInterfaces(): { interfaces: Ref<InterfaceConfig[]>; interfacesRaw: Ref<InterfaceConfig[]> } {
return { interfaces, interfacesRaw };
}

View File

@@ -144,7 +144,7 @@ export default defineComponent({
codemirror.setOption('mode', { name: 'javascript', json: true });
CodeMirror.registerHelper('lint', 'json', (text: string) => {
const found: Record<string, any> = [];
const found: Record<string, any>[] = [];
const parser = jsonlint.parser;
parser.parseError = (str: string, hash: any) => {

View File

@@ -10,7 +10,23 @@ type ImageSelection = {
height?: number;
};
export default function useImage(editor: Ref<any>, imageToken: Ref<string>): Record<string, any> {
type ImageButton = {
icon: string;
tooltip: string;
onAction: (buttonApi: any) => void;
onSetup: (buttonApi: any) => () => void;
};
type UsableImage = {
imageDrawerOpen: Ref<boolean>;
imageSelection: Ref<ImageSelection | null>;
closeImageDrawer: () => void;
onImageSelect: (image: Record<string, any>) => void;
saveImage: () => void;
imageButton: ImageButton;
};
export default function useImage(editor: Ref<any>, imageToken: Ref<string>): UsableImage {
const imageDrawerOpen = ref(false);
const imageSelection = ref<ImageSelection | null>(null);

View File

@@ -8,7 +8,22 @@ type LinkSelection = {
newTab: boolean;
};
export default function useLink(editor: Ref<any>): Record<string, any> {
type LinkButton = {
icon: string;
tooltip: string;
onAction: (buttonApi: any) => void;
onSetup: (buttonApi: any) => () => void;
};
type UsableLink = {
linkDrawerOpen: Ref<boolean>;
linkSelection: Ref<LinkSelection>;
closeLinkDrawer: () => void;
saveLink: () => void;
linkButton: LinkButton;
};
export default function useLink(editor: Ref<any>): UsableLink {
const linkDrawerOpen = ref(false);
const linkSelection = ref<LinkSelection>({
url: null,

View File

@@ -9,7 +9,29 @@ type MediaSelection = {
height?: number;
};
export default function useMedia(editor: Ref<any>, imageToken: Ref<string>): Record<string, any> {
type MediaButton = {
icon: string;
tooltip: string;
onAction: (buttonApi: any) => void;
onSetup: (buttonApi: any) => () => void;
};
type UsableMedia = {
mediaDrawerOpen: Ref<boolean>;
mediaSelection: Ref<MediaSelection | null>;
closeMediaDrawer: () => void;
openMediaTab: Ref<string[]>;
onMediaSelect: (media: Record<string, any>) => void;
embed: Ref<string>;
saveMedia: () => void;
startEmbed: Ref<string>;
mediaHeight: Ref<number | undefined>;
mediaWidth: Ref<number | undefined>;
mediaSource: Ref<any>;
mediaButton: MediaButton;
};
export default function useMedia(editor: Ref<any>, imageToken: Ref<string>): UsableMedia {
const mediaDrawerOpen = ref(false);
const mediaSelection = ref<MediaSelection | null>(null);
const openMediaTab = ref(['video']);

View File

@@ -1,7 +1,21 @@
import { i18n } from '@/lang';
import { Ref, ref } from 'vue';
export default function useSourceCode(editor: Ref<any>): Record<string, any> {
type SourceCodeButton = {
icon: string;
tooltip: string;
onAction: () => void;
};
type UsableSourceCode = {
codeDrawerOpen: Ref<boolean>;
code: Ref<string | undefined>;
closeCodeDrawer: () => void;
saveCode: () => void;
sourceCodeButton: SourceCodeButton;
};
export default function useSourceCode(editor: Ref<any>): UsableSourceCode {
const codeDrawerOpen = ref(false);
const code = ref<string>();

View File

@@ -2,11 +2,23 @@ import { get, has, isEqual } from 'lodash';
import { Ref } from 'vue';
import { RelationInfo } from './use-relation';
type UsableActions = {
getJunctionItem: (id: string | number) => string | number | Record<string, any> | null;
getNewSelectedItems: () => Record<string, any>[];
getNewItems: () => Record<string, any>[];
getUpdatedItems: () => Record<string, any>[];
getExistingItems: () => (string | number | Record<string, any>)[];
getPrimaryKeys: () => (string | number)[];
getRelatedPrimaryKeys: () => (string | number)[];
getJunctionFromRelatedId: (id: string | number, items: Record<string, any>[]) => Record<string, any> | null;
deleteItem: (deletingItem: Record<string, any>) => void;
};
export default function useActions(
value: Ref<(string | number | Record<string, any>)[] | null>,
relation: Ref<RelationInfo>,
emit: (newValue: any[] | null) => void
): Record<string, any> {
): UsableActions {
// Returns the junction item with the given Id.
function getJunctionItem(id: string | number) {
const { junctionPkField } = relation.value;
@@ -88,6 +100,12 @@ export default function useActions(
}, []) as (string | number)[];
}
function getJunctionFromRelatedId(id: string | number, items: Record<string, any>[]) {
const { relationPkField, junctionField } = relation.value;
return items.find((item) => get(item, [junctionField, relationPkField]) === id) || null;
}
function deleteItem(deletingItem: Record<string, any>) {
if (value.value === null) return;
const { junctionField, relationPkField, junctionPkField } = relation.value;
@@ -121,12 +139,6 @@ export default function useActions(
emit(newValue);
}
function getJunctionFromRelatedId(id: string | number, items: Record<string, any>[]) {
const { relationPkField, junctionField } = relation.value;
return items.find((item) => get(item, [junctionField, relationPkField]) === id) || null;
}
return {
getJunctionItem,
getNewSelectedItems,

View File

@@ -1,11 +1,22 @@
import { Ref, ref } from 'vue';
import { get, isEqual } from 'lodash';
import { RelationInfo } from './use-relation';
type UsableEdit = {
currentlyEditing: Ref<string | number | null>;
editItem: (item: any) => void;
editsAtStart: Ref<Record<string, any>>;
stageEdits: (edits: any) => void;
cancelEdit: () => void;
relatedPrimaryKey: Ref<string | number | null>;
editModalActive: Ref<boolean>;
};
export default function useEdit(
value: Ref<(string | number | Record<string, any>)[] | null>,
relation: Ref<RelationInfo>,
emit: (newVal: any[] | null) => void
): Record<string, any> {
): UsableEdit {
const editModalActive = ref(false);
const currentlyEditing = ref<string | number | null>(null);
const relatedPrimaryKey = ref<string | number | null>(null);

View File

@@ -7,6 +7,13 @@ import { cloneDeep, get } from 'lodash';
import { Ref, ref, watch } from 'vue';
import { RelationInfo } from './use-relation';
type UsablePreview = {
tableHeaders: Ref<Header[]>;
items: Ref<Record<string, any>[]>;
loading: Ref<boolean>;
error: Ref<any>;
};
export default function usePreview(
value: Ref<(string | number | Record<string, any>)[] | null>,
fields: Ref<string[]>,
@@ -15,7 +22,7 @@ export default function usePreview(
getUpdatedItems: () => Record<string, any>[],
getNewItems: () => Record<string, any>[],
getPrimaryKeys: () => (string | number)[]
): Record<string, Ref> {
): UsablePreview {
// Using a ref for the table headers here means that the table itself can update the
// values if it needs to. This allows the user to manually resize the columns for example
@@ -23,7 +30,7 @@ export default function usePreview(
const tableHeaders = ref<Header[]>([]);
const loading = ref(false);
const items = ref<Record<string, any>[]>([]);
const error = ref(null);
const error = ref<any>(null);
watch(
() => value.value,

View File

@@ -1,7 +1,7 @@
import useCollection from '@/composables/use-collection';
import { useCollectionsStore, useRelationsStore } from '@/stores/';
import { Relation } from '@/types';
import { computed, Ref } from 'vue';
import { Collection, Field, Relation } from '@/types';
import { computed, ComputedRef, Ref } from 'vue';
export type RelationInfo = {
junctionPkField: string;
@@ -12,7 +12,17 @@ export type RelationInfo = {
relationCollection: string;
};
export default function useRelation(collection: Ref<string>, field: Ref<string>): Record<string, any> {
type UsableRelation = {
junction: ComputedRef<Relation>;
junctionCollection: ComputedRef<Collection>;
relation: ComputedRef<Relation>;
relationCollection: ComputedRef<Collection>;
relationInfo: ComputedRef<RelationInfo>;
junctionPrimaryKeyField: ComputedRef<Field | null>;
relationPrimaryKeyField: ComputedRef<Field | null>;
};
export default function useRelation(collection: Ref<string>, field: Ref<string>): UsableRelation {
const relationsStore = useRelationsStore();
const collectionsStore = useCollectionsStore();

View File

@@ -1,14 +1,21 @@
import { Filter } from '@/types';
import { get } from 'lodash';
import { computed, Ref, ref } from 'vue';
import { computed, ComputedRef, Ref, ref } from 'vue';
import { RelationInfo } from './use-relation';
type UsableSelection = {
stageSelection: (newSelection: (number | string)[]) => void;
selectModalActive: Ref<boolean>;
selectedPrimaryKeys: ComputedRef<(string | number)[]>;
selectionFilters: ComputedRef<Filter[]>;
};
export default function useSelection(
value: Ref<(string | number | Record<string, any>)[] | null>,
items: Ref<Record<string, any>[]>,
relation: Ref<RelationInfo>,
emit: (newVal: any[] | null) => void
): Record<string, any> {
): UsableSelection {
const selectModalActive = ref(false);
const selectedPrimaryKeys = computed(() => {

View File

@@ -1,14 +1,20 @@
import { Sort } from '@/components/v-table/types';
import { sortBy } from 'lodash';
import { computed, Ref, ref } from 'vue';
import { computed, ComputedRef, Ref, ref } from 'vue';
import { RelationInfo } from './use-relation';
type UsableSort = {
sort: Ref<Sort>;
sortItems: (newItems: Record<string, any>[]) => void;
sortedItems: ComputedRef<Record<string, any>[]>;
};
export default function useSort(
relation: Ref<RelationInfo>,
fields: Ref<string[]>,
items: Ref<Record<string, any>[]>,
emit: (newVal: any[] | null) => void
): Record<string, any> {
): UsableSort {
const sort = ref<Sort>({ by: relation.value.sortField || fields.value[0], desc: false });
const sortedItems = computed(() => {

View File

@@ -4,6 +4,6 @@ import { LayoutConfig } from './types';
const layoutsRaw: Ref<LayoutConfig[]> = shallowRef([]);
const layouts: Ref<LayoutConfig[]> = shallowRef([]);
export function getLayouts(): Record<string, Ref<LayoutConfig[]>> {
export function getLayouts(): { layouts: Ref<LayoutConfig[]>; layoutsRaw: Ref<LayoutConfig[]> } {
return { layouts, layoutsRaw };
}

View File

@@ -1,6 +1,6 @@
import { useCollectionsStore, useUserStore } from '@/stores/';
import { Collection } from '@/types';
import { computed, Ref, ref } from 'vue';
import { computed, ComputedRef, Ref, ref } from 'vue';
export type NavItem = {
collection: string;
@@ -31,7 +31,16 @@ function collectionToNavItem(collection: Collection): NavItem {
};
}
export default function useNavigation(searchQuery?: Ref<string | null>): Record<string, any> {
type UsableNavigation = {
customNavItems: ComputedRef<NavItemGroup[] | null>;
navItems: ComputedRef<NavItem[]>;
activeGroups: Ref<string[]>;
hiddenNavItems: ComputedRef<NavItem[]>;
hiddenShown: Ref<boolean>;
search: (item: NavItem) => boolean;
};
export default function useNavigation(searchQuery?: Ref<string | null>): UsableNavigation {
const collectionsStore = useCollectionsStore();
const userStore = useUserStore();

View File

@@ -3,6 +3,6 @@ import { ref, Ref } from 'vue';
const searchQuery = ref<string | null>(null);
const visible = ref<number | null>(null);
export function useSearch(): Record<string, Ref> {
export function useSearch(): { visible: Ref<number | null>; searchQuery: Ref<string | null> } {
return { visible, searchQuery };
}

View File

@@ -14,6 +14,15 @@ export type Folder = {
children?: Folder[];
};
type UsableFolders = {
loading: Ref<boolean>;
folders: Ref<Folder[] | null>;
nestedFolders: Ref<Folder[] | null>;
error: Ref<any>;
fetchFolders: () => Promise<void>;
openFolders: Ref<string[] | null>;
};
let loading: Ref<boolean> | null = null;
let folders: Ref<Folder[] | null> | null = null;
let nestedFolders: Ref<Folder[] | null> | null = null;
@@ -21,7 +30,7 @@ let openFolders: Ref<string[] | null> | null = null;
let error: Ref<any> | null = null;
export default function useFolders(): Record<string, any> {
export default function useFolders(): UsableFolders {
if (loading === null) loading = ref(false);
if (folders === null) folders = ref<Folder[] | null>(null);
if (nestedFolders === null) nestedFolders = ref<Folder[] | null>(null);

View File

@@ -4,6 +4,6 @@ import { ModuleConfig } from './types';
const modulesRaw: Ref<ModuleConfig[]> = shallowRef([]);
const modules: Ref<ModuleConfig[]> = shallowRef([]);
export function getModules(): Record<string, Ref<ModuleConfig[]>> {
export function getModules(): { modules: Ref<ModuleConfig[]>; modulesRaw: Ref<ModuleConfig[]> } {
return { modules, modulesRaw };
}

View File

@@ -1,7 +1,7 @@
import api from '@/api';
import bytes from 'bytes';
import prettyMS from 'pretty-ms';
import { computed, ref, Ref } from 'vue';
import { computed, ComputedRef, ref, Ref } from 'vue';
type ServerInfo = {
directus: {
@@ -19,7 +19,28 @@ type ServerInfo = {
};
};
export function useProjectInfo(): Record<string, Ref> {
type UsableProjectInfo = {
info: Ref<ServerInfo | undefined>;
parsedInfo: ComputedRef<{
directus: {
version: string;
};
node: {
version: string;
uptime: string;
};
os: {
type: string;
version: string;
uptime: string;
totalmem: string;
};
} | null>;
loading: Ref<boolean>;
error: Ref<any>;
};
export function useProjectInfo(): UsableProjectInfo {
const info = ref<ServerInfo>();
const loading = ref(false);
const error = ref<any>();

View File

@@ -3,7 +3,7 @@
<div class="grid">
<div class="field">
<div class="type-label">{{ t('this_collection') }}</div>
<v-input disabled :modelValue="relations[0].collection" />
<v-input disabled :model-value="relations[0].collection" />
</div>
<div class="field">
<div class="type-label">{{ t('related_collection') }}</div>

View File

@@ -61,13 +61,13 @@ function initLocalStore(collection: string, field: string, type: typeof localTyp
availableDisplays = computed(() => {
return displays.value
.filter((inter: InterfaceConfig) => {
.filter((inter: DisplayConfig) => {
const matchesType = inter.types.includes(state.fieldData?.type || 'alias');
const matchesLocalType = (inter.groups || ['standard']).includes(type) || true;
return matchesType && matchesLocalType;
})
.sort((a: InterfaceConfig, b: InterfaceConfig) => (a.name > b.name ? 1 : -1));
.sort((a: DisplayConfig, b: DisplayConfig) => (a.name > b.name ? 1 : -1));
});
generationInfo = computed(() => {

View File

@@ -2,7 +2,16 @@ import api from '@/api';
import { Permission } from '@/types';
import { ref, Ref, watch } from 'vue';
export default function usePermissions(role: Ref<number>): Record<string, any> {
type UsablePermissions = {
loading: Ref<boolean>;
error: Ref<null>;
permissions: Ref<Permission[] | null>;
fetchPermissions: () => Promise<void>;
savePermission: (updates: Partial<Permission>) => Promise<void>;
saveAll: (create: Partial<Permission>[], update: Partial<Permission>[]) => Promise<void>;
};
export default function usePermissions(role: Ref<number>): UsablePermissions {
const loading = ref(false);
const error = ref(null);
const permissions = ref<Permission[] | null>(null);

View File

@@ -3,11 +3,19 @@ import { Collection, Permission } from '@/types';
import { unexpectedError } from '@/utils/unexpected-error';
import { inject, ref, Ref } from 'vue';
type UsableUpdatePermissions = {
getPermission: (action: string) => Permission | undefined;
setFullAccess: (action: 'create' | 'read' | 'update' | 'delete') => Promise<void>;
setNoAccess: (action: 'create' | 'read' | 'update' | 'delete') => Promise<void>;
setFullAccessAll: () => Promise<void>;
setNoAccessAll: () => Promise<void>;
};
export default function useUpdatePermissions(
collection: Ref<Collection>,
permissions: Ref<Permission[]>,
role: Ref<string>
): Record<string, any> {
): UsableUpdatePermissions {
const saving = ref(false);
const refresh = inject<() => Promise<void>>('refresh-permissions');

View File

@@ -5,7 +5,7 @@ import { ref, Ref } from 'vue';
let roles: Ref<Role[] | null> | null = null;
let loading: Ref<boolean> | null = null;
export default function useNavigation(): Record<string, Ref> {
export default function useNavigation(): { roles: Ref<Role[] | null>; loading: Ref<boolean> } {
if (roles === null) {
roles = ref<Role[] | null>(null);
}

View File

@@ -1,7 +1,7 @@
import { Filter } from '@/types/';
import { clone } from 'lodash';
export default function filtersToQuery(filters: readonly Filter[]): any {
export default function filtersToQuery(filters: readonly Filter[]): { filter: Record<string, any> } {
const filterList: Record<string, any>[] = [];
for (const filter of filters) {

View File

@@ -64,7 +64,7 @@ const defaults: JoiOptions = {
allowUnknown: true,
};
export default function generateJoi(filter: Record<string, any> | null, options?: JoiOptions): any {
export default function generateJoi(filter: Record<string, any> | null, options?: JoiOptions): AnySchema {
filter = filter || {};
options = {

View File

@@ -1,14 +1,14 @@
import { i18n } from '@/lang';
import { cloneDeep } from 'lodash';
export function translate<T extends Record<string, any>>(obj: T): any {
obj = cloneDeep(obj);
export function translate<T extends Record<string, any>>(obj: T): T {
const newObj = cloneDeep(obj);
Object.entries(obj).forEach(([key, val]) => {
if (val && typeof val === 'object') (obj as Record<string, any>)[key] = translate(val);
Object.entries(newObj).forEach(([key, val]) => {
if (val && typeof val === 'object') (newObj as Record<string, any>)[key] = translate(val);
if (val && typeof val === 'string' && val.startsWith('$t:'))
(obj as Record<string, any>)[key] = i18n.global.t(val.replace('$t:', ''));
(newObj as Record<string, any>)[key] = i18n.global.t(val.replace('$t:', ''));
});
return obj;
return newObj;
}