diff --git a/src/app/components/components.module.ts b/src/app/components/components.module.ts index 1220ee4..1b137c3 100644 --- a/src/app/components/components.module.ts +++ b/src/app/components/components.module.ts @@ -44,6 +44,7 @@ import {PageLinkRendererComponent} from './editor/database/renderers/page-link-r import {PageLinkEditorComponent} from './editor/database/editors/page-link/page-link-editor.component'; import {FormInputComponent} from './nodes/form-input/form-input.component'; import {FormInputOptionsComponent} from './nodes/form-input/options/form-input-options.component'; +import {DatabaseLinkComponent} from './editor/forms/database-link.component'; @NgModule({ declarations: [ @@ -81,6 +82,7 @@ import {FormInputOptionsComponent} from './nodes/form-input/options/form-input-o PageLinkEditorComponent, FormInputComponent, FormInputOptionsComponent, + DatabaseLinkComponent, ], imports: [ CommonModule, @@ -131,6 +133,7 @@ import {FormInputOptionsComponent} from './nodes/form-input/options/form-input-o PageLinkEditorComponent, FormInputComponent, FormInputOptionsComponent, + DatabaseLinkComponent, ], exports: [ NodePickerComponent, @@ -167,6 +170,7 @@ import {FormInputOptionsComponent} from './nodes/form-input/options/form-input-o PageLinkEditorComponent, FormInputComponent, FormInputOptionsComponent, + DatabaseLinkComponent, ] }) export class ComponentsModule {} diff --git a/src/app/components/editor/forms/database-link.component.html b/src/app/components/editor/forms/database-link.component.html new file mode 100644 index 0000000..0e0ca90 --- /dev/null +++ b/src/app/components/editor/forms/database-link.component.html @@ -0,0 +1,92 @@ +
+
+
Link Form to Database
+ + +
+ + + + Destination Database + + +
{{ port.name }}
+
+
+
+
+ + + +

Form-to-Database Mapping

+

In order for the form to save properly, define a mapping of which form fields should be stored in which database fields.

+
+
+ + + + Form Field (Source) + + + + + + Database Column (Destination) + + + + + + + + + + + + +
+
+
\ No newline at end of file diff --git a/src/app/components/editor/forms/database-link.component.scss b/src/app/components/editor/forms/database-link.component.scss new file mode 100644 index 0000000..d2ad0ea --- /dev/null +++ b/src/app/components/editor/forms/database-link.component.scss @@ -0,0 +1,35 @@ +.container { + display: flex; + flex-direction: column; + height: 100%; + + .header { + background: lightgrey; + display: flex; + flex-direction: row; + + .title { + flex: 1; + padding: 10px; + font-size: 1.2em; + } + + .close { + padding: 10px 15px; + background: #ff6666; + color: white; + } + + .revert { + color: red; + background: #eaeaea; + } + } + + .contents { + height: 100%; + overflow-y: hidden; + display: flex; + flex-direction: column; + } +} \ No newline at end of file diff --git a/src/app/components/editor/forms/database-link.component.ts b/src/app/components/editor/forms/database-link.component.ts new file mode 100644 index 0000000..0a1b022 --- /dev/null +++ b/src/app/components/editor/forms/database-link.component.ts @@ -0,0 +1,106 @@ +import {Component, Input, OnInit} from '@angular/core'; +import {AlertController, ModalController} from '@ionic/angular'; +import {EditorService} from '../../../service/editor.service'; +import {debug} from '../../../utility'; +import {NodeTypeIcons} from '../../../structures/node-types'; +import {Database, DatabaseColumn} from '../../../structures/db-api'; +import {DbApiService} from '../../../service/db-api.service'; +import HostRecord from '../../../structures/HostRecord'; + +export interface FormDatabaseMap { + formNodeId?: string; + databaseColumnId?: string; + + formNode?: any; + databaseColumn?: DatabaseColumn; +} + +@Component({ + selector: 'noded-form-database-link', + templateUrl: './database-link.component.html', + styleUrls: ['./database-link.component.scss'], +}) +export class DatabaseLinkComponent implements OnInit { + @Input() pageId: string; + @Input() editorUUID: string; + + public readonly typeIcons = NodeTypeIcons; + public databases: Database[] = []; + public selectedDatabase?: Database; + public selectedDatabaseColumns: DatabaseColumn[] = []; + public mapping: FormDatabaseMap[] = []; + public formFields: any[] = []; + + constructor( + protected alerts: AlertController, + protected modals: ModalController, + protected editorService: EditorService, + protected dbApi: DbApiService, + ) {} + + async ngOnInit() { + debug('Database link component', this); + this.editorService = this.editorService.getEditor(this.editorUUID); + + this.databases = await this.dbApi.getDatabases(); + + this.formFields = this.editorService.immutableNodes + .filter(x => x.isForm()) + .map(x => { + const data = {...x.AdditionalData}; + data.NodeId = x.UUID; + return data; + }); + + const meta = this.editorService.metadata; + if ( meta?.formDatabaseId ) { + this.selectedDatabase = this.databases.find(x => x.uuid === meta.formDatabaseId); + await this.onDatabaseChange(); + } + + if ( Array.isArray(meta?.formMapping) ) { + this.mapping = meta.formMapping.map(x => { + return { + formNode: this.formFields.find(field => field.NodeId === x.formNodeId), + databaseColumn: this.selectedDatabaseColumns.find(column => column.uuid === x.databaseColumnId), + }; + }); + } + } + + onAddMappingClick(event) { + this.mapping.push({} as FormDatabaseMap); + } + + async onDatabaseChange() { + this.mapping = []; + this.selectedDatabaseColumns = await this.selectedDatabase.columns(); + } + + onMappingDelete(item) { + this.mapping = this.mapping.filter(x => x !== item); + } + + dismiss(save = false) { + if ( save ) { + const merge = { + formDatabaseId: this.selectedDatabase?.uuid, + formMapping: this.mapping.map(x => { + return { + formNodeId: x.formNode?.NodeId || x.formNodeId, + databaseColumnId: x.databaseColumn?.uuid || x.databaseColumnId, + }; + }), + }; + + this.editorService.metadata = { + ...this.editorService.metadata, + ...merge, + }; + + this.editorService.triggerSave(); + } + + this.modals.dismiss(); + } +} diff --git a/src/app/pages/editor/editor.page.html b/src/app/pages/editor/editor.page.html index dacf9a0..3321da4 100644 --- a/src/app/pages/editor/editor.page.html +++ b/src/app/pages/editor/editor.page.html @@ -27,7 +27,7 @@ {{ (editorService.isSaving || editorService.willSave) ? 'Saving...' : 'Saved!' }} diff --git a/src/app/pages/editor/editor.page.ts b/src/app/pages/editor/editor.page.ts index fdeacb5..fd5ca4a 100644 --- a/src/app/pages/editor/editor.page.ts +++ b/src/app/pages/editor/editor.page.ts @@ -8,6 +8,8 @@ import {EditorService} from '../../service/editor.service'; import {NodeTypeIcons} from '../../structures/node-types'; import {OptionMenuComponent} from '../../components/option-menu/option-menu.component'; import {VersionModalComponent} from '../../components/version-modal/version-modal.component'; +import {debug} from '../../utility'; +import {DatabaseLinkComponent} from '../../components/editor/forms/database-link.component'; @Component({ selector: 'app-editor', @@ -17,6 +19,7 @@ import {VersionModalComponent} from '../../components/version-modal/version-moda export class EditorPage implements OnInit { public typeIcons = NodeTypeIcons; @Input() pageId: string; + @Input() isFilloutMode = false; @Input() hosted = false; @Input() version?: number; @@ -42,6 +45,7 @@ export class EditorPage implements OnInit { ) { this.route.params.subscribe(params => { this.pageId = params.id; + debug('Got page ID from route:', this.pageId, params); }); this.editorService = editorService.getEditor(); @@ -138,6 +142,7 @@ export class EditorPage implements OnInit { } async onPageMenuClick(event: MouseEvent) { + debug('Page type: ', this.pageType) const popover = await this.popover.create({ event, component: OptionMenuComponent, @@ -149,6 +154,14 @@ export class EditorPage implements OnInit { value: 'view-versions', title: 'View other versions of this page', }, + ...(this.editorService.currentPageType === 'form' ? [ + { + name: 'Link to Database', + icon: NodeTypeIcons.db, + value: 'link-to-db', + title: 'Configure the database where submissions of this form will be stored', + }, + ] : []), ], }, }); @@ -158,12 +171,38 @@ export class EditorPage implements OnInit { if ( data === 'view-versions' ) { await this.showVersionModal(); + } else if ( data === 'link-to-db' ) { + await this.showFormDatabaseLinkModal(); } }); await popover.present(); } + async showFormDatabaseLinkModal() { + const modal = await this.modals.create({ + component: DatabaseLinkComponent, + componentProps: { + pageId: this.pageId, + editorUUID: this.editorService.instanceUUID, + }, + cssClass: 'modal-med', + }); + + modal.onDidDismiss().then(data => { + debug('Database link closed', data); + }); + + const modalState = { + modal : true, + desc : 'Link form to database' + }; + + history.pushState(modalState, null); + + await modal.present(); + } + async showVersionModal() { const modal = await this.modals.create({ component: VersionModalComponent, @@ -173,8 +212,8 @@ export class EditorPage implements OnInit { cssClass: 'modal-big', }); - modal.onDidDismiss().then(reload => { - if ( reload ) { + modal.onDidDismiss().then(data => { + if ( data.data ) { this.editorService.startEditing(this.pageId); } }); diff --git a/src/app/service/editor.service.ts b/src/app/service/editor.service.ts index 19a1437..a83d87e 100644 --- a/src/app/service/editor.service.ts +++ b/src/app/service/editor.service.ts @@ -72,6 +72,19 @@ export class EditorService { return !!this.currentPage; } + public get metadata() { + if ( this.currentPage.AdditionalData ) { + return this.currentPage.AdditionalData; + } + + return {}; + } + + public set metadata(data: any) { + this.currentPage.AdditionalData = data; + this.dirtyOverride = true; + } + public get immutableNodes(): HostRecord[] { return [...this.currentNodes]; } diff --git a/src/app/structures/PageRecord.ts b/src/app/structures/PageRecord.ts index d45bd0b..e43485a 100644 --- a/src/app/structures/PageRecord.ts +++ b/src/app/structures/PageRecord.ts @@ -23,6 +23,7 @@ export default class PageRecord { public ChildPageIds: Array; public level: 'view'|'manage'|'update'|false; public PageType: 'page' | 'form' = 'page'; + public AdditionalData: any; constructor(data: any = {Name: 'Click to edit...'}) { [ @@ -40,6 +41,7 @@ export default class PageRecord { 'ChildPageIds', 'level', 'PageType', + 'AdditionalData', ].forEach(field => { if ( field in data ) { this[field] = data[field]; @@ -61,7 +63,8 @@ export default class PageRecord { 'UpdatedAt', 'CreatedUserId', 'UpdateUserId', - 'ChildPageIds' + 'ChildPageIds', + 'AdditionalData', ].forEach(field => { if ( field in this ) { data[field] = this[field]; diff --git a/src/app/utility.ts b/src/app/utility.ts index c5a2d3b..6df9b95 100644 --- a/src/app/utility.ts +++ b/src/app/utility.ts @@ -29,8 +29,14 @@ export function isDebug() { return environment.outputDebug; } +export function getCaller(error: Error) { + const stackLines = error.stack.split(/\n|\sat\s/).map(x => x.trim()).filter(Boolean); + const messageInStack = (new Error()).stack.split(/\n|\sat\s/).map(x => x.trim()).filter(Boolean)?.[0] === 'Error'; + return stackLines?.[messageInStack ? 2 : 1]?.split(/@|\s\(/)?.[0]?.replace(/[\/\\<]/g, ''); +} + export function debug(...out: any[]) { - const caller = (new Error())?.stack?.split('\n')?.[1]?.split('@')?.[0]?.replace(/[\/\\<]/g, ''); + const caller = getCaller(new Error()); // Define different types of styles const baseStyles = [ diff --git a/src/global.scss b/src/global.scss index fe642d0..c371c94 100644 --- a/src/global.scss +++ b/src/global.scss @@ -157,6 +157,14 @@ hr { } } +.mr-10 { + margin-right: 10px; +} + +ionic-selectable-modal { + border: 1px solid lightgrey; +} + .modal-big { .modal-wrapper { min-height: calc(100vh - 30px); @@ -202,7 +210,7 @@ body.dark { border-radius: 4px; } - .clear-btn { + h1, h2, h3, h4, h5, h6, p, .clear-btn { color: white; } }