diff --git a/frontend-frame/src/components/RightRail.vue b/frontend-frame/src/components/RightRail.vue new file mode 100644 index 0000000..6e12c1c --- /dev/null +++ b/frontend-frame/src/components/RightRail.vue @@ -0,0 +1,124 @@ + + + + + + + X + + + + + + + + + + diff --git a/frontend-frame/src/components/{{component}}/{{component}}Details.vue b/frontend-frame/src/components/{{component}}/{{component}}Details.vue index 569e0a5..e5f505e 100644 --- a/frontend-frame/src/components/{{component}}/{{component}}Details.vue +++ b/frontend-frame/src/components/{{component}}/{{component}}Details.vue @@ -11,7 +11,7 @@ import { {{Component}}Config } from './{{component}}Types'; import { {{component}}Service } from './{{component}}Service'; @Component -export default class {{Component}}Editor extends Vue { +export default class {{Component}}Details extends Vue { @Prop() private id!: string; private {{component}}: {{Component}}Config = { // SYSTEM-BUILDER-init-props diff --git a/frontend-frame/src/components/{{component}}/{{component}}Editor.vue b/frontend-frame/src/components/{{component}}/{{component}}Editor.vue index 71a0656..b5d3f16 100644 --- a/frontend-frame/src/components/{{component}}/{{component}}Editor.vue +++ b/frontend-frame/src/components/{{component}}/{{component}}Editor.vue @@ -18,10 +18,7 @@ import { {{Component}}Config } from './{{component}}Types'; @Component export default class {{Component}}Editor extends Vue { - @Prop() private {{component}}_id!: string; - private {{component}}: {{Component}}Config = { - // SYSTEM-BUILDER-init-props - }; + @Prop() private {{component}}!: {{Component}}Config; private loading: boolean = false; private errorMessage: string = ''; @@ -34,18 +31,18 @@ export default class {{Component}}Editor extends Vue { } private mounted() { - this.loadDetails(); + // this.loadDetails(); } - private loadDetails() { - this.loading = true; - this.errorMessage = ''; - {{component}}Service.get{{Component}}(this.{{component}}_id).then((res: {{Component}}Config) => { - this.loading = false; - this.errorMessage = ''; - this.{{component}} = res; - }).catch(this.handleError.bind(this)); - } + // private loadDetails() { + // this.loading = true; + // this.errorMessage = ''; + // {{component}}Service.get{{Component}}(this.{{component}}_id).then((res: {{Component}}Config) => { + // this.loading = false; + // this.errorMessage = ''; + // this.{{component}} = res; + // }).catch(this.handleError.bind(this)); + // } private save{{Component}}() { this.loading = true; diff --git a/frontend-frame/src/components/{{component}}/{{component}}List.vue b/frontend-frame/src/components/{{component}}/{{component}}List.vue index 3e56556..e3394ee 100644 --- a/frontend-frame/src/components/{{component}}/{{component}}List.vue +++ b/frontend-frame/src/components/{{component}}/{{component}}List.vue @@ -1,5 +1,6 @@ + {{errorMessage}} @@ -9,7 +10,10 @@ - {{errorMessage}} + + <{{Component}}Details :client="selected{{Component}}" @close-{{component}}-details="toggleEdit{{Component}}">{{Component}}Details> + + @@ -26,6 +30,9 @@ export default class {{Component}}Editor extends Vue { private filterItems: {{Component}}Config = { // SYSTEM-BUILDER-init-props }; + private selected{{Component}} = { + + } ; // SYSTEM-BUILDER-component-variables // SYSTEM-BUILDER-component-functions @@ -36,6 +43,10 @@ export default class {{Component}}Editor extends Vue { // TODO: add a search function if there is an endpoint with type 'search' + private toggleEdit{{Component}}() { + + } + private handleError(err: any) { // error this.loading = false; diff --git a/frontend-frame/src/main.ts b/frontend-frame/src/main.ts index 7755d5c..f0033a6 100644 --- a/frontend-frame/src/main.ts +++ b/frontend-frame/src/main.ts @@ -3,6 +3,10 @@ import App from './App.vue' import router from './router'; import './registerServiceWorker' +import RightRail from './components/RightRail.vue'; + +Vue.component('right-rail', RightRail); + Vue.config.productionTip = false new Vue({ diff --git a/src/frontend-services/fe-service-creator.ts b/src/frontend-services/fe-service-creator.ts index daa2d6f..e502e6a 100644 --- a/src/frontend-services/fe-service-creator.ts +++ b/src/frontend-services/fe-service-creator.ts @@ -11,7 +11,7 @@ import fs from "fs"; import {buildServiceFunctionName, getFuncParams} from "../views/service-creator"; import {METHOD, URL} from "../views/routes-creator"; import {buildDetailsView} from "./details-view-builder"; -import {buildListView} from "./list-view-builder"; +import {buildListView} from "./list-view-builder-v2"; import {buildEditorView} from "./editor-view-builder"; import {buildTypesView} from "./types-view-builder"; import {buildRoute} from "./route-view-builder"; diff --git a/src/frontend-services/list-view-builder-v2.ts b/src/frontend-services/list-view-builder-v2.ts new file mode 100644 index 0000000..fda30da --- /dev/null +++ b/src/frontend-services/list-view-builder-v2.ts @@ -0,0 +1,361 @@ +import {ColumnDef, ColumnRef, ComponentDef, EndpointDef, Filter, SystemDef, TableDef} from "../systemGenService"; +import path from "path"; +import fs from "fs"; +import {createDetailsInitValues, findColumn, lowercaseFirstLetter, makeSet, uppercaseFirstLetter} from "../helpers"; +import pluralize from "pluralize"; +import {getFuncParams} from "../views/service-creator"; + +const INIT_VALUE = { + "blob": "''", + "boolean": "false", + "date": "''", + "dateTIME": "''", + "number": "0", + "string": "''" +}; + +export function buildListView(component: ComponentDef, outDir: string, systemDef: SystemDef): Promise { + let hasDeleteEndpoint = false; + let hasItemEndpoint = false; + let hasUpdateEndpoint = false; + let hasCreateEndpoint = false; + let hasSearchEndpoint = false; + let listEndpoints: EndpointDef[] = component.endpoints.filter((epd: EndpointDef) => { + if (epd.type === 'delete') { + hasDeleteEndpoint = true; + } + if (epd.type === 'create') { + hasCreateEndpoint = true; + } + if (epd.type === 'update') { + hasUpdateEndpoint = true; + } + if (epd.type === 'item') { + hasItemEndpoint = true; + } + if (epd.type === 'search') { + hasSearchEndpoint = true; + } + return epd.type === 'list' || epd.type === 'search'; + }); + + let templateData: {[key:string]:string} = {}; + templateData.component = lowercaseFirstLetter(component.component); + templateData.Component = uppercaseFirstLetter(component.component); + templateData.componentFunctions = ''; + templateData.componentProps = createDetailsInitValues(listEndpoints[0], systemDef); + templateData.componentVariables = ''; + templateData.rightRail = ''; + templateData.moreImports = ''; + templateData.componentConfig = ''; + templateData.createButton = ''; + + let vueComponents: string[] = []; + let filterParams: Filter[] = []; + for (let i in listEndpoints) { + if (i === '0') { + templateData.filterProps = createDetailsInitValues(listEndpoints[i], systemDef); + } + if (listEndpoints[i].filters) { + filterParams = [...filterParams, ...listEndpoints[i].filters as Filter[]]; + } + templateData.componentFunctions = templateData.componentFunctions + createListRequestFunc(listEndpoints[i], systemDef, hasDeleteEndpoint); + } + if (listEndpoints.length) { + templateData.itemPropsHTML = createListMarkup(listEndpoints[0], hasDeleteEndpoint, hasUpdateEndpoint); + } + if (hasDeleteEndpoint && listEndpoints.length) { + templateData.componentFunctions = templateData.componentFunctions + createFEDeleteCode(listEndpoints[0]); + } + + if (hasItemEndpoint) { + // Add import for details + templateData.moreImports = templateData.moreImports + createDetailsImports(listEndpoints[0]); + // add component config + vueComponents.push(`${uppercaseFirstLetter(component.component)}Details,`); + // add details in right rail, with functions to handle opening and closing + templateData.rightRail = templateData.rightRail + createRightRailDetails(listEndpoints[0]); + + templateData.componentVariables = templateData.componentVariables + detailsOpenVariables(listEndpoints[0]); + } + + // combine all duplicate filterParams + // @ts-ignore + let filterSet: Filter[] = makeSet(filterParams, 'param'); + // add filterParams to the markup as filter options + if (listEndpoints.length && filterSet.length) { + templateData.filterOptions = createFilterMarkup(listEndpoints[0], filterSet, hasSearchEndpoint); + } + + if (hasCreateEndpoint) { + // add button to open editor without a selected item + templateData.createButton = templateData.createButton + createButtonHTML(listEndpoints[0]); + } + + // add create component to sidebar + if (hasCreateEndpoint || hasUpdateEndpoint) { + templateData.moreImports = templateData.moreImports + createEditorImports(listEndpoints[0]); + templateData.rightRail = templateData.rightRail + editRightRailHTML(listEndpoints[0]); + vueComponents.push(`${uppercaseFirstLetter(component.component)}Editor,`); + templateData.componentVariables = templateData.componentVariables + editorOpenVariables(listEndpoints[0]); + } + + if (hasCreateEndpoint || hasUpdateEndpoint || hasItemEndpoint) { + templateData.componentFunctions = templateData.componentFunctions + addResetSelectedFunction(listEndpoints[0], systemDef); + } + + // TODO: add details component to sidebar + // TODO: if delete endpoint exists add delete button next to each row + + if (vueComponents.length) { + let comp = vueComponents.join(` +`); + templateData.componentConfig = `({ + components: { + ${comp} + }, + })`; + } + + let output: string = buildFile(templateData); + return writeFile(output, path.join(outDir, `${component.component}List.vue`)) +} + +function writeFile(output: string, outdir: string) { + return new Promise((resolve) => { + fs.writeFile(outdir, output, 'utf8', () => { + resolve(); + }); + }); +} + +function createDetailsImports(view: EndpointDef) { + return `import ${uppercaseFirstLetter(view.component)}Details from './${lowercaseFirstLetter(view.component)}Details.vue'; +`; +} + +function createEditorImports(view: EndpointDef) { + return `import ${uppercaseFirstLetter(view.component)}Editor from './${lowercaseFirstLetter(view.component)}Editor.vue'; +`; +} + +function createButtonHTML(view: EndpointDef) { + let out = `New ${uppercaseFirstLetter(view.component)} + `; + return out; +} + +function createRightRailDetails(view: EndpointDef) { + let out = ` + <${lowercaseFirstLetter(view.component)}Details :id="selected${uppercaseFirstLetter(view.component)}.${lowercaseFirstLetter(view.component)}_id" @close-${lowercaseFirstLetter(view.component)}-details="detailsOpen = !detailsOpen; resetSelectedItem()">${lowercaseFirstLetter(view.component)}Details> + + `; + return out; +} + +function editRightRailHTML(view: EndpointDef) { + let out = ` + <${lowercaseFirstLetter(view.component)}Editor :${lowercaseFirstLetter(view.component)}="selected${uppercaseFirstLetter(view.component)}.${lowercaseFirstLetter(view.component)}_id" @close-${lowercaseFirstLetter(view.component)}-editor="editorOpen = !editorOpen; resetSelectedItem()">${lowercaseFirstLetter(view.component)}Editor> + + `; + return out; +} + +function detailsOpenVariables(view: EndpointDef) { + let out = `private detailsOpen: boolean = false; + `; + return out; +} + +function editorOpenVariables(view: EndpointDef) { + let out = `private editorOpen: boolean = false; + `; + return out; +} + +function addResetSelectedFunction(view: EndpointDef, systemDef: SystemDef) { + let out = `private resetSelectedItem() { + this.selected${uppercaseFirstLetter(view.component)} = { + ${createDetailsInitValues(view, systemDef)} + }; + } + + `; + return out; +} + +function createFilterMarkup(view: EndpointDef, filters: Filter[], hasSearchEndpoint=false): string { + let out = ``; + let inputType: {[key: string]: string} = { + "blob": "text", + "boolean": "checkbox", + "date": "date", + "dateTIME": "datetime-local", + "number": "number", + "string": "text", + }; + filters.forEach((f: Filter) => { + if (f.type) { + // base the filter markup on type + out = out + ` + ${f.param}: + + + `; + } else { + // default to a string filter + out = out + ` + + + `; + } + }); + out = out + ` + Search + + `; + return out; +} + +function createListMarkup(view: EndpointDef, hasDeleteEndpoint: boolean, hasEditEndpoint: boolean): string { + let out = ``; + + view.columns.forEach((column: ColumnRef) => { + out = out + `{{ item.${column.name} }} + `; + }); + out = out + `details + `; + if (hasEditEndpoint) { + out = out + `edit + `; + } + if (hasDeleteEndpoint) { + out = out + `delete + `; + } + + return out; +} + +function createListRequestFunc(view: EndpointDef, systemDef: SystemDef, hasDeleteEndpoint: boolean) { + let isSearch: boolean = view.type === 'search'; + let params: string[] = getFuncParams(view); + params = params.map((param: string) => { + return 'this.filterItems.' + param; + }); + params.push('0'); // offset + params.push('1000'); // limit + + let out = `private ${isSearch ? 'search' : 'get'}${uppercaseFirstLetter(pluralize(view.component))}() { + this.loading = true; + this.errorMessage = ''; + ${view.component}Service.${isSearch ? 'search' : 'get'}${uppercaseFirstLetter(pluralize(view.component))}(${params.join(', ')}).then((listItems: ${uppercaseFirstLetter(view.component)}Config[]) => { + // success + this.loading = false; + this.errorMessage = ''; + this.items = listItems; + }).catch(this.handleError.bind(this)); + } + + `; + return out; +} + +function createFEDeleteCode(view: EndpointDef) { + let out = `private deleteItem(itemKey: string) { + this.loading = true; + this.errorMessage = ''; + ${view.component}Service.delete${uppercaseFirstLetter(view.component)}(itemKey).then(() => { + this.loading = false; + this.errorMessage = ''; + return this.get${uppercaseFirstLetter(pluralize(view.component))}(); + }).catch(this.handleError.bind(this)); + } + + `; + return out; +} + +function buildFile(data: any) { + let template = ` + + {{errorMessage}} + ${data.createButton} + + ${data.filterOptions} + + + + + {{ item.${data.component}_id }} + ${data.itemPropsHTML} + + + + ${data.rightRail} + + + + + + + + + +`; + return template; +} +