mirror of
https://github.com/Kong/insomnia.git
synced 2026-04-18 13:18:59 -04:00
feat: integrate rjsf (#9120)
This commit is contained in:
176
package-lock.json
generated
176
package-lock.json
generated
@@ -7687,6 +7687,73 @@
|
||||
"react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@rjsf/core": {
|
||||
"version": "6.0.0-beta.15",
|
||||
"resolved": "https://registry.npmjs.org/@rjsf/core/-/core-6.0.0-beta.15.tgz",
|
||||
"integrity": "sha512-VF6o/tetnYxgdv19qDgTdElkLQlPHH/2yn0Tyz7J8lUVucmYuPEctV6qo88NJUAtvts9EFLr7SYKaMQpfNJonQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"markdown-to-jsx": "^7.7.13",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@rjsf/utils": "^6.0.0-beta.15",
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@rjsf/utils": {
|
||||
"version": "6.0.0-beta.15",
|
||||
"resolved": "https://registry.npmjs.org/@rjsf/utils/-/utils-6.0.0-beta.15.tgz",
|
||||
"integrity": "sha512-AWF0/OVRpdkxNrp1JIrTL+ryzGGffm/IXSICq+c0Ju3HIdcV0kvRDrlQtmdgbIbjdcWogS1Gg97cFhk/DzgjhQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"fast-uri": "^3.0.6",
|
||||
"json-schema-merge-allof": "^0.8.1",
|
||||
"jsonpointer": "^5.0.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"react-is": "^18.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@rjsf/utils/node_modules/react-is": {
|
||||
"version": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@rjsf/validator-ajv8": {
|
||||
"version": "6.0.0-beta.15",
|
||||
"resolved": "https://registry.npmjs.org/@rjsf/validator-ajv8/-/validator-ajv8-6.0.0-beta.15.tgz",
|
||||
"integrity": "sha512-M40hmygBfR8NlaR3JEiBJIqs03Qli7F5DRS7ypFDBDsZLcSm1Kd6BWbjC3EM2IutYyTgxg6lvi1lQw95PqqcYw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"ajv": "^8.17.1",
|
||||
"ajv-formats": "^2.1.1",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@rjsf/utils": "^6.0.0-beta.15"
|
||||
}
|
||||
},
|
||||
"node_modules/@rolldown/pluginutils": {
|
||||
"version": "1.0.0-beta.32",
|
||||
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.32.tgz",
|
||||
@@ -13260,6 +13327,29 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/compute-gcd": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/compute-gcd/-/compute-gcd-1.2.1.tgz",
|
||||
"integrity": "sha512-TwMbxBNz0l71+8Sc4czv13h4kEqnchV9igQZBi6QUaz09dnz13juGnnaWWJTRsP3brxOoxeB4SA2WELLw1hCtg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"validate.io-array": "^1.0.3",
|
||||
"validate.io-function": "^1.0.2",
|
||||
"validate.io-integer-array": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/compute-lcm": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/compute-lcm/-/compute-lcm-1.1.2.tgz",
|
||||
"integrity": "sha512-OFNPdQAXnQhDSKioX8/XYT6sdUlXwpeMjfd6ApxMJfyZ4GxmLR1xvMERctlYhlHwIiz6CSpBc2+qYKjHGZw4TQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"compute-gcd": "^1.2.1",
|
||||
"validate.io-array": "^1.0.3",
|
||||
"validate.io-function": "^1.0.2",
|
||||
"validate.io-integer-array": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -19185,6 +19275,31 @@
|
||||
"integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==",
|
||||
"license": "(AFL-2.1 OR BSD-3-Clause)"
|
||||
},
|
||||
"node_modules/json-schema-compare": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-compare/-/json-schema-compare-0.2.2.tgz",
|
||||
"integrity": "sha512-c4WYmDKyJXhs7WWvAWm3uIYnfyWFoIp+JEoX34rctVvEkMYCPGhXtvmFFXiffBbxfZsvQ0RNnV5H7GvDF5HCqQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.4"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-merge-allof": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-merge-allof/-/json-schema-merge-allof-0.8.1.tgz",
|
||||
"integrity": "sha512-CTUKmIlPJbsWfzRRnOXz+0MjIqvnleIXwFTzz+t9T86HnYX/Rozria6ZVGLktAU9e+NygNljveP+yxqtQp/Q4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"compute-lcm": "^1.1.2",
|
||||
"json-schema-compare": "^0.2.2",
|
||||
"lodash": "^4.17.20"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/json-schema-ref-parser": {
|
||||
"version": "9.0.9",
|
||||
"resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-9.0.9.tgz",
|
||||
@@ -19916,6 +20031,13 @@
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash-es": {
|
||||
"version": "4.17.21",
|
||||
"resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
|
||||
"integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
@@ -20203,6 +20325,19 @@
|
||||
"resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz",
|
||||
"integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g=="
|
||||
},
|
||||
"node_modules/markdown-to-jsx": {
|
||||
"version": "7.7.13",
|
||||
"resolved": "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.7.13.tgz",
|
||||
"integrity": "sha512-DiueEq2bttFcSxUs85GJcQVrOr0+VVsPfj9AEUPqmExJ3f8P/iQNvZHltV4tm1XVhu1kl0vWBZWT3l99izRMaA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">= 0.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-5.1.2.tgz",
|
||||
@@ -26890,6 +27025,44 @@
|
||||
"builtins": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/validate.io-array": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/validate.io-array/-/validate.io-array-1.0.6.tgz",
|
||||
"integrity": "sha512-DeOy7CnPEziggrOO5CZhVKJw6S3Yi7e9e65R1Nl/RTN1vTQKnzjfvks0/8kQ40FP/dsjRAOd4hxmJ7uLa6vxkg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/validate.io-function": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/validate.io-function/-/validate.io-function-1.0.2.tgz",
|
||||
"integrity": "sha512-LlFybRJEriSuBnUhQyG5bwglhh50EpTL2ul23MPIuR1odjO7XaMLFV8vHGwp7AZciFxtYOeiSCT5st+XSPONiQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/validate.io-integer": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/validate.io-integer/-/validate.io-integer-1.0.5.tgz",
|
||||
"integrity": "sha512-22izsYSLojN/P6bppBqhgUDjCkr5RY2jd+N2a3DCAUey8ydvrZ/OkGvFPR7qfOpwR2LC5p4Ngzxz36g5Vgr/hQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"validate.io-number": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/validate.io-integer-array": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/validate.io-integer-array/-/validate.io-integer-array-1.0.0.tgz",
|
||||
"integrity": "sha512-mTrMk/1ytQHtCY0oNO3dztafHYyGU88KL+jRxWuzfOmQb+4qqnWmI+gykvGp8usKZOM0H7keJHEbRaFiYA0VrA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"validate.io-array": "^1.0.3",
|
||||
"validate.io-integer": "^1.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/validate.io-number": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/validate.io-number/-/validate.io-number-1.0.3.tgz",
|
||||
"integrity": "sha512-kRAyotcbNaSYoDnXvb4MHg/0a1egJdLwS6oJ38TJY7aw9n93Fl/3blIXdyYvPOp55CNxywooG/3BcrwNrBpcSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/validator": {
|
||||
"version": "13.15.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-13.15.0.tgz",
|
||||
@@ -28390,6 +28563,9 @@
|
||||
"@react-router/fs-routes": "^7.7.0",
|
||||
"@react-router/node": "^7.7.0",
|
||||
"@react-router/serve": "^7.7.0",
|
||||
"@rjsf/core": "^6.0.0-beta.15",
|
||||
"@rjsf/utils": "^6.0.0-beta.15",
|
||||
"@rjsf/validator-ajv8": "^6.0.0-beta.15",
|
||||
"@sentry/electron": "^6.5.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/react-virtual": "3.13.6",
|
||||
|
||||
@@ -115,6 +115,9 @@
|
||||
"@react-router/fs-routes": "^7.7.0",
|
||||
"@react-router/node": "^7.7.0",
|
||||
"@react-router/serve": "^7.7.0",
|
||||
"@rjsf/core": "^6.0.0-beta.15",
|
||||
"@rjsf/utils": "^6.0.0-beta.15",
|
||||
"@rjsf/validator-ajv8": "^6.0.0-beta.15",
|
||||
"@sentry/electron": "^6.5.0",
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@tanstack/react-virtual": "3.13.6",
|
||||
|
||||
@@ -46,7 +46,7 @@ type ToolItem = Tool & { type: 'tools' } & CommonItemProps;
|
||||
type ResourceItem = Resource & { type: 'resources' } & CommonItemProps;
|
||||
type ResourceTemplateItem = ResourceTemplate & { type: 'resources' } & CommonItemProps;
|
||||
type PromptItem = Prompt & { type: 'prompts' } & CommonItemProps;
|
||||
type PrimitiveSubItemTypes = ToolItem | ResourceItem | ResourceTemplateItem | PromptItem;
|
||||
export type PrimitiveSubItemTypes = ToolItem | ResourceItem | ResourceTemplateItem | PromptItem;
|
||||
interface PrimitiveTypeItem extends CommonItemProps {
|
||||
type: McpServerPrimitiveTypes;
|
||||
name: string;
|
||||
@@ -88,7 +88,7 @@ const McpPage = () => {
|
||||
const [mcpServerData, setMcpServerData] = useState<McpServerData | null>(null);
|
||||
const [collapsedPrimitives, setCollapsedPrimitives] = useState<McpServerPrimitiveTypes[]>([]);
|
||||
const [selectedPrimitiveItem, setSelectedPrimitiveItem] = useState<PrimitiveSubItemTypes | null>(null);
|
||||
|
||||
console.log('selectedPrimitiveItem', selectedPrimitiveItem);
|
||||
const getPrimitiveCollection = () => {
|
||||
const collection: (PrimitiveTypeItem | PrimitiveSubItemTypes)[] = [];
|
||||
if (mcpServerData) {
|
||||
@@ -437,7 +437,11 @@ const McpPage = () => {
|
||||
<OrganizationTabList currentPage="mcp" />
|
||||
<PanelGroup autoSaveId="insomnia-panels" id="insomnia-panels" direction={direction}>
|
||||
<Panel id="mcp-request-pane" order={1} minSize={10} className="pane-one theme--pane">
|
||||
<McpRequestPane environment={activeEnvironment} readyState={readyState} />
|
||||
<McpRequestPane
|
||||
selectedPrimitiveItem={selectedPrimitiveItem}
|
||||
environment={activeEnvironment}
|
||||
readyState={readyState}
|
||||
/>
|
||||
</Panel>
|
||||
<Panel id="mcp-response-pane" order={2} minSize={10} className="pane-two theme--pane">
|
||||
<ErrorBoundary showAlert>
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import React, { type FC } from 'react';
|
||||
import type { IChangeEvent } from '@rjsf/core';
|
||||
import type { RJSFSchema } from '@rjsf/utils';
|
||||
import validator from '@rjsf/validator-ajv8';
|
||||
import React, { type FC, useEffect, useRef, useState } from 'react';
|
||||
import { Button, Heading, Tab, TabList, TabPanel, Tabs } from 'react-aria-components';
|
||||
import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels';
|
||||
|
||||
import type { PrimitiveSubItemTypes } from '~/routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp';
|
||||
import { InsomniaRjsfForm } from '~/ui/components/rjsf';
|
||||
|
||||
import { type AuthTypes } from '../../../common/constants';
|
||||
import type { Environment } from '../../../models/environment';
|
||||
import { getAuthObjectOrNull } from '../../../network/authentication';
|
||||
import { useMcpRequestLoaderData } from '../../../routes/organization.$organizationId.project.$projectId.workspace.$workspaceId.mcp.request.$requestId';
|
||||
import { useRequestPatcher } from '../../hooks/use-request';
|
||||
import { CodeEditor } from '../.client/codemirror/code-editor';
|
||||
import { CodeEditor, type CodeEditorHandle } from '../.client/codemirror/code-editor';
|
||||
import { AuthWrapper } from '../editors/auth/auth-wrapper';
|
||||
import { readOnlyWebsocketPairs, RequestHeadersEditor } from '../editors/request-headers-editor';
|
||||
import { Pane } from '../panes/pane';
|
||||
@@ -35,10 +41,13 @@ const PaneReadOnlyBanner = () => {
|
||||
interface Props {
|
||||
environment: Environment | null;
|
||||
readyState: boolean;
|
||||
selectedPrimitiveItem?: PrimitiveSubItemTypes | null;
|
||||
}
|
||||
|
||||
export const McpRequestPane: FC<Props> = ({ environment, readyState }) => {
|
||||
export const McpRequestPane: FC<Props> = ({ environment, readyState, selectedPrimitiveItem }) => {
|
||||
const { activeRequest } = useMcpRequestLoaderData()!;
|
||||
const [formData, setFormData] = useState({});
|
||||
const paramEditorRef = useRef<CodeEditorHandle>(null);
|
||||
const requestId = activeRequest._id;
|
||||
|
||||
const headersCount = activeRequest.headers.filter(h => !h.disabled).length + readOnlyWebsocketPairs.length;
|
||||
@@ -48,6 +57,25 @@ export const McpRequestPane: FC<Props> = ({ environment, readyState }) => {
|
||||
const uniqueKey = `${environment?.modified}::${requestId}`;
|
||||
const requestAuth = getAuthObjectOrNull(activeRequest.authentication);
|
||||
const isNoneOrInherited = requestAuth?.type === 'none' || requestAuth === null;
|
||||
const toolsSchema = selectedPrimitiveItem?.type === 'tools' ? selectedPrimitiveItem.inputSchema : undefined;
|
||||
|
||||
const handleRjsfFormChange = (e: IChangeEvent) => {
|
||||
setFormData(e.formData);
|
||||
paramEditorRef.current?.setValue(JSON.stringify(e.formData, null, 2));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setFormData({});
|
||||
paramEditorRef.current?.setValue('');
|
||||
}, [selectedPrimitiveItem]);
|
||||
|
||||
const handleSend = () => {
|
||||
window.main.mcp.primitive.callTool({
|
||||
name: selectedPrimitiveItem?.name || '',
|
||||
parameters: formData,
|
||||
requestId: requestId,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Pane type="request">
|
||||
@@ -110,14 +138,22 @@ export const McpRequestPane: FC<Props> = ({ environment, readyState }) => {
|
||||
<Heading className="text-xs font-bold uppercase text-[--hl]">Parameter Builder</Heading>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
isDisabled={readyState}
|
||||
isDisabled={!readyState}
|
||||
onClick={handleSend}
|
||||
className="asma-pressed:bg-[--hl-sm] flex h-full w-[14ch] flex-shrink-0 items-center justify-start gap-2 rounded-sm px-2 py-1 text-sm text-[--color-font] ring-1 ring-transparent transition-colors hover:bg-[--hl-xs] focus:bg-[--hl-sm] focus:ring-inset focus:ring-[--hl-md] aria-selected:bg-[--hl-xs] aria-selected:hover:bg-[--hl-sm] aria-selected:focus:bg-[--hl-sm]"
|
||||
>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
TODO Parameter Builder UI
|
||||
{toolsSchema && (
|
||||
<InsomniaRjsfForm
|
||||
formData={formData}
|
||||
onChange={handleRjsfFormChange}
|
||||
schema={toolsSchema as RJSFSchema}
|
||||
validator={validator}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Panel>
|
||||
<PanelResizeHandle className="h-[1px] w-full bg-[--hl-md]" />
|
||||
@@ -126,6 +162,7 @@ export const McpRequestPane: FC<Props> = ({ environment, readyState }) => {
|
||||
<Heading className="p-4 text-xs font-bold uppercase text-[--hl]">Parameter Overview</Heading>
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<CodeEditor
|
||||
ref={paramEditorRef}
|
||||
id="mcp-parameter-overview-editor"
|
||||
showPrettifyButton
|
||||
dynamicHeight
|
||||
|
||||
5
packages/insomnia/src/ui/components/rjsf/index.tsx
Normal file
5
packages/insomnia/src/ui/components/rjsf/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { withTheme } from '@rjsf/core';
|
||||
|
||||
import theme from './theme';
|
||||
|
||||
export const InsomniaRjsfForm = withTheme(theme);
|
||||
22
packages/insomnia/src/ui/components/rjsf/theme.tsx
Normal file
22
packages/insomnia/src/ui/components/rjsf/theme.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { ThemeProps } from '@rjsf/core';
|
||||
import type { RegistryWidgetsType, WidgetProps } from '@rjsf/utils';
|
||||
|
||||
const CustomTextWidget = (props: WidgetProps) => {
|
||||
return (
|
||||
<input
|
||||
className="w-full rounded-sm border border-solid border-[--hl-sm] bg-[--color-bg] py-1 pl-2 pr-7 text-[--color-font] transition-colors placeholder:italic focus:outline-none focus:ring-1 focus:ring-[--hl-md]"
|
||||
onChange={e => props.onChange(e.target.value)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const themeWidgets: RegistryWidgetsType = {
|
||||
TextWidget: CustomTextWidget,
|
||||
};
|
||||
const themeTemplates = {};
|
||||
|
||||
const ThemeObject: ThemeProps = {
|
||||
widgets: themeWidgets,
|
||||
templates: themeTemplates,
|
||||
};
|
||||
export default ThemeObject;
|
||||
Reference in New Issue
Block a user