Repository: directus/directus
File: app/src/composables/use-field-tree.ts (TypeScript)

Code before:
import { isNil } from 'lodash';
import { Ref, ref, watch } from 'vue';

export type FieldNode = {
	name: string;
	field: string;
	collection: string;
	relatedCollection?: string;
	key: string;
	path: string;
	type: Type;
	children?: FieldNode[];
	group?: boolean;
	_loading?: boolean;
};

export type FieldTreeContext = {
	treeList: Ref<FieldNode[]>;
	loadFieldRelations: (fieldPath: string, root?: FieldNode) => void;
	refresh: (collection?: string) => void;
};

export function useFieldTree(
	collection: Ref<string | null>,
	inject?: Ref<{ fields: Field[]; relations?: Relation[] } | null>,
	filter: (field: Field, parent?: FieldNode) => boolean = () => true,
): FieldTreeContext {
	const fieldsStore = useFieldsStore();
	const relationsStore = useRelationsStore();

	const treeList = ref<FieldNode[]>([]);
	const visitedPaths = ref<Set<string>>(new Set());

	// Refresh when collection or fields of the collection are updated
	watch([collection, () => collection.value && fieldsStore.getFieldsForCollectionSorted(collection.value)], refresh, {
		immediate: true,
	});

	return { treeList, loadFieldRelations, refresh };

	function refresh() {
		visitedPaths.value = new Set();
		treeList.value = getTree(collection.value) ?? [];

		for (const node of treeList.value) {
			if (node.relatedCollection) {
				node.children = getTree(node.relatedCollection, node);
			}
		}
	}


Review comment (suggestion):
This is a nit that won't prevent approval, but instead of adding two boolean params, I'd rather see an options object that merges with the defaults. This makes it easier to read and use, IMO. Right now, if I wanted includeRelations to use its default value, but wanted includeAliasFields to accept a passed in value, I'd have to do something like `useFieldTree(collection, inject, filter, undefined, true)`

I'd rather see this:

```
useFieldTree(collection, inject, filter, {
  includeAliasFields: true 
})
```







Code after:
import { isNil } from 'lodash';
import { Ref, ref, watch } from 'vue';

export type FieldNode = {
	name: string;
	field: string;
	collection: string;
	relatedCollection?: string;
	key: string;
	path: string;
	type: Type;
	children?: FieldNode[];
	group?: boolean;
	_loading?: boolean;
};

export type FieldTreeContext = {
	treeList: Ref<FieldNode[]>;
	loadFieldRelations: (fieldPath: string, root?: FieldNode) => void;
	refresh: (collection?: string) => void;
};

export function useFieldTree(
	collection: Ref<string | null>,
	inject?: Ref<{ fields: Field[]; relations?: Relation[] } | null>,
	filter: (field: Field, parent?: FieldNode) => boolean = () => true,
	includeRelations = true,
	includeAliasFields = false,
): FieldTreeContext {
	const fieldsStore = useFieldsStore();
	const relationsStore = useRelationsStore();

	const treeList = ref<FieldNode[]>([]);
	const visitedPaths = ref<Set<string>>(new Set());

	// Refresh when collection or fields of the collection are updated
	watch([collection, () => collection.value && fieldsStore.getFieldsForCollectionSorted(collection.value)], refresh, {
		immediate: true,
	});

	return { treeList, loadFieldRelations, refresh };

	function refresh() {
		visitedPaths.value = new Set();
		treeList.value = getTree(collection.value) ?? [];

		for (const node of treeList.value) {
			if (node.relatedCollection) {
				node.children = getTree(node.relatedCollection, node);
			}
		}