mirror of
https://github.com/directus/directus.git
synced 2026-02-14 19:25:03 -05:00
Add support for a package extension bundle type (#15672)
* Add bundle type to constants and types * Add support for API bundle extensions * Rename generateExtensionsEntry to generateExtensionsEntrypoint * Add support for App bundle extensions * Refactor App extension registration * Replace extensions inject with useExtensions() * Replace getInterfaces() with useExtensions() * Replace getDisplays() with useExtensions() * Replace getLayouts() with useExtensions() * Replace getModules() with useExtensions() * Replace getPanels() with useExtensions() * Replace getOperations() with useExtensions() * Add useExtension() composable * Replace useExtensions() with useExtension() where applicable * Remove interface getters * Remove display getters * Remove layout getters * Remove module getter * Remove panel getters * Remove operation getters * Rename extension register.ts files to index.ts * Perform module pre register check in parallel * Remove Refs from AppExtensionConfigs type * Remove old extension shims * Ensure registration of modules is awaited when hydrating * Add support for scaffolding package extensions * Add support for building bundle extensions * Add JsonValue type * Use json for complex command line flags * Load internal extensions if custom ones are not available * Fix extension manifest validation for pack extensions * Fix tests in shared * Add SplitEntrypoint type * Move command specific utils to helpers * Add SDK version getter * Move extension dev deps generation to helpers * Move template path to getter util * Move template copying to a helper * Only rename copied template files * Add directus-extension add command * Convert provided extension source path to url * Replace deprecated import.meta.globEager * Mock URL.createObjectURL to make App unit tests pass * Update rollup-plugin-typescript2 * indentation * sort vite glob imported modules * fix unintentional wrong commit * Simplify app extension import logic * reinstall @rollup/plugin-virtual * add test for getInterfaces() expected sort order Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com> Co-authored-by: Azri Kahar <42867097+azrikahar@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
0859102a61
commit
7bf90efa62
@@ -29,7 +29,7 @@
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed, inject, ref } from 'vue';
|
||||
import { getInterface } from '@/interfaces';
|
||||
import { useExtension } from '@/composables/use-extension';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -65,15 +65,8 @@ export default defineComponent({
|
||||
|
||||
const values = inject('values', ref<Record<string, any>>({}));
|
||||
|
||||
const selectedInterface = computed(() => {
|
||||
if (props.interface) {
|
||||
return getInterface(props.interface);
|
||||
}
|
||||
|
||||
if (!values.value[props.interfaceField]) return;
|
||||
|
||||
return getInterface(values.value[props.interfaceField]);
|
||||
});
|
||||
const selectedInterfaceId = computed(() => props.interface ?? values.value[props.interfaceField] ?? null);
|
||||
const selectedInterface = useExtension('interface', selectedInterfaceId);
|
||||
|
||||
const usesCustomComponent = computed(() => {
|
||||
if (!selectedInterface.value) return false;
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
<script lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { defineComponent, computed, inject, ref, watch } from 'vue';
|
||||
import { getInterfaces } from '@/interfaces';
|
||||
import { InterfaceConfig } from '@directus/shared/types';
|
||||
import { useExtensions } from '@/extensions';
|
||||
|
||||
export default defineComponent({
|
||||
props: {
|
||||
@@ -32,7 +32,7 @@ export default defineComponent({
|
||||
setup(props, { emit }) {
|
||||
const { t } = useI18n();
|
||||
|
||||
const { interfaces } = getInterfaces();
|
||||
const { interfaces } = useExtensions();
|
||||
|
||||
const values = inject('values', ref<Record<string, any>>({}));
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent, PropType, computed, ref } from 'vue';
|
||||
import { getModules } from '@/modules';
|
||||
import { Settings, SettingsModuleBarModule, SettingsModuleBarLink } from '@directus/shared/types';
|
||||
import { hideDragImage } from '@/utils/hide-drag-image';
|
||||
import Draggable from 'vuedraggable';
|
||||
@@ -69,6 +68,7 @@ import { useI18n } from 'vue-i18n';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { Field, DeepPartial } from '@directus/shared/types';
|
||||
import { MODULE_BAR_DEFAULT } from '@/constants';
|
||||
import { useExtensions } from '@/extensions';
|
||||
|
||||
type PreviewExtra = {
|
||||
to: string;
|
||||
@@ -130,7 +130,7 @@ export default defineComponent({
|
||||
const values = ref<SettingsModuleBarLink | null>();
|
||||
const initialValues = ref<SettingsModuleBarLink | null>();
|
||||
|
||||
const { modules: registeredModules } = getModules();
|
||||
const { modules: registeredModules } = useExtensions();
|
||||
|
||||
const availableModulesAsBarModule = computed<SettingsModuleBarModule[]>(() => {
|
||||
return registeredModules.value
|
||||
|
||||
37
app/src/interfaces/index.test.ts
Normal file
37
app/src/interfaces/index.test.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { expect, test, describe } from 'vitest';
|
||||
import { getInternalInterfaces } from './index';
|
||||
|
||||
/**
|
||||
* Vite v3 reworked the glob import with it's own internal sorting,
|
||||
* so this is to ensure any changes to vite's internal sorting to not affect the expected sort outcome.
|
||||
*
|
||||
* @see {@link https://github.com/directus/directus/pull/15672#issuecomment-1289975933}
|
||||
*/
|
||||
|
||||
const expectedInterfacesSortOrder = [
|
||||
'boolean',
|
||||
'input',
|
||||
'input-autocomplete-api',
|
||||
'input-code',
|
||||
'input-hash',
|
||||
'input-multiline',
|
||||
'input-rich-text-html',
|
||||
'input-rich-text-md',
|
||||
'system-collection',
|
||||
];
|
||||
|
||||
describe('interfaces', () => {
|
||||
test('getInterfaces() should return the expected sorted order of interfaces', () => {
|
||||
const interfaces = getInternalInterfaces();
|
||||
const interfaceIds = interfaces.map((inter) => inter.id);
|
||||
const interfacesToTest = interfaceIds.filter((inter) => expectedInterfacesSortOrder.includes(inter));
|
||||
|
||||
// test all expected sort order
|
||||
expect(interfacesToTest).toEqual(expectedInterfacesSortOrder);
|
||||
// test whether input interface is the first one among all the input related interfaces
|
||||
expect(interfacesToTest.filter((inter) => inter.includes('input')).findIndex((inter) => inter === 'input')).toBe(0);
|
||||
// system-collection should be not be at the start after sorting. Currently it is within the folder called "_system",
|
||||
// so it will be the first item by default without our added sort logic in getInterface().
|
||||
expect(interfacesToTest.findIndex((inter) => inter === 'system-collection')).not.toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -1,13 +1,22 @@
|
||||
import { shallowRef, Ref } from 'vue';
|
||||
import { App } from 'vue';
|
||||
import { InterfaceConfig } from '@directus/shared/types';
|
||||
import { sortBy } from 'lodash';
|
||||
|
||||
const interfacesRaw: Ref<InterfaceConfig[]> = shallowRef([]);
|
||||
const interfaces: Ref<InterfaceConfig[]> = shallowRef([]);
|
||||
export function getInternalInterfaces(): InterfaceConfig[] {
|
||||
const interfaces = import.meta.glob<InterfaceConfig>(['./*/index.ts', './_system/*/index.ts'], {
|
||||
import: 'default',
|
||||
eager: true,
|
||||
});
|
||||
|
||||
export function getInterfaces(): { interfaces: Ref<InterfaceConfig[]>; interfacesRaw: Ref<InterfaceConfig[]> } {
|
||||
return { interfaces, interfacesRaw };
|
||||
return sortBy(Object.values(interfaces), 'id');
|
||||
}
|
||||
|
||||
export function getInterface(name?: string | null): InterfaceConfig | undefined {
|
||||
return !name ? undefined : interfaces.value.find(({ id }) => id === name);
|
||||
export function registerInterfaces(interfaces: InterfaceConfig[], app: App): void {
|
||||
for (const inter of interfaces) {
|
||||
app.component(`interface-${inter.id}`, inter.component);
|
||||
|
||||
if (typeof inter.options !== 'function' && Array.isArray(inter.options) === false && inter.options !== null) {
|
||||
app.component(`interface-options-${inter.id}`, inter.options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { getRootPath } from '@/utils/get-root-path';
|
||||
import { App } from 'vue';
|
||||
import { getInterfaces } from './index';
|
||||
import { InterfaceConfig } from '@directus/shared/types';
|
||||
|
||||
const { interfacesRaw } = getInterfaces();
|
||||
|
||||
export async function registerInterfaces(app: App): Promise<void> {
|
||||
const interfaceModules = import.meta.globEager('./*/**/index.ts');
|
||||
|
||||
const interfaces: InterfaceConfig[] = Object.values(interfaceModules).map((module) => module.default);
|
||||
|
||||
try {
|
||||
const customInterfaces: { default: InterfaceConfig[] } = import.meta.env.DEV
|
||||
? await import('@directus-extensions-interface')
|
||||
: await import(/* @vite-ignore */ `${getRootPath()}extensions/interfaces/index.js`);
|
||||
|
||||
interfaces.push(...customInterfaces.default);
|
||||
} catch (err: any) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(`Couldn't load custom interfaces`);
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(err);
|
||||
}
|
||||
|
||||
interfacesRaw.value = interfaces;
|
||||
|
||||
interfacesRaw.value.forEach((inter: InterfaceConfig) => {
|
||||
app.component(`interface-${inter.id}`, inter.component);
|
||||
|
||||
if (typeof inter.options !== 'function' && Array.isArray(inter.options) === false && inter.options !== null) {
|
||||
app.component(`interface-options-${inter.id}`, inter.options);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user