UNPKG

@silexlabs/grapesjs-data-source

Version:
495 lines (472 loc) 13.6 kB
import { createRef, Ref, ref } from 'lit/directives/ref.js' import {repeat} from 'lit/directives/repeat.js' import GraphQL, { GraphQLOptions } from '../datasources/GraphQL' import { DATA_SOURCE_CHANGED, DATA_SOURCE_ERROR, DATA_SOURCE_READY, DataSourceEditor, DataSourceEditorViewOptions, IDataSourceModel } from '../types' import { getDefaultOptions, getElementFromOption } from '../utils' import { css, html, LitElement, render } from 'lit' import { customElement, property } from 'lit/decorators.js' const COMMON_STYLES = css` :host { font-family: var(--gjs-main-font); font-size: var(--gjs-font-size); } .ds-field { padding: 10px; } .ds-field > span { display: block; } hr.ds-separator { border: none; border-top: 1px solid var(--ds-button-bg); } .ds-field input, .ds-field select { background-color: var(--gjs-main-dark-color); border: none; box-shadow: none; border-radius: 2px; box-sizing: border-box; padding: 0; position: relative; padding: 10px; color: inherit; width: 100%; } .ds-btn-prim { color:inherit; background-color:var(--gjs-main-light-color); border-radius:2px; padding:3px 6px; padding:var(--gjs-input-padding); cursor:pointer; border:none } .ds-btn-prim:active { background-color:var(--gjs-main-light-color) } .ds-btn-danger { color: var(--gjs-light-color); background-color: transparent; } .ds-btn-danger:hover { color: var(--ds-highlight); } [disabled], [readonly] { font-style: italic; } ` export default (editor: DataSourceEditor, options: Partial<DataSourceEditorViewOptions> = {}) => { // Settings dialog if (options.settingsEl) { // Get the container element for the UI const settingsEl = getElementFromOption(options.settingsEl, 'options.settingsEl') const dsSettings: Ref<SettingsDataSources> = createRef() editor.on(`${DATA_SOURCE_CHANGED} ${DATA_SOURCE_ERROR} ${DATA_SOURCE_READY}`, () => { if(dsSettings.value) { dsSettings.value.dataSources = [...editor.DataSourceManager] dsSettings.value.requestUpdate() } else { renderSettings(editor, dsSettings, settingsEl) } }) renderSettings(editor, dsSettings, settingsEl) } } function renderSettings(editor: DataSourceEditor, dsSettings: Ref, settingsEl: HTMLElement) { render(html` <ds-settings ${ref(dsSettings)} .dataSources=${[]} @change=${(e: CustomEvent) => { const ds = e.detail as IDataSourceModel editor.DataSourceManager //.set([ds], { merge: true, remove: false }) .get(ds.get('id')) ?.set(ds.toJSON()) //.reset(dsSettings.value?.dataSources || []) }} @add=${(e: CustomEvent) => { const ds = e.detail as GraphQLOptions const newDS = new GraphQL(ds) editor.DataSourceManager.add(newDS) }} @add-top=${(e: CustomEvent) => { const ds = e.detail as GraphQLOptions const newDS = new GraphQL(ds) editor.DataSourceManager.add(newDS, { at: 0 }) }} @delete=${(e: CustomEvent) => { const ds = e.detail as IDataSourceModel editor.DataSourceManager.remove(ds) }} ></ds-settings> `, settingsEl) } /** * <ds-settings> is a web component that renders the settings dialog for the DataSourceEditor plugin */ @customElement('ds-settings') class SettingsDataSources extends LitElement { @property({ type: Array }) dataSources: IDataSourceModel[] constructor() { super() this.dataSources = [] } connectedCallback() { super.connectedCallback() } static styles = [ COMMON_STYLES, css` .ds-btn-prim--large { padding: 10px; margin: auto; display: block; } .ds-btn-prim--icon { background-color: var(--ds-primary); position: absolute; right: 20px; } `, ] protected render() { const dsDataSource: Ref<SettingsDataSources> = createRef() const options: GraphQLOptions = getDefaultOptions(this.dataSources.length.toString()) return html` <section> <!-- <button type="button" class="ds-btn-prim ds-btn-prim--icon" @click=${() => { this.dispatchEvent(new CustomEvent('add-top', { detail: options })) }}>\u2795</button> --> <hr class="ds-separator"> ${repeat(this.dataSources.filter(ds => !ds.hidden), (ds: IDataSourceModel) => ds.get('id'), (ds: IDataSourceModel) => html` <ds-settings__data-source ${ref(dsDataSource)} .dataSource=${ds} @change=${(e: CustomEvent) => { e.preventDefault() e.stopImmediatePropagation() this.dispatchEvent(new CustomEvent('change', { detail: ds })) }} @delete=${() => { this.dispatchEvent(new CustomEvent('delete', { detail: ds })) }} ></ds-settings__data-source> <hr class="ds-separator"> `)} <button type="button" class="ds-btn-prim ds-btn-prim--large" @click=${() => { this.dispatchEvent(new CustomEvent('add', { detail: options })) }}>Add a Data Source</button> </section> ` } } @customElement('ds-settings__data-source') // eslint-disable-next-line @typescript-eslint/no-unused-vars class SettingsDataSource extends LitElement { @property({ type: Object }) dataSource: IDataSourceModel | null errorMessage: string = '' connected: boolean = false constructor() { super() this.dataSource = null } connectedCallback() { super.connectedCallback() } connectDataSource() { if(!this.dataSource) throw new Error('No data source provided') this.dataSource.connect().then(() => { this.dispatchEvent(new CustomEvent('change')) this.errorMessage = '' this.connected = true this.requestUpdate() }).catch((err: Error) => { console.error('Data source connection error', { err }) this.errorMessage = err.message this.connected = false this.requestUpdate() }) } static styles = [ css` form { display: flex; flex-direction: column; align-items: stretch; } form :focus { outline: 1px solid var(--ds-highlight); } .ds-field--large { flex: 1 1 auto; } .ds-property__wrapper { display: flex; flex-direction: column; width: 100%; flex-wrap: wrap; } .ds-property__wrapper--horiz { flex-direction: row; } .ds-property__wrapper--vert { flex-direction: column; } .ds-button-bar { display: flex; justify-content: space-between; } .ds-no-resize { flex: 0 0 auto; } `, COMMON_STYLES, ] protected render() { if(!this.dataSource) throw new Error('No data source provided') const dsHeaders: Ref<SettingsHeaders> = createRef() return html` <form ?readonly=${this.dataSource.get('readonly') !== false} @submit=${(e: Event) => { e.preventDefault() e.stopImmediatePropagation() this.connectDataSource() }} > <h3 class="ds-property__title"> ${this.dataSource.get('label')} <small>${this.dataSource.get('readonly') !== false ? ' (Read-only)' : ''}</small> </h3> <div class="ds-property__wrapper ds-property__wrapper--horiz"> <label class="ds-field"> <span>Label</span> <input type="text" name="label" value=${this.dataSource.get('label')} @input=${(e: Event) => { this.dataSource?.set('label', (e.target as HTMLInputElement).value) // Update the label in the title this.requestUpdate() }} ?readonly=${this.dataSource.get('readonly') !== false} /> </label> <label class="ds-field ds-field--large"> <span>URL</span> <input type="url" name="url" value=${this.dataSource.get('url')} @change=${(e: Event) => this.dataSource?.set('url', (e.target as HTMLInputElement).value)} ?readonly=${this.dataSource.get('readonly') !== false} /> </label> <label class="ds-field"> <span>ID</span> <input type="text" name="id" value=${this.dataSource.get('id')} readonly disabled /> </label> <label class="ds-field"> <span>Type</span> <select name="type" readonly disabled > <option value="graphql" selected>GraphQL</option> </select> </label> <label class="ds-field"> <span>Method</span> <select name="method" @change=${(e: Event) => this.dataSource?.set('method', (e.target as HTMLInputElement).value)} ?readonly=${this.dataSource.get('readonly') !== false} ?disabled=${this.dataSource.get('readonly') !== false} > <option value="POST" ?selected=${this.dataSource.get('method') === 'POST'}>POST</option> <option value="GET" ?selected=${this.dataSource.get('method') === 'GET'}>GET</option> </select> </label> </div> <div class="ds-field"> <details> <summary>HTTP Headers</summary> <ds-settings__headers ${ref(dsHeaders)} .headers=${this.dataSource.get('headers')} @change=${() => { this.dataSource?.set('headers', dsHeaders.value?.headers) dsHeaders.value?.requestUpdate() }} ?readonly=${this.dataSource.get('readonly') !== false} ></ds-settings__headers> </details> <div class="ds-field ds-button-bar"> <div> <div> <p>Status: ${this.dataSource.isConnected() ? '\u2713 Connected' : '\u2717 Unknown'}</p> <p>${this.errorMessage}</p> </div> </div> <div class="ds-no-resize"> <div> ${this.dataSource.get('readonly') !== false ? '' : html` <button type="button" class="ds-btn-prim ds-btn-danger" @click=${() => { this.dispatchEvent(new CustomEvent('delete')) }} >Delete</button> `} <button type="submit" class="ds-btn-prim ds-btn-primary" >Test connection</button> </div> </div> </div> </div> </form> ` } } @customElement('ds-settings__headers') class SettingsHeaders extends LitElement { @property({ type: Array }) headers: Record<string, string> @property({ type: Boolean }) readonly: boolean static styles = [ css` fieldset { display: block; border: none; padding: 0; } ul { list-style: none; padding: 0; } ul > li { display: flex; align-items: center; justify-content: space-between; } ul > li > label { flex: 1 1 auto; } ul > li > button { margin: 10px; } `, COMMON_STYLES, ] constructor() { super() this.headers = {} this.readonly = false } connectedCallback() { super.connectedCallback() } protected render() { return html` <div class="ds-field"> <fieldset> ${this.readonly ? '' : html` <button type="button" class="ds-btn-prim" @click=${() => { // Default name and value let name = 'Authorization' let value = 'Bearer XXXXXX' // Make sure the header name is unique // Add a number if the header already exists let i = 0 while(typeof this.headers[name] !== 'undefined') { i++ name = `Header ${i}` value = '' } // Add the header this.headers = { ...this.headers, [name]: value, } this.dispatchEvent(new CustomEvent('change')) }} >Add a header</button> `} <ul> ${Object.entries(this.headers).map(([name, value]) => html` <li> <label class="ds-field"> <span>Name</span> <input type="text" value=${name} name=${`header-key-${encodeURI(name)}`} @change=${(e: Event) => { const target = e.target as HTMLInputElement if(!target.value) return if (typeof this.headers[name] !== 'undefined') delete this.headers[name] this.headers[target.value] = value this.dispatchEvent(new CustomEvent('change')) }} /> </label> <label class="ds-field"> <span>Value</span> <input type="text" value=${value} name=${`header-value-${encodeURI(value)}`} @change=${(e: Event) => { const target = e.target as HTMLInputElement this.headers[name] = target.value this.dispatchEvent(new CustomEvent('change')) }} /> </label> ${this.readonly ? '' : html` <button type="button" class="ds-btn-prim" @click=${() => { if (typeof this.headers[name] !== 'undefined') delete this.headers[name] this.dispatchEvent(new CustomEvent('change')) }} .disabled=${this.readonly} >Delete</button> `} </li> `)} </ul> </fieldset> </div> ` } }