@silexlabs/grapesjs-data-source
Version:
Grapesjs Data Source
1,252 lines (1,242 loc) • 62.4 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const ref_js_1 = require("lit/directives/ref.js");
const repeat_js_1 = require("lit/directives/repeat.js");
const GraphQL_1 = __importDefault(require("../datasources/GraphQL"));
const types_1 = require("../types");
const utils_1 = require("../utils");
const dataSourceRegistry_1 = require("../model/dataSourceRegistry");
const lit_1 = require("lit");
const decorators_js_1 = require("lit/decorators.js");
const COMMON_STYLES = (0, lit_1.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 {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none;
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;
}
/* Modal specific styles */
:host(.ds-modal) {
max-width: 600px;
width: 90vw;
}
`;
exports.default = (editor, options = {}) => {
// Settings dialog
if (options.settingsEl) {
// Get the container element for the UI
const settingsEl = (0, utils_1.getElementFromOption)(options.settingsEl, 'options.settingsEl');
const dsSettings = (0, ref_js_1.createRef)();
editor.on(`${types_1.DATA_SOURCE_CHANGED} ${types_1.DATA_SOURCE_ERROR} ${types_1.DATA_SOURCE_READY} ${types_1.DATA_SOURCE_DATA_LOAD_END}`, () => {
if (dsSettings.value) {
dsSettings.value.dataSources = [...(0, dataSourceRegistry_1.getAllDataSources)()];
dsSettings.value.requestUpdate();
}
else {
renderSettings(editor, dsSettings, settingsEl);
}
});
renderSettings(editor, dsSettings, settingsEl);
// Add a data source
editor.Commands.add(types_1.COMMAND_ADD_DATA_SOURCE, {
run() {
var _a;
(_a = dsSettings.value) === null || _a === void 0 ? void 0 : _a.openDataSourceModal();
},
});
}
};
function renderSettings(editor, dsSettings, settingsEl) {
(0, lit_1.render)((0, lit_1.html) `
<ds-settings
${(0, ref_js_1.ref)(dsSettings)}
.dataSources=${[]}
.editor=${editor}
@add=${(e) => {
const ds = e.detail;
const newDS = new GraphQL_1.default(ds);
(0, dataSourceRegistry_1.addDataSource)(newDS);
}}
@add-top=${(e) => {
const ds = e.detail;
const newDS = new GraphQL_1.default(ds);
(0, dataSourceRegistry_1.addDataSource)(newDS);
}}
@change=${() => {
// Refresh the datasources list when a datasource is edited
if (dsSettings.value) {
const settings = dsSettings.value;
settings.dataSources = [...(0, dataSourceRegistry_1.getAllDataSources)()];
settings.requestUpdate();
}
}}
@delete=${(e) => {
const ds = e.detail;
(0, dataSourceRegistry_1.removeDataSource)(ds);
}}
></ds-settings>
`, settingsEl);
}
/**
* <ds-settings> is a web component that renders the settings dialog for the DataSourceEditor plugin
*/
class SettingsDataSources extends lit_1.LitElement {
constructor() {
super();
this.updateCounter = 0;
this.dataSources = [];
this.editor = null;
}
connectedCallback() {
super.connectedCallback();
}
openDataSourceModal(dataSource = null) {
var _a;
if (!this.editor)
return;
const isEdit = !!dataSource;
const title = isEdit ? 'Edit Data Source' : 'Add Data Source';
// Create or use existing data source
const ds = dataSource || new GraphQL_1.default((0, utils_1.getDefaultOptions)(this.dataSources.length.toString()));
// Create the form element
const formElement = document.createElement('ds-settings__data-source');
formElement.dataSource = ds;
formElement.isEdit = isEdit;
// Handle form events
const handleSave = () => {
if (isEdit) {
// Force new array reference and increment counter to trigger card re-renders
this.dataSources = [...this.dataSources];
this.updateCounter++;
this.dispatchEvent(new CustomEvent('change', { detail: ds }));
}
else {
this.dispatchEvent(new CustomEvent('add', { detail: ds }));
}
this.requestUpdate();
this.editor.Modal.close();
};
const handleDelete = () => {
this.dispatchEvent(new CustomEvent('delete', { detail: ds }));
this.editor.Modal.close();
};
const handleCancel = () => {
this.editor.Modal.close();
};
const handleKeyDown = (e) => {
if (e.key === 'Escape') {
this.editor.Modal.close();
}
};
formElement.addEventListener('change', handleSave);
formElement.addEventListener('cancel', handleCancel);
if (isEdit) {
formElement.addEventListener('delete', handleDelete);
}
// Add ESC key handler
document.addEventListener('keydown', handleKeyDown);
// Clean up on modal close
const modalObserver = new MutationObserver(() => {
if (!this.editor.Modal.isOpen()) {
document.removeEventListener('keydown', handleKeyDown);
modalObserver.disconnect();
}
});
const modalParent = (_a = this.editor.Modal.getContentEl()) === null || _a === void 0 ? void 0 : _a.parentElement;
if (modalParent) {
modalObserver.observe(modalParent, {
attributes: true,
attributeFilter: ['style', 'class'],
});
}
// Open the modal
this.editor.Modal.open({
title,
content: formElement,
attributes: { class: 'ds-modal' },
});
}
render() {
const visibleDataSources = this.dataSources.filter(ds => !ds.hidden);
return (0, lit_1.html) `
<section class="ds-settings">
${visibleDataSources.length === 0 ? (0, lit_1.html) `
<div class="ds-empty-state">
<div class="ds-empty-state__content">
<h3 class="ds-empty-state__title">No Data Sources</h3>
<p class="ds-empty-state__description">
Connect to GraphQL APIs to display dynamic data in your website
</p>
<button
type="button"
class="ds-btn-prim ds-btn-prim--large"
@click=${() => this.openDataSourceModal()}
>
Add Your First Data Source
</button>
</div>
</div>
` : (0, lit_1.html) `
<div class="ds-data-sources">
${(0, repeat_js_1.repeat)(visibleDataSources, (ds) => `${ds.id}-${this.updateCounter}`, (ds) => (0, lit_1.html) `
<ds-data-source-card
.dataSource=${ds}
data-id=${ds.id}
@edit=${(e) => this.openDataSourceModal(e.detail)}
@test=${(e) => this.testConnection(e.detail)}
@delete=${(e) => {
this.dispatchEvent(new CustomEvent('delete', { detail: e.detail }));
}}
></ds-data-source-card>
`)}
</div>
<div class="ds-add-section">
<button
type="button"
class="ds-btn-prim"
@click=${() => this.openDataSourceModal()}
>
Add Data Source
</button>
</div>
`}
</section>
`;
}
testConnection(dataSource) {
var _a;
// Find the card component to update its loading state
const cardElement = (_a = this.shadowRoot) === null || _a === void 0 ? void 0 : _a.querySelector(`ds-data-source-card[data-id="${dataSource.id}"]`);
if (cardElement) {
cardElement.isTestingConnection = true;
}
// Create a temporary form element to handle the connection test
const tempForm = document.createElement('ds-settings__data-source');
tempForm.dataSource = dataSource;
// Override the original connectDataSource to handle card updates
tempForm.connectDataSource = () => {
if (cardElement) {
cardElement.isTestingConnection = true;
}
dataSource.connect().then(() => {
if (cardElement) {
cardElement.isTestingConnection = false;
}
this.dispatchEvent(new CustomEvent('change', { detail: dataSource }));
this.requestUpdate();
}).catch((err) => {
console.error('Data source connection error', { err });
if (cardElement) {
cardElement.isTestingConnection = false;
}
this.requestUpdate();
});
};
tempForm.connectDataSource();
}
}
SettingsDataSources.styles = [
COMMON_STYLES,
(0, lit_1.css) `
.ds-settings {
padding: 16px 0;
}
.ds-empty-state {
display: flex;
align-items: center;
justify-content: center;
min-height: 200px;
text-align: center;
}
.ds-empty-state__content {
max-width: 300px;
}
.ds-empty-state__title {
margin: 0 0 8px 0;
font-size: 18px;
color: inherit;
}
.ds-empty-state__description {
margin: 0 0 24px 0;
color: var(--gjs-light-color);
line-height: 1.4;
}
.ds-data-sources {
margin-bottom: 16px;
}
.ds-add-section {
padding: 16px 0;
border-top: 1px solid var(--ds-button-bg);
text-align: center;
}
.ds-btn-prim--large {
padding: 12px 24px;
font-size: 14px;
}
`,
];
__decorate([
(0, decorators_js_1.property)({ type: Array }),
__metadata("design:type", Array)
], SettingsDataSources.prototype, "dataSources", void 0);
__decorate([
(0, decorators_js_1.property)({ type: Object }),
__metadata("design:type", Object)
], SettingsDataSources.prototype, "editor", void 0);
__decorate([
(0, decorators_js_1.state)(),
__metadata("design:type", Number)
], SettingsDataSources.prototype, "updateCounter", void 0);
if (!customElements.get('ds-settings')) {
customElements.define('ds-settings', SettingsDataSources);
}
class SettingsDataSource extends lit_1.LitElement {
// private unknownIcon = html`<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="12" r="2"/></svg>`
constructor() {
super();
this.isEdit = false;
// State for the creation flow
this.errorMessage = '';
this.connected = false;
this.isLoading = false;
this.creationPhase = 'config'; // config = initial form, types = type selection
this.availableTypes = []; // List of types (name + kind) from lightweight query
this.selectedTypes = new Set(); // Currently selected types
this.typeSearchQuery = ''; // Search/filter for type list
this.queryTypeName = 'Query'; // The query type name from schema introspection
this.loadingIcon = (0, lit_1.html) `<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="12" r="3"><animate attributeName="r" values="3;8;3" dur="1s" repeatCount="indefinite"/><animate attributeName="opacity" values="1;0.3;1" dur="1s" repeatCount="indefinite"/></circle></svg>`;
this.connectedIcon = (0, lit_1.html) `<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>`;
this.dataSource = null;
}
connectedCallback() {
super.connectedCallback();
}
/**
* Phase 1: Fetch lightweight type names from the GraphQL endpoint
*/
fetchTypeNames() {
return __awaiter(this, void 0, void 0, function* () {
if (!this.dataSource || !(this.dataSource instanceof GraphQL_1.default)) {
this.errorMessage = 'Invalid data source';
return;
}
this.isLoading = true;
this.errorMessage = '';
this.requestUpdate();
try {
const ds = this.dataSource;
const result = yield ds.fetchTypeNames();
// Store the queryType name from the schema
this.queryTypeName = result.queryTypeName;
// Sort types by name
this.availableTypes = result.types.sort((a, b) => a.name.localeCompare(b.name));
// For existing datasources with disabledTypes, compute selected = all - disabled
// Otherwise, apply backend-specific default selection
if (this.isEdit && ds.disabledTypes && ds.disabledTypes.length > 0) {
const allTypeNames = this.availableTypes.map(t => t.name);
const selectedNames = allTypeNames.filter(name => !ds.disabledTypes.includes(name));
this.selectedTypes = new Set(selectedNames);
}
else {
const backendType = ds.backendType || 'generic';
const defaultEnabled = GraphQL_1.default.getDefaultEnabledTypes(backendType, this.availableTypes, this.queryTypeName);
this.selectedTypes = new Set(defaultEnabled);
}
// Move to type selection phase
this.creationPhase = 'types';
this.isLoading = false;
this.requestUpdate();
}
catch (e) {
console.error('[Settings] Failed to fetch type names:', e);
this.errorMessage = e.message;
this.isLoading = false;
this.requestUpdate();
}
});
}
/**
* Check if a type is required (cannot be blacklisted)
* The queryType and all SCALAR types are required
*/
isRequiredType(typeName) {
if (typeName === this.queryTypeName)
return true;
const type = this.availableTypes.find(t => t.name === typeName);
return (type === null || type === void 0 ? void 0 : type.kind) === 'SCALAR';
}
/**
* Toggle a type's selected state
*/
toggleType(typeName) {
// Query and SCALAR types cannot be unchecked - they're always required
if (this.isRequiredType(typeName))
return;
if (this.selectedTypes.has(typeName)) {
this.selectedTypes.delete(typeName);
}
else {
this.selectedTypes.add(typeName);
}
this.selectedTypes = new Set(this.selectedTypes); // Trigger reactivity
this.requestUpdate();
}
/**
* Select all types
*/
selectAllTypes() {
this.selectedTypes = new Set(this.availableTypes.map(t => t.name));
this.requestUpdate();
}
/**
* Deselect all types (except queryType and SCALAR types which are always required)
*/
deselectAllTypes() {
// Always keep queryType and SCALAR types selected
const requiredTypes = this.availableTypes
.filter(t => t.name === this.queryTypeName || t.kind === 'SCALAR')
.map(t => t.name);
this.selectedTypes = new Set(requiredTypes);
this.requestUpdate();
}
/**
* Reset types to default selection based on backend type
*/
resetToDefaults() {
const isGraphQL = this.dataSource instanceof GraphQL_1.default;
if (!isGraphQL)
return;
const backendType = this.dataSource.backendType || 'generic';
const defaultEnabled = GraphQL_1.default.getDefaultEnabledTypes(backendType, this.availableTypes, this.queryTypeName);
this.selectedTypes = new Set(defaultEnabled);
this.requestUpdate();
}
/**
* Get filtered types based on search query
* Selected types are always shown first
*/
getFilteredTypes() {
let types = this.availableTypes;
if (this.typeSearchQuery) {
const query = this.typeSearchQuery.toLowerCase();
types = types.filter(t => t.name.toLowerCase().includes(query));
}
// Sort: selected types first, then alphabetically within each group
return types.sort((a, b) => {
const aSelected = this.selectedTypes.has(a.name);
const bSelected = this.selectedTypes.has(b.name);
if (aSelected && !bSelected)
return -1;
if (!aSelected && bSelected)
return 1;
return a.name.localeCompare(b.name);
});
}
/**
* Go back to config phase
*/
goBackToConfig() {
this.creationPhase = 'config';
this.availableTypes = [];
this.selectedTypes = new Set();
this.queryTypeName = 'Query';
this.errorMessage = '';
this.requestUpdate();
}
/**
* Handle backend type change - updates selected types based on new backend defaults
*/
onBackendTypeChange(newBackendType) {
if (!this.dataSource || !(this.dataSource instanceof GraphQL_1.default))
return;
const ds = this.dataSource;
ds.backendType = newBackendType;
// Apply new default selection based on backend type
const defaultEnabled = GraphQL_1.default.getDefaultEnabledTypes(newBackendType, this.availableTypes, this.queryTypeName);
this.selectedTypes = new Set(defaultEnabled);
this.requestUpdate();
}
connectDataSource() {
if (!this.dataSource)
throw new Error('No data source provided');
// Compute disabledTypes from selection (blacklist approach)
if (this.dataSource instanceof GraphQL_1.default && this.availableTypes.length > 0) {
const ds = this.dataSource;
const allTypeNames = this.availableTypes.map(t => t.name);
const disabledTypes = allTypeNames.filter(name => !this.selectedTypes.has(name));
// Set disabled types for persistence (only blacklist, no whitelist)
ds.disabledTypes = disabledTypes;
}
this.isLoading = true;
this.errorMessage = '';
this.requestUpdate();
this.dataSource.connect().then(() => {
this.dispatchEvent(new CustomEvent('change'));
this.errorMessage = '';
this.connected = true;
this.isLoading = false;
this.requestUpdate();
}).catch((err) => {
console.error('Data source connection error', { err });
this.errorMessage = err.message;
this.connected = false;
this.isLoading = false;
this.requestUpdate();
});
}
/**
* Render the configuration form (Phase 1)
*/
renderConfigPhase() {
if (!this.dataSource)
throw new Error('No data source provided');
const dsHeaders = (0, ref_js_1.createRef)();
return (0, lit_1.html) `
<h3 class="ds-property__title">
${this.dataSource.label || 'Unnamed'}
<small>${this.dataSource.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.label}
@input=${(e) => {
if (this.dataSource)
this.dataSource.label = e.target.value;
this.requestUpdate();
}}
?readonly=${this.dataSource.readonly !== false}
/>
</label>
<label class="ds-field ds-field--large">
<span>URL</span>
<input
type="url"
name="url"
value=${this.dataSource.url}
@input=${(e) => {
if (this.dataSource) {
this.dataSource.url = e.target.value;
this.requestUpdate();
}
}}
?readonly=${this.dataSource.readonly !== false}
/>
</label>
<!-- ID field hidden - not needed by users, auto-generated -->
<label class="ds-field">
<span>Method</span>
<select
name="method"
@change=${(e) => { if (this.dataSource)
this.dataSource.method = e.target.value; }}
?readonly=${this.dataSource.readonly !== false}
?disabled=${this.dataSource.readonly !== false}
>
<option value="POST" ?selected=${this.dataSource.method === 'POST'}>POST</option>
<option value="GET" ?selected=${this.dataSource.method === 'GET'}>GET</option>
</select>
</label>
</div>
<details class="ds-advanced-details">
<summary class="ds-advanced-summary">HTTP Headers</summary>
<div class="ds-advanced-content">
<ds-settings__headers
${(0, ref_js_1.ref)(dsHeaders)}
.headers=${this.dataSource.headers}
@change=${() => {
var _a, _b;
if (this.dataSource)
this.dataSource.headers = ((_a = dsHeaders.value) === null || _a === void 0 ? void 0 : _a.headers) || {};
(_b = dsHeaders.value) === null || _b === void 0 ? void 0 : _b.requestUpdate();
}}
?readonly=${this.dataSource.readonly !== false}
></ds-settings__headers>
</div>
</details>
<div class="ds-field">
<div class="ds-status-section">
${this.isLoading
? (0, lit_1.html) `<div class="ds-status-item ds-status-loading">${this.loadingIcon} Fetching types...</div>`
: this.errorMessage
? (0, lit_1.html) `<div class="ds-status-item ds-status-error">Error: ${this.errorMessage}</div>`
: ''}
</div>
</div>
<div class="ds-field ds-actions">
<div class="ds-actions-right">
<button
type="button"
class="ds-btn-prim"
@click=${() => this.dispatchEvent(new CustomEvent('cancel'))}
>Cancel</button>
${this.isEdit ? (0, lit_1.html) `
<button
type="button"
class="ds-btn-prim"
?disabled=${this.isLoading || !this.dataSource.url}
@click=${() => this.fetchTypeNames()}
>${this.isLoading ? 'Loading...' : 'Options'}</button>
<button
type="submit"
class="ds-btn-prim"
?disabled=${this.isLoading || !this.dataSource.url}
>${this.isLoading ? 'Applying...' : 'Apply'}</button>
` : (0, lit_1.html) `
<button
type="submit"
class="ds-btn-prim"
?disabled=${this.isLoading || !this.dataSource.url}
>${this.isLoading ? 'Fetching...' : 'Next'}</button>
`}
</div>
</div>
</div>
`;
}
/**
* Render the type selection phase (Phase 2)
*/
renderTypesPhase() {
const filteredTypes = this.getFilteredTypes();
const selectedCount = this.selectedTypes.size;
const totalCount = this.availableTypes.length;
const isGraphQL = this.dataSource instanceof GraphQL_1.default;
const backendType = isGraphQL ? this.dataSource.backendType : 'generic';
return (0, lit_1.html) `
<h3 class="ds-property__title">Data Source Options</h3>
<p class="ds-help-text">Select your backend type. Any GraphQL API is a supported, choose "GraphQL" for a generic connector.</p>
<div class="ds-backend-selector">
<fieldset class="ds-backend-options">
<label class="ds-backend-option ${backendType === 'generic' ? 'ds-backend-option--selected' : ''}">
<input
type="radio"
name="backendType"
value="generic"
?checked=${backendType === 'generic'}
@change=${() => this.onBackendTypeChange('generic')}
/>
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
<path fill="#E535AB" d="M57.468 302.66l-14.376-8.3 160.15-277.38 14.376 8.3z"/>
<path fill="#E535AB" d="M39.8 272.2h320.3v16.6H39.8z"/>
<path fill="#E535AB" d="M206.348 374.026l-160.21-92.5 8.3-14.376 160.21 92.5zM345.522 132.947l-160.21-92.5 8.3-14.376 160.21 92.5z"/>
<path fill="#E535AB" d="M54.482 132.883l-8.3-14.375 160.21-92.5 8.3 14.376z"/>
<path fill="#E535AB" d="M342.568 302.663l-160.15-277.38 14.376-8.3 160.15 277.38zM52.5 107.5h16.6v185H52.5zM330.9 107.5h16.6v185h-16.6z"/>
<path fill="#E535AB" d="M203.522 367l-7.25-12.558 139.34-80.45 7.25 12.557z"/>
<path fill="#E535AB" d="M369.5 297.9c-9.6 16.7-31 22.4-47.7 12.8-16.7-9.6-22.4-31-12.8-47.7 9.6-16.7 31-22.4 47.7-12.8 16.8 9.7 22.5 31 12.8 47.7M90.9 137c-9.6 16.7-31 22.4-47.7 12.8-16.7-9.6-22.4-31-12.8-47.7 9.6-16.7 31-22.4 47.7-12.8 16.7 9.7 22.4 31 12.8 47.7M30.5 297.9c-9.6-16.7-3.9-38 12.8-47.7 16.7-9.6 38-3.9 47.7 12.8 9.6 16.7 3.9 38-12.8 47.7-16.8 9.6-38.1 3.9-47.7-12.8M309.1 137c-9.6-16.7-3.9-38 12.8-47.7 16.7-9.6 38-3.9 47.7 12.8 9.6 16.7 3.9 38-12.8 47.7-16.7 9.6-38.1 3.9-47.7-12.8M200 395.8c-19.3 0-34.9-15.6-34.9-34.9 0-19.3 15.6-34.9 34.9-34.9 19.3 0 34.9 15.6 34.9 34.9 0 19.2-15.6 34.9-34.9 34.9M200 74c-19.3 0-34.9-15.6-34.9-34.9 0-19.3 15.6-34.9 34.9-34.9 19.3 0 34.9 15.6 34.9 34.9 0 19.3-15.6 34.9-34.9 34.9"/>
</svg>
<span>GraphQL</span>
</label>
<label class="ds-backend-option ds-backend-option--beta ${backendType === 'wordpress' ? 'ds-backend-option--selected' : ''}">
<input
type="radio"
name="backendType"
value="wordpress"
?checked=${backendType === 'wordpress'}
@change=${() => this.onBackendTypeChange('wordpress')}
/>
<svg viewBox="0 0 122.52 122.523" xmlns="http://www.w3.org/2000/svg">
<g fill="#21759b">
<path d="m8.708 61.26c0 20.802 12.089 38.779 29.619 47.298l-25.069-68.686c-2.916 6.536-4.55 13.769-4.55 21.388z"/>
<path d="m96.74 58.608c0-6.495-2.333-10.993-4.334-14.494-2.664-4.329-5.161-7.995-5.161-12.324 0-4.831 3.664-9.328 8.825-9.328.233 0 .454.029.681.042-9.35-8.566-21.807-13.796-35.489-13.796-18.36 0-34.513 9.42-43.91 23.688 1.233.037 2.395.063 3.382.063 5.497 0 14.006-.667 14.006-.667 2.833-.167 3.167 3.994.337 4.329 0 0-2.847.335-6.015.501l19.138 56.925 11.501-34.493-8.188-22.434c-2.83-.166-5.511-.501-5.511-.501-2.832-.166-2.5-4.496.332-4.329 0 0 8.679.667 13.843.667 5.496 0 14.006-.667 14.006-.667 2.835-.167 3.168 3.994.337 4.329 0 0-2.853.335-6.015.501l18.992 56.494 5.242-17.517c2.272-7.269 4.001-12.49 4.001-16.989z"/>
<path d="m62.184 65.857-15.768 45.819c4.708 1.384 9.687 2.141 14.846 2.141 6.12 0 11.989-1.058 17.452-2.979-.14-.225-.269-.464-.374-.724z"/>
<path d="m107.376 36.046c.226 1.674.354 3.471.354 5.404 0 5.333-.996 11.328-3.996 18.824l-16.053 46.413c15.624-9.111 26.133-26.038 26.133-45.426.001-9.137-2.333-17.729-6.438-25.215z"/>
<path d="m61.262 0c-33.779 0-61.262 27.481-61.262 61.26 0 33.783 27.483 61.263 61.262 61.263 33.778 0 61.265-27.48 61.265-61.263-.001-33.779-27.487-61.26-61.265-61.26zm0 119.715c-32.23 0-58.453-26.223-58.453-58.455 0-32.23 26.222-58.451 58.453-58.451 32.229 0 58.45 26.221 58.45 58.451 0 32.232-26.221 58.455-58.45 58.455z"/>
</g>
</svg>
<span>WordPress</span>
</label>
<label class="ds-backend-option ds-backend-option--beta ${backendType === 'gitlab' ? 'ds-backend-option--selected' : ''}">
<input
type="radio"
name="backendType"
value="gitlab"
?checked=${backendType === 'gitlab'}
@change=${() => this.onBackendTypeChange('gitlab')}
/>
<svg viewBox="0 0 380 380" xmlns="http://www.w3.org/2000/svg">
<path fill="#e24329" d="M190 362.6 256.3 158.8H123.7z"/>
<path fill="#fc6d26" d="M190 362.6 123.7 158.8H15.3z"/>
<path fill="#fca326" d="m15.3 158.8-13.5 41.6c-1.2 3.8.1 7.9 3.3 10.3L190 362.6z"/>
<path fill="#e24329" d="M15.3 158.8h108.4L79.8 24.1c-1.4-4.4-7.6-4.4-9 0z"/>
<path fill="#fc6d26" d="m190 362.6 66.3-203.8h108.4z"/>
<path fill="#fca326" d="m364.7 158.8 13.5 41.6c1.2 3.8-.1 7.9-3.3 10.3L190 362.6z"/>
<path fill="#e24329" d="M364.7 158.8H256.3l43.9-134.7c1.4-4.4 7.6-4.4 9 0z"/>
</svg>
<span>GitLab</span>
</label>
<label class="ds-backend-option ds-backend-option--beta ${backendType === 'strapi' ? 'ds-backend-option--selected' : ''}">
<input
type="radio"
name="backendType"
value="strapi"
?checked=${backendType === 'strapi'}
@change=${() => this.onBackendTypeChange('strapi')}
/>
<svg viewBox=".24262095 .26549587 243.32256626 243.58072911" xmlns="http://www.w3.org/2000/svg">
<g fill="#8e75ff" fill-rule="evenodd">
<path d="m161.893 165.833v-78.73a5.077 5.077 0 0 0 -5.077-5.076h-78.638v-81.267h159.815a5.077 5.077 0 0 1 5.078 5.077v159.996z"/>
<path d="m78.178.76v81.267h-75.054a2.539 2.539 0 0 1 -1.796-4.333zm83.715 240.206v-75.133h81.178l-76.844 76.927a2.539 2.539 0 0 1 -4.334-1.794zm-83.715-158.939h81.176a2.539 2.539 0 0 1 2.539 2.538v81.268h-78.638a5.077 5.077 0 0 1 -5.077-5.077z" opacity=".405"/>
</g>
</svg>
<span>Strapi</span>
</label>
<label class="ds-backend-option ds-backend-option--beta ${backendType === 'supabase' ? 'ds-backend-option--selected' : ''}">
<input
type="radio"
name="backendType"
value="supabase"
?checked=${backendType === 'supabase'}
@change=${() => this.onBackendTypeChange('supabase')}
/>
<svg viewBox="0 0 109 113" xmlns="http://www.w3.org/2000/svg">
<path fill="url(#supabase-a)" d="M63.7 110.3c-2.5 3.1-7.6 1.4-7.7-2.6l-1.5-66.2h50.2c9.1 0 14.1 10.5 8.4 17.6L63.7 110.3Z"/>
<path fill="url(#supabase-b)" fill-opacity=".2" d="M63.7 110.3c-2.5 3.1-7.6 1.4-7.7-2.6l-1.5-66.2h50.2c9.1 0 14.1 10.5 8.4 17.6L63.7 110.3Z"/>
<path fill="#3ECF8E" d="M45.3 2.7c2.5-3.1 7.6-1.4 7.7 2.6l.7 66.2H4.4c-9.1 0-14-10.5-8.3-17.6L45.3 2.7Z"/>
<defs>
<linearGradient id="supabase-a" x1="53.97" x2="94.12" y1="54.97" y2="71.95" gradientUnits="userSpaceOnUse">
<stop stop-color="#249361"/><stop offset="1" stop-color="#3ECF8E"/>
</linearGradient>
<linearGradient id="supabase-b" x1="36.16" x2="54.48" y1="30.58" y2="65.72" gradientUnits="userSpaceOnUse">
<stop/><stop offset="1" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>
<span>Supabase</span>
</label>
</fieldset>
</div>
<!-- Advanced: Manual type selection -->
<details class="ds-advanced-details">
<summary class="ds-advanced-summary">
Show / hide fields in the expressions
<span class="ds-types-count-inline">(${selectedCount}/${totalCount})</span>
</summary>
<div class="ds-advanced-content">
<p class="ds-help-text"><br>Uncheck types you don't want to see in your expressions.</p>
<div class="ds-types-header">
<input
type="text"
class="ds-types-search"
placeholder="Search types..."
.value=${this.typeSearchQuery}
@input=${(e) => {
this.typeSearchQuery = e.target.value;
this.requestUpdate();
}}
/>
<div class="ds-types-actions">
<button type="button" class="ds-btn-prim" @click=${() => this.selectAllTypes()}>Select All</button>
<button type="button" class="ds-btn-prim" @click=${() => this.deselectAllTypes()}>Deselect All</button>
<button type="button" class="ds-btn-prim" @click=${() => this.resetToDefaults()}>Reset to Defaults</button>
</div>
</div>
<div class="ds-types-list">
${filteredTypes.map(type => {
const isRequired = this.isRequiredType(type.name);
return (0, lit_1.html) `
<div class="ds-type-item" @click=${() => this.toggleType(type.name)}>
<input
type="checkbox"
.checked=${this.selectedTypes.has(type.name)}
?disabled=${isRequired}
@click=${(e) => e.stopPropagation()}
@change=${() => this.toggleType(type.name)}
/>
<label>${type.name}</label>
<span class="ds-type-kind ds-type-kind--${type.kind.toLowerCase()}">${type.kind}</span>
</div>
`;
})}
${filteredTypes.length === 0 ? (0, lit_1.html) `
<div style="padding: 20px; text-align: center; color: var(--gjs-light-color);">
No types match "${this.typeSearchQuery}"
</div>
` : ''}
</div>
<div class="ds-types-count">
${selectedCount} of ${totalCount} types selected
${this.typeSearchQuery ? ` (showing ${filteredTypes.length} matching "${this.typeSearchQuery}")` : ''}
</div>
</div>
</details>
<div class="ds-field">
<div class="ds-status-section">
${this.isLoading
? (0, lit_1.html) `<div class="ds-status-item ds-status-loading">${this.loadingIcon} Connecting...</div>`
: this.errorMessage
? (0, lit_1.html) `<div class="ds-status-item ds-status-error">Error: ${this.errorMessage}</div>`
: this.connected
? (0, lit_1.html) `<div class="ds-status-item ds-status-success">${this.connectedIcon} Connection successful</div>`
: ''}
</div>
</div>
<div class="ds-field ds-actions">
<div class="ds-actions-left">
<button
type="button"
class="ds-btn-prim"
@click=${() => this.goBackToConfig()}
?disabled=${this.isLoading}
>Back</button>
</div>
<div class="ds-actions-right">
<button
type="button"
class="ds-btn-prim"
@click=${() => this.dispatchEvent(new CustomEvent('cancel'))}
>Cancel</button>
<button
type="submit"
class="ds-btn-prim"
?disabled=${this.isLoading || this.selectedTypes.size === 0}
>${this.isLoading ? 'Connecting...' : 'Apply'}</button>
</div>
</div>
`;
}
render() {
if (!this.dataSource)
throw new Error('No data source provided');
return (0, lit_1.html) `
<form
?readonly=${this.dataSource.readonly !== false}
@submit=${(e) => {
e.preventDefault();
e.stopImmediatePropagation();
// Handle form submission based on current phase
if (this.creationPhase === 'config') {
if (this.isEdit) {
this.connectDataSource();
}
else {
this.fetchTypeNames();
}
}
else {
this.connectDataSource();
}
}}
>
${this.creationPhase === 'config' ? this.renderConfigPhase() : this.renderTypesPhase()}
</form>
`;
}
}
SettingsDataSource.styles = [
(0, lit_1.css) `
form {
display: flex;
flex-direction: column;
align-items: stretch;
}
.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-status-section {
margin: 10px 0;
}
.ds-status-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-radius: 4px;
font-size: 14px;
}
.ds-status-loading {
background-color: var(--gjs-main-dark-color);
color: var(--ds-highlight);
}
.ds-status-success {
background-color: var(--gjs-main-dark-color);
color: var(--ds-primary);
}
.ds-status-error {
background-color: var(--gjs-main-dark-color);
color: var(--ds-highlight);
}
.ds-actions {
display: flex;
gap: 10px;
justify-content: space-between;
align-items: center;
margin-top: 15px;
}
.ds-actions-left {
display: flex;
gap: 10px;
}
.ds-actions-right {
display: flex;
gap: 10px;
margin-left: auto;
}
.ds-no-resize {
flex: 0 0 auto;
}
button:disabled {
opacity: 0.6;
cursor: not-allowed;
}
/* Backend selector styles */
.ds-backend-selector {
margin-bottom: 15px;
}
.ds-backend-options {
display: flex;
gap: 10px;
border: none;
padding: 0;
margin: 0;
}
.ds-backend-option {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 12px 16px;
border: 2px solid var(--gjs-main-dark-color);
border-radius: 8px;
cursor: pointer;
transition: all 0.2s ease;
background: var(--gjs-main-dark-color);
min-width: 80px;
}
.ds-backend-option:hover {
border-color: var(--gjs-light-color);
}
.ds-backend-option--selected {
border-color: var(--ds-primary);
background: rgba(var(--ds-primary-rgb, 76, 175, 80), 0.1);
}
.ds-backend-option input[type="radio"] {
position: absolute;
opacity: 0;
pointer-events: none;
}
.ds-backend-option svg {
width: 40px;
height: 40px;
}
.ds-backend-option span {
font-size: 11px;
color: var(--gjs-light-color);
text-align: center;
}
.ds-backend-option--beta {
position: relative;
}
.ds-backend-option--beta::after {
content: 'beta';
position: absolute;
top: 4px;
right: 4px;
padding: 1px 4px;
padding-top: 3px;
font-size: smaller;
font-weight: bold;
text-transform: uppercase;
background: var(--ds-primary);
color: var(--gjs-secondary-color);
border-radius: 3px;
line-height: 1.2;
}
/* Inline backend selector (compact for config phase) */
.ds-backend-selector--inline {
display: flex;
gap: 8px;
margin-top: 8px;
}
.ds-backend-option--compact {
flex-direction: row;
padding: 8px 14px;
min-width: auto;
gap: 8px;
}
.ds-backend-option--compact svg {
width: 20px;
height: 20px;
}
.ds-backend-option--compact span {
font-size: 13px;
}
/* Advanced options accordion */
.ds-advanced-details {
margin: 15px 0;
border: 1px solid var(--gjs-main-dark-color);
border-radius: 6px;
background: var(--gjs-main-dark-color);
}
.ds-advanced-summary {
padding: 12px 15px;
cursor: pointer;
font-size: 13px;
color: var(--gjs-light-color);
user-select: none;
display: flex;
align-items: center;
gap: 8px;
}
.ds-advanced-summary:hover {
color: inherit;
}
.ds-advanced-summary::before {
content: '▶';
font-size: 10px;
transition: transform 0.2s;
}
.ds-advanced-details[open] .ds-advanced-summary::before {
transform: rotate(90deg);
}
.ds-advanced-content {
padding: 0 15px 15px;
border-top: 1px solid var(--gjs-main-light-color);
}
.ds-hint {
display: block;
margin-top: 5px;
font-size: 11px;
color: var(--gjs-light-color);
}
.ds-help-text {
margin: 0 0 10px;
font-size: 11px;
color: var(--gjs-light-color);
font-style: italic;
}
.ds-types-count-inline {
font-size: 12px;
color: var(--gjs-light-color);
margin-left: auto;
}
/* Type selection styles */
.ds-types-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
flex-wrap: wrap;
gap: 10px;
}
.ds-types-search {
flex: 1;
min-width: 150px;
}
.ds-types-actions {
display: flex;
gap: 5px;
}
.ds-types-actions button {
padding: 4px 8px;
font-size: 12px;
}
.ds-types-list {
max-height: 300px;
overflow-y: auto;
border: 1px solid var(--gjs-main-dark-color);
border-radius: 4px;
padding: 8px;
background-color: var(--gjs-main-dark-color);
}
.ds-type-item {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 8px;
border-radius: 3px;
cursor: pointer;
transition: background-color 0.15s;
}
.ds-type-item:hover {
background-color: var(--gjs-main-light-color);
}
.ds-type-item input[type="checkbox"] {
width: 16px;
height: 16px;
cursor: pointer;
}
.ds-type-item label {
cursor: pointer;
flex: 1;
font-family: monospace;
font-size: 13px;
}
.ds-type-kind {
font-size: 10px;
padding: 2px 5px;
border-radius: 3px;
background-color: var(--gjs-main-light-color);
color: var(--gjs-light-color);
text-transform: lowercase;
}
.ds-type-kind--scalar {
background-color: rgba(100, 200, 100, 0.2);
color: #4caf50;
}
.ds-type-kind--object {
background-color: rgba(100, 150, 255, 0.2);
color: #2196f3;
}
.ds-type-kind--interface {
background-color: rgba(200, 150, 255, 0.2);
color: #9c27b0;
}
.ds-type-kind--enum {
background-color: rgba(255, 200, 100, 0.2);
color: #ff9800;
}
.ds-type-kind--union {
background-color: rgba(255, 150, 150, 0.2);
color: #f44336;
}
.ds-type-kind--input_object {
background-color: rgba(150, 200, 200, 0.2);
color: #00bcd4;
}
.ds-types-count {
font-size: 12px;
color: var(--gjs-light-color);
margin-top: 8px;
}
`,
COMMON_STYLES,
];
__decorate([
(0, decorators_js_1.property)({ type: Object }),
__metadata("design:type", Object)
], SettingsDataSource.prototype, "dataSource", void 0);
__decorate([
(0, decorators_js_1.property)({ type: Boolean }),
__metadata("design:type", Boolean)
], SettingsDataSource.prototype, "isEdit", void 0);
__decorate([
(0, decorators_js_1.state)(),
__metadata("design:type", String)
], SettingsDataSource.prototype, "errorMessage", void 0);
__decorate([
(0, decorators_js_1.state)(),
__metadata("design:type", Boolean)
], SettingsDataSource.prototype, "connected", void 0);
__decorate([
(0, decorators_js_1.state)(),
__metadata("design:type", Boolean)
], SettingsDataSource.prototype, "isLoading", void 0);
__decorate([
(0, decorators_js_1.state)(),
__metadata("design:type", String)
], SettingsDataSource.prototype, "creationPhase", void 0);
__decorate([
(0, decorators_js_1.state)(),
__metadata("design:type", Array)
], SettingsDataSource.prototype, "availableTypes", void 0);
__decorate([
(0, decorators_js_1.state)(),
__metadata("design:type", Set)
], SettingsDataSource.prototype, "selectedTypes", void 0);
__decorate([
(0, decorators_js_1.state)(),
__metadata("design:type", String)
], SettingsDataSource.prototype, "typeSearchQuery", void 0);
__decorate([
(0, decorators_js_1.state)(),
__metadata("design:type", String)
], SettingsDataSource.prototype, "queryTypeName", void 0);
if (!customElements.get('ds-settings__data-source')) {
customElements.define('ds-settings__data-source', SettingsDataSource);
}
/**
* Compact card component for displaying data source summary
*/
class DataSourceCard extends lit_1.LitElement {
// private connectedIcon = html`<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"/></svg>`
// private errorIcon = html`<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z"/></svg>`
constructor() {
super();
this.isTestingConnection = false;
this.loadingIcon = (0, lit_1.html) `<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><circle cx="12" cy="12" r="3"><animate attributeName="r" values="3;8;3" dur="1s" repeatCount="indefinite"/><animate attributeName="opacity" values="1;0.3;1" dur="1s" repeatCount="indefinite"/></circle></svg>`;
this.graphqlLogo = (0, lit_1.html) `<svg class="ds-card__logo" viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg"><path fill="#E535AB" d="M57.468 302.66l-14.376-8.3 160.15-277.38 14.376 8.3z"/><path fill="#E535AB" d="M39.8 272.2h320.3v16.6H39.8z"/><path fill="#E535AB" d="M206.348 374.026l-160.21-92.5 8.3-14.376 160.21 92.5zM345.522 132.947l-160.21-92.5 8.3-14.376 160.21 92.5z"/><path fill="#E535AB" d="M54.482 132.883l-8.3-14.375 160.21-92.5 8.3 14.376z"/><path fill="#E535AB" d="M342.568 302.663l-160.15-277.38 14.376-8.3 160.15 277.38zM52.5 107.5h16.6v185H52.5zM330.9 107.5h16.6v185h-16.6z"/><path fill="#E535AB" d="M203.522 367l-7.25-12.558 139.34-80.45 7.25 12.557z"/><path fill="#E535AB" d="M369.5 297.9c-9.6 16.7-31 22.4-47.7 12.8-16.7-9.6-22.4-31-12.8-47.7 9.6-16.7 31-22.4 47.7-12.8 16.8 9.7 22.5 31 12.8 47.7M90.9 137c-9.6 16.7-31 22.4-47.7 12.8-16.7-9.6-22.4-31-12.8-47.7 9.6-16.7 31-22.4 47.7-12.8 16.7 9.7 22.4 31 12.8 47.7M30.5 297.9c-9.6-16.7-3.9-38 12.8-47.7 16.7-9.6 38-3.9 47.7 12.8 9.6 16.7 3.9 38-12.8 47.7-16.8 9.6-38.1 3.9-47.7-12.8M309.1 137c-9.6-16.7-3.9-38 12.8-47.7 16.7-9.6 38-3.9 47.7 12.8 9.6 16.7 3.9 38-12.8 47.7-16.7 9.6-38.1 3.9-47.7-12.8M200 395.8c-19.3 0-34.9-15.6-34.9-34.9 0-19.3 15.6-34.9 34.9-34.9 19.3 0 34.9 15.6 34.9 34.9 0 19.2-15.6 34.9-34.9 34.9M200 74c-19.3 0-34.9-15.6-34.9-34.9 0-19.3 15.6-34.9 34.9-34.9 19.3 0 34.9 15.6 34.9 34.9 0 19.3-15.6 34.9-34.9 34.9"/></svg>`;
this.wordpressLogo = (0, lit_1.html) `<svg class="ds-card__logo" viewBox="0 0 122.52 122.523" xmlns="http://www.w3.org/2000/svg"><g fill="#21759b"><path d="m8.708 61.26c0 20.802 12.089 38.779 29.619 47.298l-25.069-68.686c-2.916 6.5