Add panels as extension type

This commit is contained in:
rijkvanzanten
2021-05-25 20:24:10 -04:00
parent 1bc6826e1c
commit 215ccbf64c
12 changed files with 244 additions and 4 deletions

View File

@@ -1,5 +1,6 @@
import { getDisplays } from '@/displays';
import { getInterfaces } from '@/interfaces';
import { getPanels } from '@/panels';
import { getLayouts } from '@/layouts';
import { getModules } from '@/modules';
import { useCollectionsStore, useFieldsStore } from '@/stores';
@@ -10,6 +11,7 @@ import { i18n, Language, loadedLanguages } from './index';
const { modules, modulesRaw } = getModules();
const { layouts, layoutsRaw } = getLayouts();
const { interfaces, interfacesRaw } = getInterfaces();
const { panels, panelsRaw } = getPanels();
const { displays, displaysRaw } = getDisplays();
export async function setLanguage(lang: Language): Promise<boolean> {
@@ -33,6 +35,7 @@ export async function setLanguage(lang: Language): Promise<boolean> {
modules.value = translate(modulesRaw.value);
layouts.value = translate(layoutsRaw.value);
interfaces.value = translate(interfacesRaw.value);
panels.value = translate(panelsRaw.value);
displays.value = translate(displaysRaw.value);
collectionsStore.translateCollections();

View File

@@ -1176,3 +1176,7 @@ layouts:
calendar: Calendar
start_date_field: Start Date Field
end_date_field: End Date Field
panels:
metric:
name: Metric
description: Show a single value based on a query

View File

@@ -50,13 +50,14 @@ import './views/register';
import { registerInterfaces } from './interfaces/register';
import { loadModules } from './modules/register';
import { registerPanels } from './panels/register';
import { registerLayouts } from './layouts/register';
import { registerDisplays } from './displays/register';
import App from './app.vue';
async function init() {
await Promise.all([registerInterfaces(), registerDisplays(), registerLayouts(), loadModules()]);
await Promise.all([registerInterfaces(), registerPanels(), registerDisplays(), registerLayouts(), loadModules()]);
Vue.config.productionTip = false;

View File

@@ -1,6 +1,7 @@
import { defineModule } from '@/modules/define';
import InsightsOverview from './routes/overview.vue';
import InsightsDashboard from './routes/dashboard.vue';
import InsightsPanelConfiguration from './routes/panel-configuration.vue';
export default defineModule({
id: 'insights',
@@ -17,6 +18,16 @@ export default defineModule({
path: '/:primaryKey',
component: InsightsDashboard,
props: true,
children: [
{
name: 'panel-detail',
path: ':panelKey',
props: true,
components: {
detail: InsightsPanelConfiguration,
},
},
],
},
],
order: 30,

View File

@@ -8,6 +8,10 @@
</template>
<template #actions>
<v-button v-if="editMode" rounded icon outlined :to="`/insights/${currentDashboard.id}/panel/+`">
<v-icon name="add" />
</v-button>
<v-button rounded icon @click="editMode = !editMode">
<v-icon :name="editMode ? 'check' : 'edit'" />
</v-button>
@@ -17,7 +21,11 @@
<insights-navigation />
</template>
<div class="workspace" :class="{ editing: editMode }"></div>
<div class="workspace" :class="{ editing: editMode }">
<!-- <div class="dummy" /> -->
</div>
<router-view name="detail" :dashboard-key="primaryKey" />
</private-view>
</template>
@@ -44,7 +52,13 @@ export default defineComponent({
insightsStore.state.dashboards.find((dashboard) => dashboard.id === props.primaryKey)
);
return { currentDashboard, editMode };
const stagedPanels = ref([]);
const panels = computed(() => {
return currentDashboard.value?.panels || [];
});
return { currentDashboard, editMode, panels };
},
});
</script>
@@ -61,6 +75,10 @@ export default defineComponent({
overflow: visible;
}
.workspace > * {
z-index: 2;
}
.workspace::before {
position: absolute;
top: -4px;
@@ -68,7 +86,7 @@ export default defineComponent({
display: block;
width: calc(100% + 8px);
height: calc(100% + 8px);
background-image: radial-gradient(#efefef 25%, transparent 25%);
background-image: radial-gradient(#efefef 20%, transparent 20%);
background-position: -6px -6px;
background-size: 20px 20px;
opacity: 0;
@@ -80,4 +98,17 @@ export default defineComponent({
.workspace.editing::before {
opacity: 1;
}
/* .dummy {
--pos-x: 5;
--pos-y: 5;
--width: 5;
--height: 5;
display: block;
grid-row: var(--pos-y) / span var(--height);
grid-column: var(--pos-x) / span var(--width);
background-color: var(--primary);
border-radius: var(--border-radius);
} */
</style>

View File

@@ -0,0 +1,80 @@
<template>
<v-drawer active :title="values.name || $t('panel')">
<div class="content">
<v-fancy-select class="select" :items="selectItems" v-model="values.type" />
</div>
</v-drawer>
</template>
<script lang="ts">
import { computed, defineComponent, ref } from '@vue/composition-api';
import { useInsightsStore } from '@/stores';
import { getPanels } from '@/panels';
import { FancySelectItem } from '@/components/v-fancy-select/types';
export default defineComponent({
name: 'PanelConfiguration',
props: {
primaryKey: {
type: String,
default: '+',
},
dashboardKey: {
type: String,
required: true,
},
},
setup(props) {
const { panels } = getPanels();
const insightsStore = useInsightsStore();
const existing = computed(() =>
insightsStore.state.dashboards
.find((dashboard) => dashboard.id === props.dashboardKey)!
.panels.find((panel) => panel.id === props.primaryKey)
);
const edits = ref({});
const values = computed(() => {
if (existing.value) return { ...existing.value, ...edits.value };
return edits.value;
});
const selectItems = computed<FancySelectItem[]>(() => {
return panels.value.map((panel) => {
const item: FancySelectItem = {
text: panel.name,
icon: panel.icon,
description: panel.description,
value: panel.id,
};
return item;
});
});
return { existing, values, selectItems };
},
});
</script>
<style scoped>
.content {
padding: var(--content-padding);
padding-top: 0;
padding-bottom: var(--content-padding);
}
</style>
<!--
type
options
--
toggle name
icon color
note
-->

13
app/src/panels/define.ts Normal file
View File

@@ -0,0 +1,13 @@
import { PanelConfig, PanelDefineParam } from './types';
export function definePanel(config: PanelDefineParam): PanelConfig {
let options: PanelConfig;
if (typeof config === 'function') {
options = config();
} else {
options = config;
}
return options;
}

17
app/src/panels/index.ts Normal file
View File

@@ -0,0 +1,17 @@
import { ref, Ref } from '@vue/composition-api';
import { PanelConfig } from './types';
let panelsRaw: Ref<PanelConfig[]>;
let panels: Ref<PanelConfig[]>;
export function getPanels(): Record<string, Ref<PanelConfig[]>> {
if (!panelsRaw) {
panelsRaw = ref([]);
}
if (!panels) {
panels = ref([]);
}
return { panels, panelsRaw };
}

View File

@@ -0,0 +1,13 @@
import { definePanel } from '../define';
import PanelMetric from './metric.vue';
export default definePanel({
id: 'metric',
name: '$t:panels.metric.name',
description: '$t:panels.metric.description',
icon: 'functions',
component: PanelMetric,
options: [],
minWidth: 4,
minHeight: 4,
});

View File

@@ -0,0 +1,3 @@
<template>
<div>Metric</div>
</template>

View File

@@ -0,0 +1,47 @@
// import api from '@/api';
// import { getRootPath } from '@/utils/get-root-path';
import registerComponent from '@/utils/register-component/';
// import asyncPool from 'tiny-async-pool';
import { Component } from 'vue';
import { getPanels } from './index';
import { PanelConfig } from './types';
const { panelsRaw } = getPanels();
export async function registerPanels(): Promise<void> {
const context = require.context('.', true, /^.*index\.ts$/);
const modules = context
.keys()
.map((key) => context(key))
.map((mod) => mod.default)
.filter((m) => m);
// try {
// const customResponse = await api.get('/extensions/panels/');
// const panels: string[] = customResponse.data.data || [];
// await asyncPool(5, panels, async (panelName) => {
// try {
// const result = await import(
// /* webpackIgnore: true */ getRootPath() + `extensions/panels/${panelName}/index.js`
// );
// modules.push(result.default);
// } catch (err) {
// console.warn(`Couldn't load custom panel "${panelName}":`, err);
// }
// });
// } catch {
// console.warn(`Couldn't load custom panels`);
// }
panelsRaw.value = modules;
panelsRaw.value.forEach((panel: PanelConfig) => {
registerComponent('panel-' + panel.id, panel.component);
if (typeof panel.options !== 'function' && Array.isArray(panel.options) === false) {
registerComponent(`panel-options-${panel.id}`, panel.options as Component);
}
});
}

17
app/src/panels/types.ts Normal file
View File

@@ -0,0 +1,17 @@
import { Field } from '@/types';
import { AsyncComponent, Component } from 'vue';
import VueI18n from 'vue-i18n';
export interface PanelConfig {
id: string;
name: string;
icon: string;
description?: string | VueI18n.TranslateResult;
component: Component | AsyncComponent;
options: DeepPartial<Field>[] | Component | AsyncComponent;
minWidth: number;
minHeight: number;
}
export type PanelDefineParam = PanelDefineParamGeneric<PanelConfig>;
export type PanelDefineParamGeneric<T> = T | (() => T);