Add layout registration logic (#120)

* Move modules types into extensions file

* Rename modules store to extensions store

* Start on registering tabular

* Add register component util

* Register layouts

* Build bundle for modern browsers

Snuck this commit into the wrong branch, just because I can

* Add tests for layout registration + add dummy tabular
This commit is contained in:
Rijk van Zanten
2020-02-25 09:48:36 -05:00
committed by GitHub
parent a6d17706e2
commit 0669515c36
20 changed files with 219 additions and 30 deletions

View File

@@ -6,7 +6,7 @@
"author": "Rijk van Zanten <rijk@rngr.org>",
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build": "vue-cli-service build --modern",
"test": "vue-cli-service test:unit",
"lint": "vue-cli-service lint",
"lint:styles": "stylelint \"**/*.{vue,scss}\"",

View File

@@ -0,0 +1,84 @@
import Vue from 'vue';
import VueCompositionAPI from '@vue/composition-api';
import { LayoutConfig } from '@/types/extensions';
import layoutRegistration from './register';
import * as registerUtil from '@/utils/register-component';
import { useExtensionsStore } from '@/stores/extensions';
describe('Layouts / Register', () => {
beforeAll(() => {
Vue.config.productionTip = false;
Vue.config.devtools = false;
Vue.use(VueCompositionAPI);
});
it('Calls registerComponent util', () => {
jest.spyOn(registerUtil, 'registerComponent');
const testLayout: LayoutConfig = {
id: 'test',
name: 'Test',
icon: 'test',
component: {
render(h) {
return h('div');
}
}
};
layoutRegistration.registerLayout(testLayout);
expect(registerUtil.registerComponent).toHaveBeenCalledWith(
'layout-test',
testLayout.component
);
});
it('Adds the layout to the extensions store', () => {
const extensionsStore = useExtensionsStore({});
extensionsStore.state.layouts = [];
const testLayout: LayoutConfig = {
id: 'test',
name: 'Test',
icon: 'test',
component: {
render(h) {
return h('div');
}
}
};
layoutRegistration.registerLayout(testLayout);
expect(extensionsStore.state.layouts).toEqual([
{
id: 'test',
name: 'Test',
icon: 'test'
}
]);
});
it('Calls the name function if its a method', () => {
const testLayout: LayoutConfig = {
id: 'test',
name: jest.fn(),
icon: 'test',
component: {
render(h) {
return h('div');
}
}
};
layoutRegistration.registerLayout(testLayout);
expect(testLayout.name).toHaveBeenCalled();
});
it('Registers all global layouts', () => {
jest.spyOn(layoutRegistration, 'registerLayout');
layoutRegistration.registerGlobalLayouts();
expect(layoutRegistration.registerLayout).toHaveBeenCalled();
});
});

31
src/layouts/register.ts Normal file
View File

@@ -0,0 +1,31 @@
import TabularLayout from './tabular/';
import { LayoutConfig } from '@/types/extensions';
import { registerComponent } from '@/utils/register-component';
import { useExtensionsStore } from '@/stores/extensions';
import { i18n } from '@/lang';
const lib = {
registerLayout,
registerGlobalLayouts
};
export default lib;
export function registerLayout(config: LayoutConfig) {
const extensionsStore = useExtensionsStore();
registerComponent(`layout-${config.id}`, config.component);
const name = typeof config.name === 'function' ? config.name(i18n) : config.name;
const layoutForStore = {
id: config.id,
name: name,
icon: config.icon
};
extensionsStore.state.layouts = [...extensionsStore.state.layouts, layoutForStore];
}
export function registerGlobalLayouts() {
[TabularLayout].forEach(lib.registerLayout);
}

View File

@@ -0,0 +1,11 @@
import { LayoutConfig } from '@/types/extensions';
import Component from './tabular.vue';
const TabularLayout: LayoutConfig = {
id: 'tabular',
name: 'Tabular',
icon: 'box',
component: Component
};
export default TabularLayout;

View File

@@ -0,0 +1,22 @@
<template>
<div class="tabular-layout">
Tabular
</div>
</template>
<script lang="ts">
import { createComponent } from '@vue/composition-api';
export default createComponent({
props: {},
setup() {
return {};
}
});
</script>
<style lang="scss" scoped>
.tabular-layout {
background-color: red;
}
</style>

View File

@@ -6,10 +6,12 @@ import './directives/register';
import './components/register';
import './views/register';
import { registerGlobalModules } from './modules/register';
import { registerGlobalLayouts } from './layouts/register';
import router from './router';
import i18n from './lang/';
registerGlobalModules();
registerGlobalLayouts();
Vue.config.productionTip = false;

View File

@@ -1,4 +1,4 @@
import { ModuleConfig } from '@/types/modules';
import { ModuleConfig } from '@/types/extensions';
import Collections from './collections.vue';
const config: ModuleConfig = {

View File

@@ -1,4 +1,4 @@
import { ModuleConfig } from '@/types/modules';
import { ModuleConfig } from '@/types/extensions';
import Files from './files.vue';
const config: ModuleConfig = {

View File

@@ -3,8 +3,8 @@ import VueCompositionAPI from '@vue/composition-api';
import { RouteConfig } from 'vue-router';
import * as router from '@/router';
import moduleRegistration from './register';
import { useModulesStore } from '@/stores/modules';
import { ModuleConfig } from '@/types/modules';
import { useExtensionsStore } from '@/stores/extensions';
import { ModuleConfig } from '@/types/extensions';
describe('Modules / Register', () => {
beforeAll(() => {
@@ -38,7 +38,7 @@ describe('Modules / Register', () => {
});
it('Adds the modules to the store', () => {
const modulesStore = useModulesStore({});
const extensionsStore = useExtensionsStore({});
const testModules: ModuleConfig[] = [
{
@@ -55,7 +55,7 @@ describe('Modules / Register', () => {
moduleRegistration.registerModules(testModules);
expect(modulesStore.state.modules).toEqual([
expect(extensionsStore.state.modules).toEqual([
{
id: 'test',
icon: 'box',

View File

@@ -3,9 +3,9 @@ import FilesModule from './files/';
import SettingsModule from './settings/';
import UsersModule from './users/';
import { RouteConfig } from 'vue-router';
import { ModuleConfig, Module } from '@/types/modules';
import { ModuleConfig, Module } from '@/types/extensions';
import { replaceRoutes } from '@/router';
import useModulesStore from '@/stores/modules';
import useExtensionsStore from '@/stores/extensions';
import { i18n } from '@/lang';
const lib = {
@@ -23,7 +23,7 @@ export function insertBeforeProjectWildcard(routes: RouteConfig[], moduleRoutes:
}
export function registerModules(modules: ModuleConfig[]) {
const modulesStore = useModulesStore();
const extensionsStore = useExtensionsStore();
/** @todo
* This is where we will download the module definitions for custom modules
@@ -51,7 +51,7 @@ export function registerModules(modules: ModuleConfig[]) {
};
});
modulesStore.state.modules = modulesForStore;
extensionsStore.state.modules = modulesForStore;
}
export function registerGlobalModules() {

View File

@@ -1,4 +1,4 @@
import { ModuleConfig } from '@/types/modules';
import { ModuleConfig } from '@/types/extensions';
import Settings from './settings.vue';
const config: ModuleConfig = {

View File

@@ -1,4 +1,4 @@
import { ModuleConfig } from '@/types/modules';
import { ModuleConfig } from '@/types/extensions';
import Users from './users.vue';
const config: ModuleConfig = {

View File

@@ -0,0 +1,10 @@
import { createStore } from 'pinia';
import { Module, Layout } from '@/types/extensions';
export const useExtensionsStore = createStore({
id: 'extensions',
state: () => ({
modules: [] as Module[],
layouts: [] as Layout[]
})
});

View File

@@ -0,0 +1,4 @@
import { useExtensionsStore } from './extensions';
export { useExtensionsStore };
export default useExtensionsStore;

View File

@@ -1,4 +0,0 @@
import { useModulesStore } from './modules';
export { useModulesStore };
export default useModulesStore;

View File

@@ -1,9 +0,0 @@
import { createStore } from 'pinia';
import { Module } from '@/types/modules';
export const useModulesStore = createStore({
id: 'modules',
state: () => ({
modules: [] as Module[]
})
});

View File

@@ -1,6 +1,6 @@
import { Component } from 'vue';
import Vuei18n from 'vue-i18n';
import { RouteConfig } from 'vue-router';
import { i18n } from '@/lang/';
export type Module = {
id: string;
@@ -14,3 +14,16 @@ export type ModuleConfig = {
icon: string;
name: string | ((i18n: Vuei18n) => Vuei18n.TranslateResult);
};
export type Layout = {
id: string;
icon: string;
name: string | Vuei18n.TranslateResult;
};
export type LayoutConfig = {
id: string;
icon: string;
name: string | ((i18n: Vuei18n) => Vuei18n.TranslateResult);
component: Component;
};

View File

@@ -0,0 +1,15 @@
import Vue, { Component } from 'vue';
import registerComponent from './register-component';
describe('Utils / Register Component', () => {
it('Calls Vue.component with the given arguments', () => {
const spy = jest.spyOn(Vue, 'component');
const component: Component = {
render(h) {
return h('div');
}
};
registerComponent('test', component);
expect(spy).toHaveBeenCalledWith('test', component);
});
});

View File

@@ -0,0 +1,10 @@
import Vue, { Component } from 'vue';
function registerComponent(id: string, component: Component): void;
function registerComponent(id: string, component: Parameters<typeof Vue.component>[1]): void;
function registerComponent(id: string, component: any) {
Vue.component(id, component);
}
export { registerComponent };
export default registerComponent;

View File

@@ -10,7 +10,7 @@
<script lang="ts">
import { createComponent, computed } from '@vue/composition-api';
import ModuleBarLogo from './_module-bar-logo.vue';
import { useModulesStore } from '@/stores/modules/';
import { useExtensionsStore } from '@/stores/extensions/';
import { useProjectsStore } from '@/stores/projects';
export default createComponent({
@@ -18,12 +18,12 @@ export default createComponent({
ModuleBarLogo
},
setup() {
const modulesStore = useModulesStore();
const extensionsStore = useExtensionsStore();
const projectsStore = useProjectsStore();
const { currentProjectKey } = projectsStore.state;
const modules = computed(() =>
modulesStore.state.modules.map(module => ({
extensionsStore.state.modules.map(module => ({
...module,
to: `/${currentProjectKey}/${module.id}/`
}))