Register interfaces dynamically, prevent recursive import

This commit is contained in:
rijkvanzanten
2020-08-26 18:17:15 -04:00
parent 248d5d18d9
commit 7cba365316
11 changed files with 308 additions and 80 deletions

View File

@@ -1,69 +1,12 @@
import InterfaceCheckboxes from './checkboxes';
import InterfaceCode from './code';
import InterfaceCollections from './collections';
import InterfaceColor from './color';
import InterfaceDateTime from './datetime';
import InterfaceDivider from './divider/';
import InterfaceDropdown from './dropdown/';
import InterfaceDropdownMultiselect from './dropdown-multiselect/';
import InterfaceFile from './file';
import InterfaceFiles from './files';
import InterfaceHash from './hash';
import InterfaceIcon from './icon';
import InterfaceImage from './image';
import InterfaceManyToMany from './many-to-many';
import InterfaceManyToOne from './many-to-one';
import InterfaceMarkdown from './markdown';
import InterfaceNotice from './notice';
import InterfaceNumeric from './numeric/';
import InterfaceOneToMany from './one-to-many';
import InterfaceRadioButtons from './radio-buttons';
import InterfaceRepeater from './repeater';
import InterfaceSlider from './slider/';
import InterfaceSlug from './slug';
import InterfaceStatus from './status';
import InterfaceTags from './tags';
import InterfaceTFASetup from './tfa-setup';
import InterfaceTextarea from './textarea/';
import InterfaceTextInput from './text-input/';
import InterfaceToggle from './toggle/';
import InterfaceTranslations from './translations';
import InterfaceUser from './user';
import InterfaceWYSIWYG from './wysiwyg/';
import { ref, Ref } from '@vue/composition-api';
import { InterfaceConfig } from './types';
export const interfaces = [
InterfaceCheckboxes,
InterfaceCode,
InterfaceCollections,
InterfaceColor,
InterfaceDateTime,
InterfaceDivider,
InterfaceDropdown,
InterfaceDropdownMultiselect,
InterfaceFile,
InterfaceFiles,
InterfaceHash,
InterfaceIcon,
InterfaceImage,
InterfaceManyToMany,
InterfaceManyToOne,
InterfaceMarkdown,
InterfaceNotice,
InterfaceNumeric,
InterfaceOneToMany,
InterfaceRadioButtons,
InterfaceRepeater,
InterfaceSlider,
InterfaceSlug,
InterfaceStatus,
InterfaceTags,
InterfaceTFASetup,
InterfaceTextarea,
InterfaceTextInput,
InterfaceToggle,
InterfaceTranslations,
InterfaceUser,
InterfaceWYSIWYG,
];
let interfaces: Ref<InterfaceConfig[]>;
export default interfaces;
export function getInterfaces() {
if (!interfaces) {
interfaces = ref([]);
}
return interfaces;
}

View File

@@ -0,0 +1,11 @@
import { defineInterface } from '../define';
import InterfaceOptions from './interface-options.vue';
export default defineInterface(({ i18n }) => ({
id: 'interface-options',
name: 'Interface Options',
icon: 'box',
component: InterfaceOptions,
types: ['string'],
options: [],
}));

View File

@@ -0,0 +1,66 @@
<template>
<v-notice v-if="!selectedInterface">
{{ $t('select_interface') }}
</v-notice>
<v-notice v-else-if="!selectedInterface.options">
{{ $t('no_options_available') }}
</v-notice>
<div class="inset" v-else>
<v-form
v-if="Array.isArray(selectedInterface.options)"
:fields="selectedInterface.options"
primary-key="+"
:edits="value"
@input="$listeners.input"
/>
<component
:value="value"
@input="$listeners.input"
:field-data="fieldData"
:is="`interface-options-${selectedInterface.id}`"
v-else
/>
</div>
</template>
<script lang="ts">
import { defineComponent, computed, inject, ref } from '@vue/composition-api';
import { getInterfaces } from '@/interfaces';
export default defineComponent({
props: {
value: {
type: Object,
default: null,
},
interfaceField: {
type: String,
required: true,
},
},
setup(props, { parent }) {
const interfaces = getInterfaces();
const values = inject('form-values', ref<Record<string, any>>({}));
const selectedInterface = computed(() => {
if (!values.value[props.interfaceField]) return;
return interfaces.value.find((inter) => inter.id === values.value[props.interfaceField]);
});
return { selectedInterface, values };
},
});
</script>
<style lang="scss" scoped>
.inset {
padding: 8px;
border: var(--border-width) solid var(--border-normal);
border-radius: var(--border-radius);
}
</style>

View File

@@ -0,0 +1,11 @@
import { defineInterface } from '../define';
import InterfaceInterface from './interface.vue';
export default defineInterface(({ i18n }) => ({
id: 'interface',
name: 'Interface',
icon: 'box',
component: InterfaceInterface,
types: ['string'],
options: [],
}));

View File

@@ -0,0 +1,34 @@
<template>
<v-select :items="items" @input="$listeners.input" :value="value" />
</template>
<script lang="ts">
import { defineComponent, computed } from '@vue/composition-api';
import i18n from '@/lang';
import { getInterfaces } from '@/interfaces';
export default defineComponent({
props: {
value: {
type: String,
default: null,
},
},
setup() {
const interfaces = getInterfaces();
const items = computed(() => {
return interfaces.value
.filter((inter) => inter.relationship === undefined && inter.system !== true)
.map((inter) => {
return {
text: inter.name,
value: inter.id,
};
});
});
return { items };
},
});
</script>

View File

@@ -1,10 +1,22 @@
import registerComponent from '@/utils/register-component/';
import interfaces from './index';
import { getInterfaces } from './index';
import { Component } from 'vue';
interfaces.forEach((inter) => {
const interfaces = getInterfaces();
const context = require.context('.', true, /^.*index\.ts$/);
const modules = context
.keys()
.map((key) => context(key))
.map((mod) => mod.default)
.filter((m) => m);
interfaces.value = modules;
interfaces.value.forEach((inter) => {
registerComponent('interface-' + inter.id, inter.component);
if (typeof inter.options === 'function') {
registerComponent(`interface-options-${inter.id}`, inter.options);
if (typeof inter.options !== 'function' && Array.isArray(inter.options) === false) {
registerComponent(`interface-options-${inter.id}`, inter.options as Component);
}
});

View File

@@ -1,5 +1,6 @@
import { defineInterface } from '../define';
import InterfaceRepeater from './repeater.vue';
import RepeaterOptions from './options.vue';
export default defineInterface(({ i18n }) => ({
id: 'repeater',
@@ -7,5 +8,5 @@ export default defineInterface(({ i18n }) => ({
icon: 'replay',
component: InterfaceRepeater,
types: ['json'],
options: [],
options: RepeaterOptions,
}));

View File

@@ -0,0 +1,143 @@
<template>
<div>
<p class="type-label">Template</p>
<v-input class="input" v-model="template" :placeholder="`{{ field }}`" />
<p class="type-label">Fields</p>
<repeater v-model="repeaterValue" :template="`{{ field }} — {{ interface }}`" :fields="repeaterFields" />
</div>
</template>
<script lang="ts">
import { defineComponent, PropType, computed } from '@vue/composition-api';
import Repeater from './repeater.vue';
import { Field, FieldMeta } from '@/types';
import i18n from '@/lang';
export default defineComponent({
components: { Repeater },
props: {
value: {
type: Object as PropType<any>,
default: null,
},
},
setup(props, { emit }) {
const repeaterValue = computed({
get() {
return props.value?.fields?.map((field: Field) => field.meta);
},
set(newVal: FieldMeta[]) {
const fields = newVal.map((meta) => ({
field: meta.field,
meta,
}));
emit('input', {
...(props.value || {}),
fields: fields,
});
},
});
const repeaterFields: DeepPartial<Field>[] = [
{
name: i18n.tc('field', 1),
field: 'field',
type: 'string',
meta: {
interface: 'text-input',
width: 'half',
sort: 1,
options: {
font: 'monospace',
},
},
schema: null,
},
{
name: i18n.t('field_width'),
field: 'width',
type: 'string',
meta: {
interface: 'dropdown',
width: 'half',
sort: 2,
options: {
choices: [
{
value: 'half',
text: i18n.t('half_width'),
},
{
value: 'full',
text: i18n.t('full_width'),
},
],
},
},
schema: null,
},
{
name: i18n.t('interface'),
field: 'interface',
type: 'string',
meta: {
interface: 'interface',
width: 'half',
sort: 3,
},
schema: null,
},
{
name: i18n.t('note'),
field: 'note',
type: 'string',
meta: {
interface: 'text-input',
width: 'half',
sort: 4,
},
schema: null,
},
{
name: i18n.t('options'),
field: 'options',
type: 'string',
meta: {
interface: 'interface-options',
width: 'full',
sort: 5,
options: {
interfaceField: 'interface',
},
},
},
];
const template = computed({
get() {
return props.value?.template;
},
set(newTemplate: string) {
emit('input', {
...(props.value || {}),
template: newTemplate,
});
},
});
return { repeaterValue, repeaterFields, template };
},
});
</script>
<style lang="scss" scoped>
.type-label {
margin-bottom: 4px;
}
.input {
margin-bottom: 24px;
}
</style>

View File

@@ -16,7 +16,5 @@ export type InterfaceConfig = {
};
export type InterfaceContext = { i18n: VueI18n };
export type InterfaceDefineParam = InterfaceDefineParamGeneric<InterfaceConfig>;
export type InterfaceDefineParamGeneric<T> = T | ((context: InterfaceContext) => T);

View File

@@ -22,14 +22,19 @@
v-model="fieldData.meta.options"
/>
<component v-model="fieldData" :is="`interface-options-${selectedInterface.id}`" v-else />
<component
v-model="fieldData.meta.options"
:field-data="fieldData"
:is="`interface-options-${selectedInterface.id}`"
v-else
/>
</template>
</div>
</template>
<script lang="ts">
import { defineComponent, computed } from '@vue/composition-api';
import interfaces from '@/interfaces';
import { getInterfaces } from '@/interfaces';
import { state } from '../store';
@@ -41,8 +46,10 @@ export default defineComponent({
},
},
setup(props) {
const interfaces = getInterfaces();
const availableInterfaces = computed(() =>
interfaces.filter((inter) => {
interfaces.value.filter((inter) => {
// Filter out all system interfaces
if (inter.system !== undefined && inter.system === true) return false;
@@ -72,7 +79,7 @@ export default defineComponent({
);
const selectedInterface = computed(() => {
return interfaces.find((inter) => inter.id === state.fieldData.meta.interface);
return interfaces.value.find((inter) => inter.id === state.fieldData.meta.interface);
});
return { fieldData: state.fieldData, selectItems, selectedInterface };

View File

@@ -105,7 +105,7 @@
import { defineComponent, PropType, ref, computed } from '@vue/composition-api';
import { Field } from '@/types';
import { useCollectionsStore, useFieldsStore } from '@/stores/';
import interfaces from '@/interfaces';
import { getInterfaces } from '@/interfaces';
export default defineComponent({
props: {
@@ -119,6 +119,8 @@ export default defineComponent({
},
},
setup(props) {
const interfaces = getInterfaces();
const editActive = ref(false);
const fieldsStore = useFieldsStore();
const collectionsStore = useCollectionsStore();
@@ -127,7 +129,7 @@ export default defineComponent({
const { duplicateActive, duplicateName, collections, duplicateTo, saveDuplicate, duplicating } = useDuplicate();
const interfaceName = computed(() => {
return interfaces.find((inter) => inter.id === props.field.meta.interface)?.name;
return interfaces.value.find((inter) => inter.id === props.field.meta.interface)?.name;
});
return {