├── .eslintrc.js ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .nvmrc ├── .prettierrc.js ├── .travis.yml ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── LICENSE ├── README.md ├── app ├── .env ├── .env.development ├── .gitignore ├── .rescriptsrc.js ├── LICENSE ├── README.md ├── cypress.json ├── cypress │ ├── .eslintrc.js │ ├── fixtures │ │ ├── invalidSchema.json │ │ ├── simpleSchema.json │ │ └── simpleUiSchema.json │ ├── integration │ │ ├── dnd.spec.js │ │ ├── editSchemas.spec.js │ │ ├── editor.spec.js │ │ └── smoketest.spec.js │ ├── plugins │ │ └── index.js │ └── support │ │ ├── commands.js │ │ └── index.js ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ ├── jsonforms-angular-webcomponent │ │ ├── main-es2015.js │ │ ├── polyfills-es2015.js │ │ ├── runtime-es2015.js │ │ └── styles.css │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.test.tsx │ ├── App.tsx │ ├── components │ │ ├── AngularMaterialPreview.tsx │ │ └── Footer.tsx │ ├── core │ │ └── schemaService.ts │ ├── index.tsx │ ├── react-app-env.d.ts │ └── setupTests.ts └── tsconfig.json ├── jsonforms-editor ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── package.json ├── rollup.config.ts ├── src │ ├── JsonFormsEditor.css │ ├── JsonFormsEditor.test.tsx │ ├── JsonFormsEditor.tsx │ ├── core │ │ ├── api │ │ │ ├── categorizationService.ts │ │ │ ├── index.ts │ │ │ ├── paletteService.tsx │ │ │ └── schemaService.ts │ │ ├── components │ │ │ ├── ErrorDialog.tsx │ │ │ ├── ExportDialog.tsx │ │ │ ├── Footer.tsx │ │ │ ├── Formatted.tsx │ │ │ ├── Header.tsx │ │ │ ├── Layout.tsx │ │ │ ├── OkCancelDialog.tsx │ │ │ ├── ShowMoreLess.tsx │ │ │ ├── TabContent.tsx │ │ │ └── index.ts │ │ ├── context │ │ │ ├── context.ts │ │ │ └── index.ts │ │ ├── dnd │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── icons │ │ │ ├── icons.tsx │ │ │ └── index.ts │ │ ├── jsonschema │ │ │ ├── index.ts │ │ │ └── specification │ │ │ │ ├── rule.json │ │ │ │ └── schema.json │ │ ├── model │ │ │ ├── actions.ts │ │ │ ├── index.ts │ │ │ ├── reducer.test.ts │ │ │ ├── reducer.ts │ │ │ ├── schema.test.ts │ │ │ ├── schema.ts │ │ │ ├── uischema.test.ts │ │ │ └── uischema.ts │ │ ├── renderers │ │ │ ├── DroppableArrayControl.tsx │ │ │ ├── DroppableCategorizationLayout.tsx │ │ │ ├── DroppableCategoryLayout.tsx │ │ │ ├── DroppableElement.tsx │ │ │ ├── DroppableGroupLayout.tsx │ │ │ └── DroppableLayout.tsx │ │ ├── selection │ │ │ ├── index.ts │ │ │ └── selection.ts │ │ └── util │ │ │ ├── clipboard.ts │ │ │ ├── clone.ts │ │ │ ├── generators │ │ │ └── uiSchema.ts │ │ │ ├── hooks.ts │ │ │ ├── index.ts │ │ │ ├── schemasUtil.test.ts │ │ │ ├── schemasUtil.ts │ │ │ └── tree.ts │ ├── editor │ │ ├── components │ │ │ ├── Editor.tsx │ │ │ ├── EditorElement.tsx │ │ │ ├── EditorPanel.tsx │ │ │ ├── EditorPreview.tsx │ │ │ ├── EmptyEditor.tsx │ │ │ └── preview │ │ │ │ ├── ReactMaterialPreview.tsx │ │ │ │ ├── index.ts │ │ │ │ └── options.ts │ │ └── index.ts │ ├── env.ts │ ├── index.ts │ ├── palette-panel │ │ ├── components │ │ │ ├── JsonSchemaPanel.tsx │ │ │ ├── PalletePanel.tsx │ │ │ ├── SchemaJson.tsx │ │ │ ├── SchemaTree.tsx │ │ │ ├── Tree.tsx │ │ │ ├── UIElementsTree.tsx │ │ │ └── UISchemaPanel.tsx │ │ └── index.ts │ ├── properties │ │ ├── components │ │ │ ├── Properties.tsx │ │ │ └── PropertiesPanel.tsx │ │ ├── index.ts │ │ ├── propertiesService.ts │ │ ├── renderers │ │ │ └── RuleEditorRenderer.tsx │ │ ├── schemaDecorators.ts │ │ └── schemaProviders.ts │ ├── setupTests.ts │ └── text-editor │ │ ├── components │ │ └── JsonEditorDialog.tsx │ │ ├── index.ts │ │ └── jsonSchemaValidation.ts ├── tsconfig.build.json └── tsconfig.json ├── lerna.json ├── package-lock.json ├── package.json └── testapp ├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt └── src ├── App.js ├── App.test.js ├── index.js └── setupTests.js /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | module.exports = { 9 | // prettier must always be the last entry to ensure all rules are compatible 10 | extends: ['react-app', 'prettier'], 11 | plugins: ['header', 'simple-import-sort', 'prettier'], 12 | rules: { 13 | 'prettier/prettier': ['error', { endOfLine: 'auto' }], 14 | // use sorting of simple-import-sort and disable others 15 | 'sort-imports': 'off', 16 | 'import/order': 'off', 17 | 'simple-import-sort/sort': 'error', 18 | // header 19 | 'header/header': [ 20 | 2, 21 | 'block', 22 | [ 23 | '*', 24 | ' * ---------------------------------------------------------------------', 25 | ' * Copyright (c) 2021 EclipseSource Munich', 26 | ' * Licensed under MIT', 27 | ' * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE', 28 | ' * ---------------------------------------------------------------------', 29 | ' ', 30 | ], 31 | ], 32 | }, 33 | }; 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | --- 5 | 6 | **Description** 7 | A clear and concise description of what the issue is. 8 | 9 | **To reproduce** 10 | Steps to reproduce the behavior: 11 | 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Screenshots** 21 | If applicable, add screenshots to help explain your problem. 22 | 23 | **Browser** 24 | 25 | - Browser [e.g. chrome, safari] 26 | - Version [e.g. 22] 27 | 28 | **Additional context** 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | **Motivation** 7 | A clear and concise description of what the motivation is. Is your feature request related to a problem? Ex. I'm always frustrated when [...] 8 | 9 | **Solution** 10 | A clear and concise description of what you want to happen. 11 | 12 | **Alternatives** 13 | A clear and concise description of any alternative solutions or features you've considered. 14 | 15 | **Additional context** 16 | Add any other context or screenshots about the feature request here. 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | .yalc 4 | yalc.lock 5 | build/ 6 | .eslintcache 7 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 12 2 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | jsxSingleQuote: true, 3 | singleQuote: true, 4 | } 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '12' 5 | 6 | install: 7 | - npm ci 8 | 9 | script: 10 | - npm run init 11 | - npm run build 12 | - npm test 13 | - npm run cypress:ci 14 | - npm run lint 15 | - npx lerna run ci:test 16 | - npx lerna run ci:build 17 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | // See http://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations. 3 | // Extension identifier format: ${publisher}.${name}. Example: vscode.csharp 4 | 5 | // List of extensions which should be recommended for users of this workspace. 6 | "recommendations": [ 7 | "msjsdiag.debugger-for-chrome", 8 | "dbaeumer.vscode-eslint", 9 | "davidanson.vscode-markdownlint", 10 | "esbenp.prettier-vscode" 11 | ], 12 | // List of extensions recommended by VS Code that should not be recommended for users of this workspace. 13 | "unwantedRecommendations": [] 14 | } 15 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Chrome Debug", 9 | "type": "chrome", 10 | "request": "launch", 11 | "url": "http://localhost:3000", 12 | "webRoot": "${workspaceFolder}/app/src", 13 | "sourceMapPathOverrides": { 14 | "webpack:///src/*": "${webRoot}/*" 15 | } 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | // eslint extension options 3 | "eslint.enable": true, 4 | "eslint.validate": [ 5 | "javascript", 6 | "javascriptreact", 7 | "typescript", 8 | "typescriptreact" 9 | ], 10 | 11 | // apply eslint fixes on save 12 | "editor.codeActionsOnSave": { 13 | "source.fixAll.eslint": true 14 | }, 15 | 16 | // prettier extension setting 17 | "editor.formatOnSave": true, 18 | "[javascript]": { 19 | "editor.defaultFormatter": "esbenp.prettier-vscode" 20 | }, 21 | "[javascriptreact]": { 22 | "editor.defaultFormatter": "esbenp.prettier-vscode" 23 | }, 24 | "[typescript]": { 25 | "editor.defaultFormatter": "esbenp.prettier-vscode" 26 | }, 27 | "[typescriptreact]": { 28 | "editor.defaultFormatter": "esbenp.prettier-vscode" 29 | }, 30 | "prettier.configPath": ".prettierrc.js", 31 | "files.insertFinalNewline": true 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2021 EclipseSource Munich 4 | https://github.com/eclipsesource/jsonforms-editor 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsonforms-editor 2 | 3 | Editor for JSON Schema and JSON Forms Ui Schema 4 | 5 | [![Build Status](https://travis-ci.com/eclipsesource/jsonforms-editor.svg?branch=master)](https://travis-ci.com/eclipsesource/jsonforms-editor) [![Netlify Status](https://api.netlify.com/api/v1/badges/2c2a42d3-77fb-4cd8-aca1-4cfa6c9a4a03/deploy-status)](https://app.netlify.com/sites/jsonforms-editor/deploys) 6 | 7 | This is a monorepo consisting of the `@jsonforms/editor` library component and the published JSON Forms editor app. 8 | 9 | ## Live Demo 10 | 11 | You can try a [live demo of the editor](https://jsonforms-editor.netlify.app/) on Netlify. 12 | 13 | ## Setup 14 | 15 | - `npm ci` 16 | - `npm run init` 17 | 18 | ## Build 19 | 20 | - `npm run build` 21 | 22 | The `@jsonforms/editor` library component will be located in `jsonforms-editor/dist`. 23 | The JSON Forms editor app will be located in `app/build`. 24 | 25 | ## Develop 26 | 27 | ### Recommended setup 28 | 29 | - Node >= 12 30 | - Visual Studio Code 31 | - Install recommended extensions 32 | 33 | Linting, formatting and import sorting should work automatically. 34 | 35 | ### Scripts 36 | 37 | - Build and watch jsonforms-editor library with `npm run watch` 38 | - Start the app with `npm start` 39 | - Run unit tests with `npm run test` 40 | - Run UI tests with `npm run cypress:open` 41 | 42 | ### Debugging in VS Code 43 | 44 | Start the app by running `npm start` and start debugging in VS Code by pressing F5 or by clicking the green debug icon (launch config `Chrome Debug`). 45 | -------------------------------------------------------------------------------- /app/.env: -------------------------------------------------------------------------------- 1 | EXTEND_ESLINT=true 2 | -------------------------------------------------------------------------------- /app/.env.development: -------------------------------------------------------------------------------- 1 | REACT_APP_DEBUG=true 2 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # cypress 15 | /cypress/videos/ 16 | /cypress/screenshots/ 17 | 18 | # misc 19 | .DS_Store 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | -------------------------------------------------------------------------------- /app/.rescriptsrc.js: -------------------------------------------------------------------------------- 1 | const { appendWebpackPlugin } = require('@rescripts/utilities'); 2 | 3 | const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); 4 | 5 | const addMonacoPlugin = () => (config) => 6 | appendWebpackPlugin( 7 | new MonacoWebpackPlugin({ 8 | // available options are documented at https://github.com/Microsoft/monaco-editor-webpack-plugin#options 9 | languages: ['json'], 10 | }), 11 | config 12 | ); 13 | 14 | module.exports = [addMonacoPlugin()]; 15 | -------------------------------------------------------------------------------- /app/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2021 EclipseSource Munich 4 | https://github.com/eclipsesource/jsonforms-editor 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | # JSON Forms Editor app 2 | 3 | This is the official JSON Forms Editor app. 4 | It is based on the `@jsonforms/editor` component. 5 | 6 | ## Setup 7 | 8 | Setup is handled as part of the monorepo management. 9 | See the [README](../README.md). 10 | 11 | ## Build 12 | 13 | - `npm run build` 14 | 15 | The JSON Forms editor app will be located in the `build` directory. 16 | Note that the build uses the built version of the `@jsonforms/editor` component. 17 | 18 | ## Develop 19 | 20 | ### CRA 21 | 22 | The app is based on `create-react-app`. 23 | 24 | We use `rescripts` to customize non-configurable CRA features: 25 | 26 | - We add the `MonacoWebpackPlugin` to the build 27 | - We also add the `@jsonforms/editor` sources to the build. 28 | 29 | By consuming the sources we don't exactly behave like a regular consumer of the library. 30 | See the [testapp](../testapp) for smoke tests regarding this use case. 31 | 32 | ### Scripts 33 | 34 | - Start the app with `npm start` 35 | - Run unit tests with `npm run test` 36 | - Run UI tests with `npm run cypress:open` 37 | -------------------------------------------------------------------------------- /app/cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000", 3 | "viewportHeight": 800, 4 | "viewportWidth": 1280 5 | } 6 | -------------------------------------------------------------------------------- /app/cypress/.eslintrc.js: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | module.exports = { 9 | extends: ['plugin:cypress/recommended'], 10 | }; 11 | -------------------------------------------------------------------------------- /app/cypress/fixtures/invalidSchema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": "invalidProperty" 4 | } 5 | -------------------------------------------------------------------------------- /app/cypress/fixtures/simpleSchema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "object", 3 | "properties": { "name": { "type": "string" } } 4 | } 5 | -------------------------------------------------------------------------------- /app/cypress/fixtures/simpleUiSchema.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "VerticalLayout", 3 | "elements": [{ "type": "Control", "scope": "#/properties/name" }] 4 | } 5 | -------------------------------------------------------------------------------- /app/cypress/integration/dnd.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | 9 | describe('Layout Dnd Tests on Example model', () => { 10 | beforeEach(() => { 11 | cy.visit('/'); 12 | }); 13 | 14 | it('Create Horizontal Layout', () => { 15 | cy.get('[data-cy="HorizontalLayout-source"]').dragTo( 16 | '[data-cy="nolayout-drop"]' 17 | ); 18 | cy.get('[data-cy="/-drop-0"]'); 19 | }); 20 | 21 | it('Create Vertical Layout', () => { 22 | cy.get('[data-cy="VerticalLayout-source"]').dragTo( 23 | '[data-cy="nolayout-drop"]' 24 | ); 25 | cy.get('[data-cy="/-drop-0"]'); 26 | }); 27 | }); 28 | 29 | describe('Control Creation Dnd Tests on Example model', () => { 30 | beforeEach(() => { 31 | cy.visit('/'); 32 | // add a layout 33 | cy.get('[data-cy="HorizontalLayout-source"]').dragTo( 34 | '[data-cy="nolayout-drop"]' 35 | ); 36 | }); 37 | 38 | it('Create "name" Control', () => { 39 | cy.get('[data-cy="/properties/name-source"]').dragTo( 40 | '[data-cy="/-drop-0"]' 41 | ); 42 | // TODO more specific check 43 | cy.get('input'); 44 | }); 45 | 46 | it('Create "personalData/height" Control', () => { 47 | // expand personalData 48 | cy.get('[data-cy="/properties/personalData-source"]').click(); 49 | // drag personalData/height 50 | cy.get( 51 | '[data-cy="/properties/personalData/properties/height-source"]' 52 | ).dragTo('[data-cy="/-drop-0"]'); 53 | // TODO more specific check 54 | cy.get('input'); 55 | }); 56 | }); 57 | 58 | describe('Dnd Move Tests on Example model', () => { 59 | beforeEach(() => { 60 | cy.visit('/'); 61 | //SETUP: horizontal layout with two controls("personalData/height" and "name") and a vertical layout 62 | // add a layout 63 | cy.get('[data-cy="HorizontalLayout-source"]').dragTo( 64 | '[data-cy="nolayout-drop"]' 65 | ); 66 | cy.get('[data-cy="/properties/personalData-source"]').click(); 67 | cy.get( 68 | '[data-cy="/properties/personalData/properties/height-source"]' 69 | ).dragTo('[data-cy="/-drop-0"]'); 70 | cy.get('[data-cy="/properties/name-source"]').dragTo( 71 | '[data-cy="/-drop-1"]' 72 | ); 73 | cy.get('[data-cy="VerticalLayout-source"]').dragTo('[data-cy="/-drop-2"]'); 74 | 75 | //check that order is: height, name 76 | cy.get('[data-cy="editorElement-/elements/0"]').should( 77 | 'have.text', 78 | '#/properties/personalData/properties/height' + 'Height*' 79 | ); 80 | cy.get('[data-cy="editorElement-/elements/1"]').should( 81 | 'have.text', 82 | '#/properties/name' + 'Name' 83 | ); 84 | }); 85 | 86 | it('Move element in the same parent, to the right', () => { 87 | // MOVE "height" after "name" 88 | cy.get('[data-cy="editorElement-/elements/0-header"]').dragTo( 89 | '[data-cy="/-drop-2"]' 90 | ); 91 | 92 | //check that order changed to: name, height 93 | cy.get('[data-cy="editorElement-/elements/0"]').should( 94 | 'have.text', 95 | '#/properties/name' + 'Name' 96 | ); 97 | cy.get('[data-cy="editorElement-/elements/1"]').should( 98 | 'have.text', 99 | '#/properties/personalData/properties/height' + 'Height*' 100 | ); 101 | }); 102 | 103 | it('Move element in the same parent, to the left', () => { 104 | // MOVE "name" before "height" 105 | cy.get('[data-cy="editorElement-/elements/1-header"]').dragTo( 106 | '[data-cy="/-drop-0"]' 107 | ); 108 | 109 | //check that order changed to: name, height 110 | cy.get('[data-cy="editorElement-/elements/0"]').should( 111 | 'have.text', 112 | '#/properties/name' + 'Name' 113 | ); 114 | cy.get('[data-cy="editorElement-/elements/1"]').should( 115 | 'have.text', 116 | '#/properties/personalData/properties/height' + 'Height*' 117 | ); 118 | }); 119 | 120 | it('Move element to new parent', () => { 121 | // MOVE "height" to vertical layout 122 | cy.get('[data-cy="editorElement-/elements/0-header"]').dragTo( 123 | '[data-cy="/elements/2-drop-0"]' 124 | ); 125 | 126 | //check that order changed to: name, vertical-layout/height 127 | cy.get('[data-cy="editorElement-/elements/0"]').should( 128 | 'have.text', 129 | '#/properties/name' + 'Name' 130 | ); 131 | cy.get('[data-cy="editorElement-/elements/1/elements/0"]').should( 132 | 'have.text', 133 | '#/properties/personalData/properties/height' + 'Height*' 134 | ); 135 | }); 136 | 137 | it('No layout change when droping element in the drop point to its left', () => { 138 | // drop name in the drop point before it 139 | cy.get('[data-cy="editorElement-/elements/0-header"]').dragTo( 140 | '[data-cy="/-drop-0"]' 141 | ); 142 | 143 | //check that order didn't change 144 | cy.get('[data-cy="editorElement-/elements/0"]').should( 145 | 'have.text', 146 | '#/properties/personalData/properties/height' + 'Height*' 147 | ); 148 | cy.get('[data-cy="editorElement-/elements/1"]').should( 149 | 'have.text', 150 | '#/properties/name' + 'Name' 151 | ); 152 | }); 153 | 154 | it('No layout change when droping element in the drop point to its right', () => { 155 | // drop name in the drop point before it 156 | cy.get('[data-cy="editorElement-/elements/0-header"]').dragTo( 157 | '[data-cy="/-drop-1"]' 158 | ); 159 | 160 | //check that order didn't change 161 | cy.get('[data-cy="editorElement-/elements/0"]').should( 162 | 'have.text', 163 | '#/properties/personalData/properties/height' + 'Height*' 164 | ); 165 | cy.get('[data-cy="editorElement-/elements/1"]').should( 166 | 'have.text', 167 | '#/properties/name' + 'Name' 168 | ); 169 | }); 170 | 171 | it('Layout elements are moved with their children', () => { 172 | // SETUP: "height" to vertical layout 173 | cy.get('[data-cy="editorElement-/elements/0-header"]').dragTo( 174 | '[data-cy="/elements/2-drop-0"]' 175 | ); 176 | 177 | //MOVE layout with control (now on position 1) 178 | cy.get('[data-cy="editorElement-/elements/1-header"]').dragTo( 179 | '[data-cy="/-drop-0"]' 180 | ); 181 | //check height is contained in first element 182 | cy.get('[data-cy="editorElement-/elements/0/elements/0"]').should( 183 | 'have.text', 184 | '#/properties/personalData/properties/height' + 'Height*' 185 | ); 186 | cy.get('[data-cy="editorElement-/elements/1"]').should( 187 | 'have.text', 188 | '#/properties/name' + 'Name' 189 | ); 190 | }); 191 | }); 192 | -------------------------------------------------------------------------------- /app/cypress/integration/editSchemas.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | describe('Edit schemas', () => { 9 | beforeEach(function () { 10 | cy.fixture('simpleSchema').then((simpleSchema) => { 11 | this.simpleSchema = simpleSchema; 12 | }); 13 | cy.fixture('invalidSchema').then((invalidSchema) => { 14 | this.invalidSchema = invalidSchema; 15 | }); 16 | cy.fixture('simpleUiSchema').then((simpleUiSchema) => { 17 | this.simpleUiSchema = simpleUiSchema; 18 | }); 19 | cy.visit('/'); 20 | }); 21 | 22 | const cyReplaceTextInFocus = (jsonObject) => { 23 | cy.focused() 24 | .type('{cmd}a') 25 | .type('{del}') 26 | .type('{ctrl}a') 27 | .type('{del}') 28 | .type(JSON.stringify(jsonObject), { 29 | parseSpecialCharSequences: false, 30 | }); 31 | }; 32 | 33 | const editSchema = (schema, tabSelector) => { 34 | cy.get(`[data-cy="${tabSelector}"]`).click(); 35 | cy.get('[data-cy="edit-schema"]').click(); 36 | 37 | cyReplaceTextInFocus(schema); 38 | cy.get('[data-cy="apply"]').click(); 39 | 40 | cy.get('[data-cy="debug-toggle"]').then(($toggle) => { 41 | if ($toggle.find('input')[0].checked) { 42 | $toggle.click(); 43 | } 44 | }); 45 | 46 | cy.get('[data-cy="schema-text"]').should( 47 | 'have.text', 48 | JSON.stringify(schema, null, 2) 49 | ); 50 | }; 51 | 52 | const cancelEditSchema = (schema, tabSelector) => { 53 | cy.get(`[data-cy="${tabSelector}"]`).click(); 54 | 55 | cy.get('[data-cy="schema-text"]') 56 | .invoke('text') 57 | .then((originalText) => { 58 | cy.get('[data-cy="edit-schema"]').click(); 59 | cyReplaceTextInFocus(schema); 60 | cy.get('[data-cy="cancel"]').click(); 61 | 62 | cy.get('[data-cy="schema-text"]').should('have.text', originalText); 63 | }); 64 | }; 65 | 66 | const escapeEditSchema = (schema, tabSelector) => { 67 | cy.get(`[data-cy="${tabSelector}"]`).click(); 68 | 69 | cy.get('[data-cy="schema-text"]') 70 | .invoke('text') 71 | .then((originalText) => { 72 | cy.get('[data-cy="edit-schema"]').click(); 73 | cyReplaceTextInFocus(schema); 74 | cy.focused().type('{esc}'); 75 | 76 | cy.get('[data-cy="schema-text"]').should('have.text', originalText); 77 | }); 78 | }; 79 | 80 | it('Edit JSON Schema', function () { 81 | editSchema(this.simpleSchema, 'tab-JSON Schema'); 82 | }); 83 | 84 | it('Cancel Edit JSON Schema', function () { 85 | cancelEditSchema(this.simpleSchema, 'tab-JSON Schema'); 86 | }); 87 | 88 | it('Escape Edit JSON Schema', function () { 89 | escapeEditSchema(this.simpleSchema, 'tab-JSON Schema'); 90 | }); 91 | 92 | it('Validate invalid JSON Schema', function () { 93 | cy.get(`[data-cy="tab-JSON Schema"]`).click(); 94 | 95 | cy.get('[data-cy="schema-text"]') 96 | .invoke('text') 97 | .then((originalText) => { 98 | cy.get('[data-cy="edit-schema"]').click(); 99 | cyReplaceTextInFocus(this.invalidSchema); 100 | cy.get('.squiggly-warning').should('exist'); 101 | }); 102 | }); 103 | 104 | it('Validate valid JSON Schema', function () { 105 | cy.get(`[data-cy="tab-JSON Schema"]`).click(); 106 | 107 | cy.get('[data-cy="schema-text"]') 108 | .invoke('text') 109 | .then((originalText) => { 110 | cy.get('[data-cy="edit-schema"]').click(); 111 | cyReplaceTextInFocus(this.simpleSchema); 112 | cy.get('.squiggly-warning').should('not.exist'); 113 | }); 114 | }); 115 | 116 | it('Edit UI Schema', function () { 117 | editSchema(this.simpleUiSchema, 'tab-UI Schema'); 118 | }); 119 | 120 | it('Cancel Edit UI Schema', function () { 121 | cancelEditSchema(this.simpleUiSchema, 'tab-UI Schema'); 122 | }); 123 | 124 | it('Escape Edit UI Schema', function () { 125 | escapeEditSchema(this.simpleUiSchema, 'tab-UI Schema'); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /app/cypress/integration/editor.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | describe('Remove controls', () => { 9 | beforeEach(() => { 10 | cy.visit('/'); 11 | }); 12 | 13 | it('Can remove control root', () => { 14 | // SETUP: add one control 15 | cy.get('[data-cy="/properties/name-source"]').dragTo( 16 | '[data-cy="nolayout-drop"]' 17 | ); 18 | 19 | // remove control 20 | cy.get('[data-cy="editorElement-/-removeButton"]').click(); 21 | 22 | // check that layout is empty 23 | cy.get('[data-cy="nolayout-drop"]'); 24 | }); 25 | 26 | it('Can remove layout root', () => { 27 | // SETUP: add a layout with one control 28 | cy.get('[data-cy="HorizontalLayout-source"]').dragTo( 29 | '[data-cy="nolayout-drop"]' 30 | ); 31 | cy.get('[data-cy="/properties/name-source"]').dragTo( 32 | '[data-cy="/-drop-0"]' 33 | ); 34 | 35 | // remove layuot with control 36 | cy.get('[data-cy="editorElement-/-removeButton"]').click(); 37 | cy.get('[data-cy="ok-button"]').click(); 38 | 39 | // check that layout is empty 40 | cy.get('[data-cy="nolayout-drop"]'); 41 | }); 42 | 43 | it('Can remove control', () => { 44 | // SETUP: add a layout with three controls 45 | cy.get('[data-cy="HorizontalLayout-source"]').dragTo( 46 | '[data-cy="nolayout-drop"]' 47 | ); 48 | cy.get('[data-cy="/properties/birthDate-source"]').dragTo( 49 | '[data-cy="/-drop-0"]' 50 | ); 51 | cy.get('[data-cy="/properties/name-source"]').dragTo( 52 | '[data-cy="/-drop-1"]' 53 | ); 54 | cy.get('[data-cy="/properties/occupation-source"]').dragTo( 55 | '[data-cy="/-drop-2"]' 56 | ); 57 | 58 | // remove middle control 59 | cy.get('[data-cy="editorElement-/elements/1-removeButton"]').click(); 60 | 61 | // check that height and occupation controls remain 62 | cy.get('[data-cy="editorElement-/elements/0"]').should( 63 | 'contain.text', 64 | 'Birth Date' 65 | ); 66 | cy.get('[data-cy="editorElement-/elements/1"]').should( 67 | 'have.text', 68 | '#/properties/occupation' + 'Occupation' 69 | ); 70 | }); 71 | 72 | it('Can remove layout', () => { 73 | // SETUP: add a layout with three elements (two controls, one layout) 74 | cy.get('[data-cy="HorizontalLayout-source"]').dragTo( 75 | '[data-cy="nolayout-drop"]' 76 | ); 77 | cy.get('[data-cy="HorizontalLayout-source"]').dragTo( 78 | '[data-cy="/-drop-0"]' 79 | ); 80 | cy.get('[data-cy="/properties/name-source"]').dragTo( 81 | '[data-cy="/-drop-1"]' 82 | ); 83 | cy.get('[data-cy="/properties/occupation-source"]').dragTo( 84 | '[data-cy="/-drop-2"]' 85 | ); 86 | 87 | // remove middle control 88 | cy.get('[data-cy="editorElement-/elements/0-removeButton"]').click(); 89 | 90 | // check that height and occupation controls remain 91 | cy.get('[data-cy="editorElement-/elements/0"]').should( 92 | 'contain.text', 93 | 'Name' 94 | ); 95 | cy.get('[data-cy="editorElement-/elements/1"]').should( 96 | 'have.text', 97 | '#/properties/occupation' + 'Occupation' 98 | ); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /app/cypress/integration/smoketest.spec.js: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | describe('Smoketest', () => { 9 | beforeEach(() => { 10 | cy.visit('/'); 11 | }); 12 | 13 | it('Renders Title', () => { 14 | cy.contains('JSON Forms Editor'); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /app/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | module.exports = (on, config) => { 9 | // `on` is used to hook into various events Cypress emits 10 | // `config` is the resolved Cypress config 11 | }; 12 | -------------------------------------------------------------------------------- /app/cypress/support/commands.js: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | Cypress.Commands.add( 9 | 'dragTo', 10 | { prevSubject: 'element' }, 11 | (subject, targetEl) => { 12 | cy.wrap(subject) 13 | .trigger('mousedown', { which: 1 }) 14 | .trigger('dragstart') 15 | .trigger('drag', {}); 16 | cy.get(targetEl) 17 | .trigger('dragover') 18 | .trigger('drop') 19 | .then(($targetEl) => { 20 | $targetEl.trigger('dragend'); 21 | $targetEl.trigger('mouseup', { which: 1 }); 22 | }); 23 | } 24 | ); 25 | -------------------------------------------------------------------------------- /app/cypress/support/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import './commands'; 9 | 10 | // Alternatively you can use CommonJS syntax: 11 | // require('./commands') 12 | -------------------------------------------------------------------------------- /app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jsonforms-editor", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@jsonforms/editor": "^0.1.0", 7 | "@material-ui/core": "^4.9.14", 8 | "@rescripts/cli": "0.0.14", 9 | "@rescripts/utilities": "0.0.7", 10 | "@types/react": "^16.9.0", 11 | "@types/react-dom": "^16.9.0", 12 | "eslint": "^7.11.0", 13 | "eslint-config-prettier": "^6.11.0", 14 | "eslint-plugin-header": "^3.0.0", 15 | "eslint-plugin-prettier": "^3.1.3", 16 | "eslint-plugin-simple-import-sort": "^5.0.3", 17 | "monaco-editor-webpack-plugin": "2.0.0", 18 | "prettier": "^2.0.5", 19 | "react": "^16.13.1", 20 | "react-dom": "^16.13.1", 21 | "react-scripts": "^4.0.0", 22 | "typescript": "^3.9.6" 23 | }, 24 | "scripts": { 25 | "start": "rescripts start", 26 | "build": "rescripts build", 27 | "test": "rescripts test --watchAll=false", 28 | "eject": "react-scripts eject", 29 | "cypress:open": "cypress open", 30 | "cypress:run": "cypress run --config video=false", 31 | "cypress:ci": "BROWSER=none start-server-and-test start http://localhost:3000 cypress:run", 32 | "lint": "eslint --ext js,jsx,ts,tsx src cypress" 33 | }, 34 | "browserslist": { 35 | "production": [ 36 | ">0.2%", 37 | "not dead", 38 | "not op_mini all" 39 | ], 40 | "development": [ 41 | "last 1 chrome version", 42 | "last 1 firefox version", 43 | "last 1 safari version" 44 | ] 45 | }, 46 | "devDependencies": { 47 | "@testing-library/jest-dom": "^4.2.4", 48 | "@testing-library/react": "^9.3.2", 49 | "@testing-library/user-event": "^7.1.2", 50 | "@types/jest": "^26.0.0", 51 | "@types/node": "^12.0.0", 52 | "cypress": "^4.5.0", 53 | "eslint-plugin-cypress": "^2.10.3", 54 | "start-server-and-test": "^1.11.0" 55 | }, 56 | "jest": { 57 | "transformIgnorePatterns": [ 58 | "node_modules/(?!(monaco-editor)/)" 59 | ], 60 | "moduleNameMapper": { 61 | "^.+\\.(css|scss)$": "identity-obj-proxy", 62 | "monaco-editor": "/../node_modules/react-monaco-editor" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /app/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipsesource/jsonforms-editor/a64f24d3c305a47c989625c8992450754ac4eaad/app/public/favicon.ico -------------------------------------------------------------------------------- /app/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 15 | 24 | 25 | 27 | JSON Forms Editor 28 | 29 | 30 | 31 |
32 | 36 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/public/jsonforms-angular-webcomponent/runtime-es2015.js: -------------------------------------------------------------------------------- 1 | !function(e){function r(r){for(var n,l,f=r[0],i=r[1],p=r[2],c=0,s=[];c { 14 | // components with 'useEffect' need to be awaited 15 | const container = render(
); 16 | await act(async () => { 17 | render(, container); 18 | }); 19 | const titleElement = container.getByText(/JSON Forms Editor/i); 20 | expect(titleElement).toBeInTheDocument(); 21 | }); 22 | -------------------------------------------------------------------------------- /app/src/App.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { 9 | defaultSchemaDecorators, 10 | defaultSchemaProviders, 11 | JsonFormsEditor, 12 | ReactMaterialPreview, 13 | } from '@jsonforms/editor'; 14 | import React from 'react'; 15 | 16 | import { AngularMaterialPreview } from './components/AngularMaterialPreview'; 17 | import { Footer } from './components/Footer'; 18 | import { ExampleSchemaService } from './core/schemaService'; 19 | 20 | const schemaService = new ExampleSchemaService(); 21 | export const App = () => ( 22 | 32 | ); 33 | -------------------------------------------------------------------------------- /app/src/components/AngularMaterialPreview.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { 9 | generateEmptyData, 10 | previewOptions, 11 | useExportSchema, 12 | useExportUiSchema, 13 | useSchema, 14 | } from '@jsonforms/editor'; 15 | import React, { useMemo } from 'react'; 16 | 17 | declare global { 18 | namespace JSX { 19 | interface IntrinsicElements { 20 | 'ng-jsonforms': any; 21 | } 22 | } 23 | } 24 | 25 | export const AngularMaterialPreview: React.FC = () => { 26 | const schema = useExportSchema(); 27 | const uiSchema = useExportUiSchema(); 28 | const editorSchema = useSchema(); 29 | const data = useMemo( 30 | () => (editorSchema ? generateEmptyData(editorSchema) : {}), 31 | [editorSchema] 32 | ); 33 | const inputSchema = JSON.stringify(schema); 34 | const inputUISchema = JSON.stringify(uiSchema); 35 | const inputData = JSON.stringify(data); 36 | const options = JSON.stringify(previewOptions); 37 | 38 | return inputUISchema && inputSchema ? ( 39 |
40 | 46 |
47 | ) : null; 48 | }; 49 | -------------------------------------------------------------------------------- /app/src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { Container, Link, makeStyles, Typography } from '@material-ui/core'; 9 | import React from 'react'; 10 | 11 | const useStyles = makeStyles(() => ({ 12 | container: { 13 | display: 'flex', 14 | flexDirection: 'row', 15 | justifyContent: 'flex-end', 16 | }, 17 | })); 18 | 19 | const Copyright: React.FC = () => ( 20 | 21 | {'Copyright © '} 22 | 23 | EclipseSource 24 | 25 | {' ' + new Date().getFullYear()} 26 | 27 | ); 28 | 29 | export const Footer: React.FC = () => { 30 | const classes = useStyles(); 31 | return ( 32 | 33 | 34 | 35 | ); 36 | }; 37 | -------------------------------------------------------------------------------- /app/src/core/schemaService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { SchemaService } from '@jsonforms/editor'; 9 | 10 | const exampleSchema = { 11 | type: 'object', 12 | title: 'Person', 13 | properties: { 14 | name: { 15 | type: 'string', 16 | minLength: 3, 17 | }, 18 | birthDate: { 19 | type: 'string', 20 | format: 'date', 21 | }, 22 | personalData: { 23 | type: 'object', 24 | properties: { 25 | age: { 26 | type: 'integer', 27 | description: 'Please enter your age.', 28 | }, 29 | height: { 30 | type: 'number', 31 | }, 32 | drivingSkill: { 33 | type: 'number', 34 | maximum: 10, 35 | minimum: 1, 36 | default: 7, 37 | }, 38 | }, 39 | required: ['age', 'height'], 40 | }, 41 | friends: { 42 | type: 'array', 43 | items: { 44 | type: 'object', 45 | title: 'Friend', 46 | properties: { 47 | name: { 48 | type: 'string', 49 | }, 50 | isClose: { 51 | type: 'boolean', 52 | }, 53 | }, 54 | }, 55 | }, 56 | nationality: { 57 | type: 'string', 58 | enum: ['DE', 'IT', 'JP', 'US', 'RU', 'Other'], 59 | }, 60 | occupation: { 61 | type: 'string', 62 | }, 63 | }, 64 | }; 65 | 66 | const exampleUischema = undefined; 67 | 68 | export class ExampleSchemaService implements SchemaService { 69 | getSchema = async () => exampleSchema; 70 | getUiSchema = async () => exampleUischema; 71 | } 72 | -------------------------------------------------------------------------------- /app/src/index.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { CssBaseline } from '@material-ui/core'; 9 | import React from 'react'; 10 | import ReactDOM from 'react-dom'; 11 | 12 | import { App } from './App'; 13 | 14 | ReactDOM.render( 15 | 16 | 17 | 18 | , 19 | document.getElementById('root') 20 | ); 21 | -------------------------------------------------------------------------------- /app/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | /// 9 | -------------------------------------------------------------------------------- /app/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import '@testing-library/jest-dom/extend-expect'; 9 | -------------------------------------------------------------------------------- /app/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "resolveJsonModule": true, 18 | "isolatedModules": true, 19 | "noEmit": true, 20 | "jsx": "react", 21 | "noFallthroughCasesInSwitch": true 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /jsonforms-editor/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | stats.html 3 | -------------------------------------------------------------------------------- /jsonforms-editor/.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | stats.html 3 | -------------------------------------------------------------------------------- /jsonforms-editor/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2021 EclipseSource Munich 4 | https://github.com/eclipsesource/jsonforms-editor 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /jsonforms-editor/README.md: -------------------------------------------------------------------------------- 1 | # JSONForms - More Forms. Less Code 2 | 3 | **Complex Forms in the blink of an eye** 4 | 5 | [JSON Forms](https://jsonforms.io/support) eliminates the tedious task of writing fully-featured forms by hand by leveraging the capabilities of JSON, JSON Schema and Javascript. 6 | 7 | ## JSON Forms Editor 8 | 9 | The JSON Forms Editor allows graphical editing of JSON Schemas and JSON Forms UI Schemas. 10 | 11 | This package contains the core `@jsonforms/editor` on which the [JSON Forms Editor app](../app) is based. 12 | 13 | ## Usage 14 | 15 | - `npm install --save @jsonforms/editor` 16 | 17 | ```typescript 18 | import JsonFormsEditor, { 19 | defaultSchemaDecorators, 20 | propertySchemaProvider, 21 | } from '@jsonforms/editor'; 22 | 23 | const App = () => ( 24 | 28 | ); 29 | ``` 30 | 31 | ## Monaco Editor 32 | 33 | If you want syntax highlighting and autocompletion in the embedded Monaco Editor you'll need to customize your build. 34 | The easiest way is to use the `MonacoWebpackPlugin` plugin. 35 | Check [react-monaco-editor](https://github.com/react-monaco-editor/react-monaco-editor) for more information. 36 | 37 | ## Jest 38 | 39 | If you're using Jest for component testing you'll need to configure it for the Monaco Editor and imported CSS. 40 | 41 | For example you can configure Jest via the `package.json`: 42 | 43 | ```json 44 | "jest": { 45 | "transformIgnorePatterns": [ 46 | "node_modules/(?!(monaco-editor)/)" 47 | ], 48 | "moduleNameMapper": { 49 | "^.+\\.(css|scss)$": "identity-obj-proxy", 50 | "monaco-editor": "/../node_modules/react-monaco-editor" 51 | } 52 | } 53 | ``` 54 | 55 | ## Feedback, Help and Support 56 | 57 | We have a [Spectrum Chat](https://spectrum.chat/jsonforms) where you can reach out to the community if you have questions or contact us [directly via email](mailto:jsonforms@eclipsesource.com?subject=JSON%20Forms%20Editor). 58 | In addition EclipseSource also offers [professional support](https://jsonforms.io/support) for JSON Forms. 59 | 60 | ## License 61 | 62 | The JSONForms project is licensed under the MIT License. See the [LICENSE file](https://github.com/eclipsesource/jsonforms/blob/master/LICENSE) for more information. 63 | -------------------------------------------------------------------------------- /jsonforms-editor/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@jsonforms/editor", 3 | "version": "0.1.0", 4 | "private": true, 5 | "main": "dist/index.js", 6 | "module": "dist/index.es.js", 7 | "files": [ 8 | "dist" 9 | ], 10 | "typings": "src", 11 | "dependencies": { 12 | "@jsonforms/core": "^2.4.1-beta.0", 13 | "@jsonforms/material-renderers": "^2.4.1-beta.0", 14 | "@jsonforms/react": "^2.4.1-beta.0", 15 | "@material-ui/core": "^4.9.14", 16 | "@material-ui/icons": "^4.9.1", 17 | "@material-ui/lab": "^4.0.0-alpha.53", 18 | "json-schema-traverse": "^0.4.1", 19 | "lodash": "^4.17.15", 20 | "react": "^16.13.1", 21 | "react-dnd": "^10.0.2", 22 | "react-dnd-html5-backend": "^10.0.2", 23 | "react-dom": "^16.13.1", 24 | "react-monaco-editor": "^0.36.0", 25 | "monaco-editor": "0.21.2", 26 | "react-redux": "^7.2.0", 27 | "react-reflex": "^3.0.22", 28 | "react-scripts": "^4.0.0", 29 | "react-spring": "^8.0.27", 30 | "redux": "^4.0.5", 31 | "uuid": "^8.1.0" 32 | }, 33 | "scripts": { 34 | "build": "rimraf dist && rollup --config rollup.config.ts", 35 | "watch": "rimraf dist && rollup -cw", 36 | "jest": "jest", 37 | "test": "jest", 38 | "lint": "eslint --ext js,jsx,ts,tsx src" 39 | }, 40 | "browserslist": { 41 | "production": [ 42 | ">0.2%", 43 | "not dead", 44 | "not op_mini all" 45 | ], 46 | "development": [ 47 | "last 1 chrome version", 48 | "last 1 firefox version", 49 | "last 1 safari version" 50 | ] 51 | }, 52 | "devDependencies": { 53 | "@rollup/plugin-commonjs": "^20.0.0", 54 | "@rollup/plugin-json": "^4.1.0", 55 | "@rollup/plugin-node-resolve": "^13.0.5", 56 | "@rollup/plugin-typescript": "^8.2.5", 57 | "@testing-library/jest-dom": "^4.2.4", 58 | "@testing-library/react": "^9.3.2", 59 | "@testing-library/user-event": "^7.1.2", 60 | "@types/jest": "^26.0.0", 61 | "@types/json-schema-traverse": "^0.4.0", 62 | "@types/lodash": "^4.14.153", 63 | "@types/node": "^12.0.0", 64 | "@types/react": "^16.9.0", 65 | "@types/react-dom": "^16.9.0", 66 | "eslint": "^7.11.0", 67 | "eslint-config-prettier": "^6.11.0", 68 | "eslint-plugin-header": "^3.0.0", 69 | "eslint-plugin-prettier": "^3.1.3", 70 | "eslint-plugin-simple-import-sort": "^5.0.3", 71 | "jest": "26.6.0", 72 | "prettier": "^2.0.5", 73 | "rimraf": "^3.0.2", 74 | "rollup": "^2.57.0", 75 | "rollup-plugin-import-css": "^3.0.2", 76 | "rollup-plugin-visualizer": "^5.5.2", 77 | "typescript": "^3.9.6" 78 | }, 79 | "babel": { 80 | "presets": [ 81 | "react-app" 82 | ] 83 | }, 84 | "jest": { 85 | "roots": [ 86 | "src/" 87 | ], 88 | "setupFilesAfterEnv": [ 89 | "/src/setupTests.ts" 90 | ], 91 | "testMatch": [ 92 | "/src/**/__tests__/**/*.{js,jsx,ts,tsx}", 93 | "/src/**/*.{spec,test}.{js,jsx,ts,tsx}" 94 | ], 95 | "testEnvironment": "jest-environment-jsdom-fourteen", 96 | "transform": { 97 | "^.+\\.(js|jsx|ts|tsx)$": "babel-jest" 98 | }, 99 | "transformIgnorePatterns": [ 100 | "node_modules/(?!(monaco-editor)/)" 101 | ], 102 | "moduleNameMapper": { 103 | "^.+\\.(css|scss)$": "identity-obj-proxy", 104 | "monaco-editor": "/../node_modules/react-monaco-editor" 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /jsonforms-editor/rollup.config.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | 9 | import json from '@rollup/plugin-json'; 10 | import typescript from '@rollup/plugin-typescript'; 11 | import css from 'rollup-plugin-import-css'; 12 | import { visualizer } from 'rollup-plugin-visualizer'; 13 | 14 | const packageJson = require('./package.json'); 15 | 16 | const config = { 17 | input: 'src/index.ts', 18 | output: [ 19 | { 20 | file: packageJson.main, 21 | format: 'cjs', 22 | sourcemap: true, 23 | }, 24 | { 25 | file: packageJson.module, 26 | format: 'esm', 27 | sourcemap: true, 28 | }, 29 | ], 30 | plugins: [typescript(), css(), json(), visualizer({ open: false })], 31 | external: [ 32 | ...Object.keys(packageJson.dependencies), 33 | /^@material-ui\/.*/, 34 | 'react-reflex/styles.css', 35 | 'monaco-editor/esm/vs/editor/editor.api', 36 | 'react-spring/web.cjs', 37 | ], 38 | }; 39 | 40 | export default config; 41 | -------------------------------------------------------------------------------- /jsonforms-editor/src/JsonFormsEditor.css: -------------------------------------------------------------------------------- 1 | /* customize problematic react-reflex rules: 2 | * - set height back to auto 3 | * - use align-self to grow the splitter vertically instead of relying on a pre-set height 4 | */ 5 | .reflex-container.vertical > .reflex-splitter { 6 | height: auto; 7 | align-self: stretch; 8 | } 9 | -------------------------------------------------------------------------------- /jsonforms-editor/src/JsonFormsEditor.test.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { act, render } from '@testing-library/react'; 9 | import React from 'react'; 10 | 11 | import { JsonFormsEditor } from './JsonFormsEditor'; 12 | import { defaultSchemaDecorators } from './properties/schemaDecorators'; 13 | import { propertySchemaProvider } from './properties/schemaProviders'; 14 | 15 | test('renders header', async () => { 16 | // components with 'useEffect' need to be awaited 17 | const container = render(
); 18 | await act(async () => { 19 | render( 20 | , 24 | container 25 | ); 26 | }); 27 | const titleElement = container.getByText(/JSON Forms Editor/i); 28 | expect(titleElement).toBeInTheDocument(); 29 | }); 30 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/api/categorizationService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | 9 | import { CategorizationLayout, EditorUISchemaElement } from '../model'; 10 | import { SelectedElement } from '../selection'; 11 | 12 | export interface CategorizationService { 13 | clearTabSelections: () => void; 14 | removeElement: (element: EditorUISchemaElement) => void; 15 | 16 | getTabSelection: (categorization: CategorizationLayout) => SelectedElement; 17 | 18 | setTabSelection: ( 19 | categorization: CategorizationLayout, 20 | selection: SelectedElement 21 | ) => void; 22 | } 23 | 24 | export class CategorizationServiceImpl implements CategorizationService { 25 | private parentUuids = new Map(); 26 | private selectedTabs = new Map(); 27 | 28 | getTabSelection: (categorization: CategorizationLayout) => SelectedElement = ( 29 | categorization 30 | ) => this.selectedTabs.get(categorization.uuid); 31 | 32 | setTabSelection: ( 33 | categorization: CategorizationLayout, 34 | selection: SelectedElement 35 | ) => void = (categorization, selection) => { 36 | this.selectedTabs.set(categorization.uuid, selection); 37 | 38 | if (!this.parentUuids.has(categorization.uuid)) { 39 | // capture element parents that are Categorization or Category 40 | this.parentUuids.set( 41 | categorization.uuid, 42 | this.getParentCategoryIds(categorization.parent) 43 | ); 44 | } 45 | }; 46 | 47 | clearTabSelections: () => void = () => { 48 | this.selectedTabs.clear(); 49 | this.parentUuids.clear(); 50 | }; 51 | 52 | removeElement: (element: EditorUISchemaElement) => void = (element) => { 53 | // no need to hold the memory for Map entry in this case 54 | this.selectedTabs.delete(element.uuid); 55 | this.parentUuids.delete(element.uuid); 56 | 57 | this.parentUuids.forEach((parents, uuid, map) => { 58 | if (parents.includes(element.uuid)) { 59 | map.delete(uuid); 60 | this.selectedTabs.delete(uuid); 61 | } 62 | }); 63 | }; 64 | 65 | private getParentCategoryIds = ( 66 | categorization: EditorUISchemaElement | undefined 67 | ): string[] => { 68 | if (categorization === undefined) { 69 | return []; 70 | } 71 | if ( 72 | categorization.type === 'Categorization' || 73 | categorization.type === 'Category' 74 | ) { 75 | return [ 76 | categorization.uuid, 77 | ...this.getParentCategoryIds(categorization.parent), 78 | ]; 79 | } else { 80 | return this.getParentCategoryIds(categorization.parent); 81 | } 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/api/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export * from './schemaService'; 9 | export * from './paletteService'; 10 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/api/paletteService.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | 9 | import React from 'react'; 10 | 11 | import { 12 | CategorizationIcon, 13 | GroupIcon, 14 | HorizontalIcon, 15 | LabelIcon, 16 | VerticalIcon, 17 | } from '../icons'; 18 | import { EditorUISchemaElement } from '../model/uischema'; 19 | import { 20 | createCategorization, 21 | createLabel, 22 | createLayout, 23 | } from '../util/generators/uiSchema'; 24 | 25 | export interface PaletteService { 26 | getPaletteElements(): PaletteElement[]; 27 | } 28 | 29 | export interface PaletteElement { 30 | type: string; 31 | label: string; 32 | icon: React.ReactNode; 33 | uiSchemaElementProvider: () => EditorUISchemaElement; 34 | } 35 | 36 | const paletteElements: PaletteElement[] = [ 37 | { 38 | type: 'HorizontalLayout', 39 | label: 'Horizontal Layout', 40 | icon: , 41 | uiSchemaElementProvider: () => createLayout('HorizontalLayout'), 42 | } as PaletteElement, 43 | { 44 | type: 'VerticalLayout', 45 | label: 'Vertical Layout', 46 | icon: , 47 | uiSchemaElementProvider: () => createLayout('VerticalLayout'), 48 | }, 49 | { 50 | type: 'Group', 51 | label: 'Group', 52 | icon: , 53 | uiSchemaElementProvider: () => createLayout('Group'), 54 | }, 55 | { 56 | type: 'Label', 57 | label: 'Label', 58 | icon: , 59 | uiSchemaElementProvider: () => createLabel(), 60 | }, 61 | { 62 | type: 'Categorization', 63 | label: 'Categorization', 64 | icon: , 65 | uiSchemaElementProvider: () => createCategorization(), 66 | }, 67 | ]; 68 | 69 | export class DefaultPaletteService implements PaletteService { 70 | getPaletteElements = () => paletteElements; 71 | } 72 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/api/schemaService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | 9 | export interface SchemaService { 10 | getSchema(): Promise; 11 | getUiSchema(): Promise; 12 | } 13 | 14 | export class EmptySchemaService implements SchemaService { 15 | getSchema = async () => undefined; 16 | getUiSchema = async () => undefined; 17 | } 18 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/components/ErrorDialog.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { 9 | Button, 10 | Dialog, 11 | DialogActions, 12 | DialogContent, 13 | DialogContentText, 14 | DialogTitle, 15 | } from '@material-ui/core'; 16 | import React from 'react'; 17 | 18 | interface ErrorDialogProps { 19 | open: boolean; 20 | title: string; 21 | text: string; 22 | onClose: () => void; 23 | } 24 | 25 | export const ErrorDialog: React.FC = ({ 26 | open, 27 | title, 28 | text, 29 | onClose, 30 | }) => { 31 | return ( 32 | 33 | {title} 34 | 35 | {text} 36 | 37 | 38 | 41 | 42 | 43 | ); 44 | }; 45 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/components/ExportDialog.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import Button from '@material-ui/core/Button'; 9 | import Dialog from '@material-ui/core/Dialog'; 10 | import DialogActions from '@material-ui/core/DialogActions'; 11 | import DialogContent from '@material-ui/core/DialogContent'; 12 | import DialogTitle from '@material-ui/core/DialogTitle'; 13 | import Hidden from '@material-ui/core/Hidden'; 14 | import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; 15 | import Tab from '@material-ui/core/Tab'; 16 | import Tabs from '@material-ui/core/Tabs'; 17 | import Cancel from '@material-ui/icons/Cancel'; 18 | import React, { useState } from 'react'; 19 | 20 | import { FormattedJson } from './Formatted'; 21 | 22 | const useStyles = makeStyles((theme: Theme) => 23 | createStyles({ 24 | button: { 25 | margin: theme.spacing(1), 26 | }, 27 | title: { 28 | textAlign: 'center', 29 | }, 30 | content: { 31 | maxHeight: '90vh', 32 | height: '90vh', 33 | }, 34 | }) 35 | ); 36 | 37 | export interface ExportDialogProps { 38 | open: boolean; 39 | onClose: () => void; 40 | schema: any; 41 | uiSchema: any; 42 | } 43 | export const ExportDialog = ({ 44 | open, 45 | onClose, 46 | schema, 47 | uiSchema, 48 | }: ExportDialogProps) => { 49 | const classes = useStyles(); 50 | const [selectedTab, setSelectedTab] = useState(0); 51 | const handleTabChange = (event: React.ChangeEvent<{}>, newValue: number) => { 52 | setSelectedTab(newValue); 53 | }; 54 | return ( 55 | 64 | 65 | {'Export'} 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 90 | 91 | 92 | ); 93 | }; 94 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { Container, makeStyles, Typography } from '@material-ui/core'; 9 | import React from 'react'; 10 | 11 | const useStyles = makeStyles(() => ({ 12 | container: { 13 | display: 'flex', 14 | flexDirection: 'row', 15 | justifyContent: 'flex-end', 16 | }, 17 | })); 18 | 19 | export const Footer: React.FC = () => { 20 | const classes = useStyles(); 21 | return ( 22 | 23 | 24 | {`Copyright © ${new Date().getFullYear()}`} 25 | 26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/components/Formatted.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import React from 'react'; 9 | 10 | interface FormattedJsonProps { 11 | object?: any; 12 | } 13 | 14 | export const FormattedJson: React.FC = (object) => { 15 | return
{JSON.stringify(object, null, 2)}
; 16 | }; 17 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/components/Header.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import AppBar from '@material-ui/core/AppBar'; 9 | import IconButton from '@material-ui/core/IconButton'; 10 | import { createStyles, makeStyles } from '@material-ui/core/styles'; 11 | import Toolbar from '@material-ui/core/Toolbar'; 12 | import Typography from '@material-ui/core/Typography'; 13 | import CloudDownload from '@material-ui/icons/CloudDownload'; 14 | import React, { useState } from 'react'; 15 | 16 | import { useExportSchema, useExportUiSchema } from '../util/hooks'; 17 | import { ExportDialog } from './ExportDialog'; 18 | 19 | const useStyles = makeStyles(() => 20 | createStyles({ 21 | title: { 22 | flexGrow: 1, 23 | }, 24 | }) 25 | ); 26 | 27 | export const Header: React.FC = () => { 28 | const classes = useStyles(); 29 | const schema = useExportSchema(); 30 | const uiSchema = useExportUiSchema(); 31 | const [open, setOpen] = useState(false); 32 | const onClose = () => setOpen(false); 33 | const openDownloadDialog = () => setOpen(true); 34 | 35 | return ( 36 | 37 | 38 | 44 | JSON Forms Editor 45 | 46 | 51 | 52 | 53 | 54 | {open && ( 55 | 61 | )} 62 | 63 | ); 64 | }; 65 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/components/Layout.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { makeStyles } from '@material-ui/core'; 9 | import React from 'react'; 10 | 11 | const useStyles = makeStyles((theme) => ({ 12 | main: { 13 | marginTop: theme.spacing(2), 14 | marginBottom: theme.spacing(2), 15 | minHeight: 0, 16 | }, 17 | container: { 18 | display: 'grid', 19 | height: '100vh', 20 | gridTemplateAreas: 'header content footer', 21 | gridTemplateColumns: '1fr', 22 | gridTemplateRows: 'auto 1fr auto', 23 | }, 24 | footer: { 25 | padding: theme.spacing(2, 2), 26 | backgroundColor: 27 | theme.palette.type === 'light' 28 | ? theme.palette.grey[200] 29 | : theme.palette.grey[800], 30 | }, 31 | })); 32 | 33 | interface LayoutProps { 34 | HeaderComponent?: React.ComponentType; 35 | FooterComponent?: React.ComponentType; 36 | } 37 | 38 | export const Layout: React.FC = ({ 39 | HeaderComponent, 40 | FooterComponent, 41 | children, 42 | }) => { 43 | const classes = useStyles(); 44 | return ( 45 |
46 |
{HeaderComponent ? : null}
47 |
{children}
48 |
49 | {FooterComponent ? : null} 50 |
51 |
52 | ); 53 | }; 54 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/components/OkCancelDialog.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { 9 | Button, 10 | Dialog, 11 | DialogActions, 12 | DialogContent, 13 | DialogContentText, 14 | DialogTitle, 15 | } from '@material-ui/core'; 16 | import React from 'react'; 17 | 18 | interface OkCancelDialogProps { 19 | open: boolean; 20 | title?: string; 21 | text: string; 22 | onOk: () => void; 23 | onCancel: () => void; 24 | } 25 | 26 | export const OkCancelDialog: React.FC = ({ 27 | open, 28 | title = '', 29 | text, 30 | onOk, 31 | onCancel, 32 | }) => { 33 | return ( 34 | 35 | {title} 36 | 37 | {text} 38 | 39 | 40 | 43 | 46 | 47 | 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/components/ShowMoreLess.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { Button, Collapse } from '@material-ui/core'; 9 | import React, { useState } from 'react'; 10 | export interface ShowMoreLessProps { 11 | className?: string; 12 | } 13 | 14 | export const ShowMoreLess: React.FC = ({ 15 | className, 16 | children, 17 | }) => { 18 | const [showMore, setShowMore] = useState(false); 19 | return ( 20 |
21 | {children} 22 | 30 |
31 | ); 32 | }; 33 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/components/TabContent.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { makeStyles } from '@material-ui/core/styles'; 9 | import React from 'react'; 10 | export interface TabContentProps { 11 | children?: React.ReactNode; 12 | index: number; 13 | currentIndex: number; 14 | } 15 | 16 | const useStyles = makeStyles((theme) => ({ 17 | tabContent: { 18 | padding: theme.spacing(1, 1, 0, 1), 19 | height: '100%', 20 | overflow: 'auto', 21 | }, 22 | })); 23 | 24 | export const TabContent: React.FC = ( 25 | props: TabContentProps 26 | ) => { 27 | const { children, index, currentIndex, ...other } = props; 28 | const classes = useStyles(); 29 | return ( 30 | 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/components/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export { Layout } from './Layout'; 9 | export * from './Formatted'; 10 | export * from './TabContent'; 11 | export * from './Header'; 12 | export * from './Footer'; 13 | export * from './ErrorDialog'; 14 | export * from './OkCancelDialog'; 15 | export * from './ExportDialog'; 16 | export * from './ShowMoreLess'; 17 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/context/context.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import React, { useContext } from 'react'; 9 | 10 | import { PropertiesService } from '../../properties/propertiesService'; 11 | import { CategorizationService } from '../api/categorizationService'; 12 | import { PaletteService } from '../api/paletteService'; 13 | import { SchemaService } from '../api/schemaService'; 14 | import { SchemaElement } from '../model'; 15 | import { EditorAction } from '../model/actions'; 16 | import { EditorUISchemaElement } from '../model/uischema'; 17 | import { SelectedElement } from '../selection'; 18 | 19 | export interface EditorContext { 20 | schemaService: SchemaService; 21 | paletteService: PaletteService; 22 | propertiesService: PropertiesService; 23 | schema: SchemaElement | undefined; 24 | uiSchema: EditorUISchemaElement | undefined; 25 | dispatch: (action: EditorAction) => void; 26 | selection: SelectedElement; 27 | setSelection: (selection: SelectedElement) => void; 28 | categorizationService: CategorizationService; 29 | } 30 | 31 | /**We always use a provider so default can be undefined*/ 32 | const defaultContext: any = undefined; 33 | 34 | export const EditorContextInstance = 35 | React.createContext(defaultContext); 36 | 37 | export const useEditorContext = (): EditorContext => 38 | useContext(EditorContextInstance); 39 | 40 | export const useGitLabService = (): SchemaService => { 41 | const { schemaService } = useEditorContext(); 42 | return schemaService; 43 | }; 44 | 45 | export const useSchema = (): SchemaElement | undefined => { 46 | const { schema } = useEditorContext(); 47 | return schema; 48 | }; 49 | 50 | export const useUiSchema = (): EditorUISchemaElement | undefined => { 51 | const { uiSchema } = useEditorContext(); 52 | return uiSchema; 53 | }; 54 | 55 | export const useSelection = (): [ 56 | SelectedElement, 57 | (selection: SelectedElement) => void 58 | ] => { 59 | const { selection, setSelection } = useEditorContext(); 60 | return [selection, setSelection]; 61 | }; 62 | 63 | export const useDispatch = (): ((action: EditorAction) => void) => { 64 | const { dispatch } = useEditorContext(); 65 | return dispatch; 66 | }; 67 | 68 | export const usePaletteService = (): PaletteService => { 69 | const { paletteService } = useEditorContext(); 70 | return paletteService; 71 | }; 72 | 73 | export const usePropertiesService = (): PropertiesService => { 74 | const { propertiesService } = useEditorContext(); 75 | return propertiesService; 76 | }; 77 | 78 | export const useCategorizationService = (): CategorizationService => { 79 | const { categorizationService } = useEditorContext(); 80 | return categorizationService; 81 | }; 82 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/context/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export * from './context'; 9 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/dnd/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export * from './types'; 9 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/dnd/types.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { getArrayContainer, SchemaElement } from '../model'; 9 | import { 10 | containsControls, 11 | EditorLayout, 12 | EditorUISchemaElement, 13 | getDetailContainer, 14 | } from '../model/uischema'; 15 | import { tryFindByUUID } from '../util/schemasUtil'; 16 | import { getHierarchy } from '../util/tree'; 17 | 18 | export const NEW_UI_SCHEMA_ELEMENT: 'newUiSchemaElement' = 'newUiSchemaElement'; 19 | export const MOVE_UI_SCHEMA_ELEMENT: 'moveUiSchemaElement' = 20 | 'moveUiSchemaElement'; 21 | 22 | export type DndType = NewUISchemaElement | MoveUISchemaElement; 23 | 24 | export interface NewUISchemaElement { 25 | type: 'newUiSchemaElement'; 26 | uiSchemaElement: EditorUISchemaElement; 27 | schemaUUID?: string; 28 | } 29 | 30 | const newUISchemaElement = ( 31 | uiSchemaElement: EditorUISchemaElement, 32 | schemaUUID?: string 33 | ) => ({ 34 | type: NEW_UI_SCHEMA_ELEMENT, 35 | uiSchemaElement, 36 | schemaUUID, 37 | }); 38 | 39 | export interface MoveUISchemaElement { 40 | type: 'moveUiSchemaElement'; 41 | uiSchemaElement: EditorUISchemaElement; 42 | schema?: SchemaElement; 43 | } 44 | 45 | const moveUISchemaElement = ( 46 | uiSchemaElement: EditorUISchemaElement, 47 | schema?: SchemaElement 48 | ) => ({ 49 | type: MOVE_UI_SCHEMA_ELEMENT, 50 | uiSchemaElement, 51 | schema, 52 | }); 53 | 54 | export const DndItems = { newUISchemaElement, moveUISchemaElement }; 55 | 56 | export const canDropIntoLayout = ( 57 | item: NewUISchemaElement, 58 | rootSchema: SchemaElement | undefined, 59 | layout: EditorUISchemaElement 60 | ) => { 61 | // check scope changes 62 | const detailContainer = getDetailContainer(layout); 63 | return canDropIntoScope(item, rootSchema, detailContainer); 64 | }; 65 | 66 | /** 67 | * Check whether the element to drop fits into the given scope, 68 | * e.g. whether a nested array object is dropped into the correct array ui schema control. 69 | * 70 | * @param item the drag and drop item 71 | * @param scopeUISchemaElement the nearest scope changing element, 72 | * e.g. the nearest array control into which shall be dropped. 73 | * Use `undefined` when dropping outside of any scope changing element. 74 | */ 75 | export const canDropIntoScope = ( 76 | item: NewUISchemaElement, 77 | rootSchema: SchemaElement | undefined, 78 | scopeUISchemaElement: EditorUISchemaElement | undefined 79 | ) => { 80 | const controlObject = tryFindByUUID(rootSchema, item.schemaUUID); 81 | if (controlObject) { 82 | const scopeSchemaElement = getScopeChangingContainer(controlObject); 83 | if (!scopesMatch(scopeSchemaElement, scopeUISchemaElement)) { 84 | return false; 85 | } 86 | } 87 | return true; 88 | }; 89 | 90 | /** 91 | * Scopes match if they are linked or both don't exist. 92 | */ 93 | const scopesMatch = ( 94 | schemaScope: SchemaElement | undefined, 95 | uiScope: EditorUISchemaElement | undefined 96 | ) => { 97 | return uiScope?.linkedSchemaElement === schemaScope?.uuid; 98 | }; 99 | 100 | /** 101 | * Returns the closest scope changing schema container 102 | */ 103 | const getScopeChangingContainer = (element: SchemaElement) => { 104 | // TODO check other cases than array 105 | return getArrayContainer(element); 106 | }; 107 | 108 | export const canMoveSchemaElementTo = ( 109 | item: MoveUISchemaElement, 110 | target: EditorUISchemaElement, 111 | index: number 112 | ) => { 113 | const elementToMove = item.uiSchemaElement as EditorUISchemaElement; 114 | return ( 115 | !isMoveRoot(elementToMove) && 116 | !isMoveIntoItself(elementToMove, target) && 117 | !isMoveNextToItself(elementToMove, target, index) && 118 | !isMovingControlsInterScopes(elementToMove, target) 119 | ); 120 | }; 121 | 122 | const isMoveRoot = (elementToMove: EditorUISchemaElement) => 123 | !elementToMove.parent; 124 | const isMoveIntoItself = ( 125 | elementToMove: EditorUISchemaElement, 126 | target: EditorUISchemaElement 127 | ) => getHierarchy(target).includes(elementToMove); 128 | const isMoveNextToItself = ( 129 | elementToMove: EditorUISchemaElement, 130 | target: EditorUISchemaElement, 131 | index: number 132 | ) => { 133 | if (target === elementToMove.parent) { 134 | const currentIndex = (target as EditorLayout).elements.indexOf( 135 | elementToMove 136 | ); 137 | if (currentIndex === index || currentIndex === index - 1) { 138 | return true; 139 | } 140 | } 141 | return false; 142 | }; 143 | const isMovingControlsInterScopes = ( 144 | elementToMove: EditorUISchemaElement, 145 | target: EditorUISchemaElement 146 | ) => 147 | containsControls(elementToMove) && 148 | getDetailContainer(elementToMove) !== getDetailContainer(target); 149 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/icons/icons.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { styled } from '@material-ui/core'; 9 | import CropFreeIcon from '@material-ui/icons/CropFree'; 10 | import Height from '@material-ui/icons/Height'; 11 | import InsertLinkIcon from '@material-ui/icons/InsertLink'; 12 | import LabelOutlinedIcon from '@material-ui/icons/LabelOutlined'; 13 | import ListAltIcon from '@material-ui/icons/ListAlt'; 14 | import QueueOutlinedIcon from '@material-ui/icons/QueueOutlined'; 15 | import RadioButtonUncheckedIcon from '@material-ui/icons/RadioButtonUnchecked'; 16 | import TabIcon from '@material-ui/icons/Tab'; 17 | import TextFieldsIcon from '@material-ui/icons/TextFields'; 18 | import React from 'react'; 19 | 20 | import { ARRAY, OBJECT, PRIMITIVE, SchemaElementType } from '../model'; 21 | 22 | export const VerticalIcon = Height; 23 | export const HorizontalIcon = styled(Height)({ 24 | transform: 'rotate(90deg)', 25 | }); 26 | export const GroupIcon = CropFreeIcon; 27 | export const CategorizationIcon = TabIcon; 28 | export const CategoryIcon = CropFreeIcon; 29 | 30 | export const LabelIcon = TextFieldsIcon; 31 | 32 | export const ControlIcon = InsertLinkIcon; 33 | export const ObjectIcon = ListAltIcon; 34 | export const ArrayIcon = QueueOutlinedIcon; 35 | export const PrimitiveIcon = LabelOutlinedIcon; 36 | export const OtherIcon = RadioButtonUncheckedIcon; 37 | 38 | export const getIconForSchemaType = (type: SchemaElementType) => { 39 | switch (type) { 40 | case OBJECT: 41 | return ObjectIcon; 42 | case ARRAY: 43 | return ArrayIcon; 44 | case PRIMITIVE: 45 | return PrimitiveIcon; 46 | default: 47 | return OtherIcon; 48 | } 49 | }; 50 | 51 | export const getIconForUISchemaType = (type: string) => { 52 | switch (type) { 53 | case 'HorizontalLayout': 54 | return HorizontalIcon; 55 | case 'VerticalLayout': 56 | return VerticalIcon; 57 | case 'Group': 58 | return GroupIcon; 59 | case 'Category': 60 | return CategoryIcon; 61 | case 'Categorization': 62 | return CategorizationIcon; 63 | case 'Control': 64 | return ControlIcon; 65 | case 'Label': 66 | return LabelIcon; 67 | default: 68 | return OtherIcon; 69 | } 70 | }; 71 | 72 | interface UISchemaIconProps { 73 | type: string; 74 | } 75 | export const UISchemaIcon: React.FC = ({ type }) => { 76 | return React.createElement(getIconForUISchemaType(type), {}); 77 | }; 78 | 79 | interface SchemaIconProps { 80 | type: SchemaElementType; 81 | } 82 | export const SchemaIcon: React.FC = ({ type }) => { 83 | return React.createElement(getIconForSchemaType(type), {}); 84 | }; 85 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/icons/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export * from './icons'; 9 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/jsonschema/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import rule from './specification/rule.json'; 9 | import schema from './specification/schema.json'; 10 | 11 | interface SchemaInformation { 12 | uri: string; 13 | schema: any; 14 | } 15 | 16 | export const jsonSchemaDraft7 = { 17 | uri: 'http://json-schema.org/draft-07/schema', 18 | schema: schema, 19 | }; 20 | 21 | export const ruleSchema = { 22 | uri: 'http://jsonforms.io/uischema/rule', 23 | schema: rule, 24 | }; 25 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/jsonschema/specification/rule.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "http://jsonforms.io/uischema/rule", 4 | "title": "Rule UI schema meta-schema", 5 | "type": ["object"], 6 | "properties": { 7 | "effect": { 8 | "description": "The effect of the rule", 9 | "type": "string", 10 | "enum": ["HIDE", "SHOW", "ENABLE", "DISABLE"] 11 | }, 12 | "condition": { 13 | "description": "The condition of the rule that must evaluate to true in order to trigger the effect.", 14 | "oneOf": [ 15 | { "$ref": "#/definitions/leafCondition" }, 16 | { "$ref": "#/definitions/schemaBasedCondition" }, 17 | { "$ref": "#/definitions/orCondition" }, 18 | { "$ref": "#/definitions/andCondition" } 19 | ] 20 | } 21 | }, 22 | "required": ["effect", "condition"], 23 | "definitions": { 24 | "condition": { 25 | "type": "object", 26 | "properties": { "type": { "type": "string", "readOnly": true } } 27 | }, 28 | "scopable": { 29 | "type": "object", 30 | "properties": { 31 | "scope": { "type": "string" } 32 | }, 33 | "required": ["scope"] 34 | }, 35 | "leafCondition": { 36 | "allOf": [ 37 | { "$ref": "#/definitions/scopable" }, 38 | { 39 | "properties": { 40 | "type": { "const": "LEAF" }, 41 | "expectedValue": {} 42 | }, 43 | "required": ["type", "expectedValue"] 44 | } 45 | ] 46 | }, 47 | "schemaBasedCondition": { 48 | "allOf": [ 49 | { "$ref": "#/definitions/scopable" }, 50 | { "$ref": "#/definitions/condition" }, 51 | { 52 | "properties": { 53 | "schema": { "$ref": "http://json-schema.org/draft-07/schema#" } 54 | }, 55 | "required": ["schema"] 56 | } 57 | ] 58 | }, 59 | "composableCondition": { 60 | "allOf": [ 61 | { "$ref": "#/definitions/condition" }, 62 | { 63 | "properties": { 64 | "conditions": { 65 | "type": "array", 66 | "items": { "$ref": "#/definitions/condition" } 67 | } 68 | }, 69 | "required": ["conditions"] 70 | } 71 | ] 72 | }, 73 | "orCondition": { 74 | "allOf": [ 75 | { "$ref": "#/definitions/composableCondition" }, 76 | { 77 | "properties": { 78 | "type": { "const": "OR" } 79 | }, 80 | "required": ["type"] 81 | } 82 | ] 83 | }, 84 | "andCondition": { 85 | "allOf": [ 86 | { "$ref": "#/definitions/composableCondition" }, 87 | { 88 | "properties": { 89 | "type": { "const": "AND" } 90 | }, 91 | "required": ["type"] 92 | } 93 | ] 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/jsonschema/specification/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "$id": "http://json-schema.org/draft-07/schema#", 4 | "title": "Core schema meta-schema", 5 | "definitions": { 6 | "schemaArray": { 7 | "type": "array", 8 | "minItems": 1, 9 | "items": { "$ref": "#" } 10 | }, 11 | "nonNegativeInteger": { 12 | "type": "integer", 13 | "minimum": 0 14 | }, 15 | "nonNegativeIntegerDefault0": { 16 | "allOf": [ 17 | { "$ref": "#/definitions/nonNegativeInteger" }, 18 | { "default": 0 } 19 | ] 20 | }, 21 | "simpleTypes": { 22 | "enum": [ 23 | "array", 24 | "boolean", 25 | "integer", 26 | "null", 27 | "number", 28 | "object", 29 | "string" 30 | ] 31 | }, 32 | "stringArray": { 33 | "type": "array", 34 | "items": { "type": "string" }, 35 | "uniqueItems": true, 36 | "default": [] 37 | } 38 | }, 39 | "type": ["object", "boolean"], 40 | "properties": { 41 | "$id": { 42 | "type": "string", 43 | "format": "uri-reference" 44 | }, 45 | "$schema": { 46 | "type": "string", 47 | "format": "uri" 48 | }, 49 | "$ref": { 50 | "type": "string", 51 | "format": "uri-reference" 52 | }, 53 | "$comment": { 54 | "type": "string" 55 | }, 56 | "title": { 57 | "type": "string" 58 | }, 59 | "description": { 60 | "type": "string" 61 | }, 62 | "default": true, 63 | "readOnly": { 64 | "type": "boolean", 65 | "default": false 66 | }, 67 | "writeOnly": { 68 | "type": "boolean", 69 | "default": false 70 | }, 71 | "examples": { 72 | "type": "array", 73 | "items": true 74 | }, 75 | "multipleOf": { 76 | "type": "number", 77 | "exclusiveMinimum": 0 78 | }, 79 | "maximum": { 80 | "type": "number" 81 | }, 82 | "exclusiveMaximum": { 83 | "type": "number" 84 | }, 85 | "minimum": { 86 | "type": "number" 87 | }, 88 | "exclusiveMinimum": { 89 | "type": "number" 90 | }, 91 | "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, 92 | "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, 93 | "pattern": { 94 | "type": "string", 95 | "format": "regex" 96 | }, 97 | "additionalItems": { "$ref": "#" }, 98 | "items": { 99 | "anyOf": [{ "$ref": "#" }, { "$ref": "#/definitions/schemaArray" }], 100 | "default": true 101 | }, 102 | "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, 103 | "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, 104 | "uniqueItems": { 105 | "type": "boolean", 106 | "default": false 107 | }, 108 | "contains": { "$ref": "#" }, 109 | "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, 110 | "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, 111 | "required": { "$ref": "#/definitions/stringArray" }, 112 | "additionalProperties": { "$ref": "#" }, 113 | "definitions": { 114 | "type": "object", 115 | "additionalProperties": { "$ref": "#" }, 116 | "default": {} 117 | }, 118 | "properties": { 119 | "type": "object", 120 | "additionalProperties": { "$ref": "#" }, 121 | "default": {} 122 | }, 123 | "patternProperties": { 124 | "type": "object", 125 | "additionalProperties": { "$ref": "#" }, 126 | "propertyNames": { "format": "regex" }, 127 | "default": {} 128 | }, 129 | "dependencies": { 130 | "type": "object", 131 | "additionalProperties": { 132 | "anyOf": [{ "$ref": "#" }, { "$ref": "#/definitions/stringArray" }] 133 | } 134 | }, 135 | "propertyNames": { "$ref": "#" }, 136 | "const": true, 137 | "enum": { 138 | "type": "array", 139 | "items": true, 140 | "minItems": 1, 141 | "uniqueItems": true 142 | }, 143 | "type": { 144 | "anyOf": [ 145 | { "$ref": "#/definitions/simpleTypes" }, 146 | { 147 | "type": "array", 148 | "items": { "$ref": "#/definitions/simpleTypes" }, 149 | "minItems": 1, 150 | "uniqueItems": true 151 | } 152 | ] 153 | }, 154 | "format": { "type": "string" }, 155 | "contentMediaType": { "type": "string" }, 156 | "contentEncoding": { "type": "string" }, 157 | "if": { "$ref": "#" }, 158 | "then": { "$ref": "#" }, 159 | "else": { "$ref": "#" }, 160 | "allOf": { "$ref": "#/definitions/schemaArray" }, 161 | "anyOf": { "$ref": "#/definitions/schemaArray" }, 162 | "oneOf": { "$ref": "#/definitions/schemaArray" }, 163 | "not": { "$ref": "#" } 164 | }, 165 | "default": true 166 | } 167 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/model/actions.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { EditorUISchemaElement } from './uischema'; 9 | 10 | export type UiSchemaAction = AddUnscopedElementToLayout | UpdateUiSchemaElement; 11 | 12 | export type CombinedAction = 13 | | SetUiSchemaAction 14 | | SetSchemaAction 15 | | SetSchemasAction 16 | | AddScopedElementToLayout 17 | | MoveUiSchemaElement 18 | | RemoveUiSchemaElement 19 | | AddDetail; 20 | 21 | export type EditorAction = UiSchemaAction | CombinedAction; 22 | 23 | export const SET_SCHEMA: 'jsonforms-editor/SET_SCHEMA' = 24 | 'jsonforms-editor/SET_SCHEMA'; 25 | export const SET_UISCHEMA: 'jsonforms-editor/SET_UISCHEMA' = 26 | 'jsonforms-editor/SET_UISCHEMA'; 27 | export const SET_SCHEMAS: 'jsonforms-editor/SET_SCHEMAS' = 28 | 'jsonforms-editor/SET_SCHEMAS'; 29 | export const ADD_SCOPED_ELEMENT_TO_LAYOUT: 'jsonforms-editor/ADD_SCOPED_ELEMENT_TO_LAYOUT' = 30 | 'jsonforms-editor/ADD_SCOPED_ELEMENT_TO_LAYOUT'; 31 | export const ADD_UNSCOPED_ELEMENT_TO_LAYOUT: 'jsonforms-editor/ADD_UNSCOPED_ELEMENT_TO_LAYOUT' = 32 | 'jsonforms-editor/ADD_UNSCOPED_ELEMENT_TO_LAYOUT'; 33 | export const MOVE_UISCHEMA_ELEMENT: 'jsonforms-editor/MOVE_UISCHEMA_ELEMENT' = 34 | 'jsonforms-editor/MOVE_UISCHEMA_ELEMENT'; 35 | export const REMOVE_UISCHEMA_ELEMENT: 'jsonforms-editor/REMOVE_UISCHEMA_ELEMENT' = 36 | 'jsonforms-editor/REMOVE_UISCHEMA_ELEMENT'; 37 | export const UPDATE_UISCHEMA_ELEMENT: 'jsonforms-editor/UPDATE_UISCHEMA_ELEMENT' = 38 | 'jsonforms-editor/UPDATE_UISCHEMA_ELEMENT'; 39 | export const ADD_DETAIL: 'jsonforms-editor/ADD_DETAIL' = 40 | 'jsonforms-editor/ADD_DETAIL'; 41 | 42 | export interface SetSchemaAction { 43 | type: 'jsonforms-editor/SET_SCHEMA'; 44 | schema: any; 45 | } 46 | 47 | export interface SetUiSchemaAction { 48 | type: 'jsonforms-editor/SET_UISCHEMA'; 49 | uiSchema: any; 50 | } 51 | 52 | export interface SetSchemasAction { 53 | type: 'jsonforms-editor/SET_SCHEMAS'; 54 | schema: any; 55 | uiSchema: any; 56 | } 57 | 58 | export interface AddScopedElementToLayout { 59 | type: 'jsonforms-editor/ADD_SCOPED_ELEMENT_TO_LAYOUT'; 60 | uiSchemaElement: EditorUISchemaElement; 61 | layoutUUID: string; 62 | schemaUUID: string; 63 | index: number; 64 | } 65 | 66 | export interface AddUnscopedElementToLayout { 67 | type: 'jsonforms-editor/ADD_UNSCOPED_ELEMENT_TO_LAYOUT'; 68 | uiSchemaElement: EditorUISchemaElement; 69 | layoutUUID: string; 70 | index: number; 71 | } 72 | 73 | export interface MoveUiSchemaElement { 74 | type: 'jsonforms-editor/MOVE_UISCHEMA_ELEMENT'; 75 | elementUUID: string; 76 | newContainerUUID: string; 77 | index: number; 78 | schemaUUID?: string; 79 | } 80 | 81 | export interface RemoveUiSchemaElement { 82 | type: 'jsonforms-editor/REMOVE_UISCHEMA_ELEMENT'; 83 | elementUUID: string; 84 | } 85 | 86 | export interface UpdateUiSchemaElement { 87 | type: 'jsonforms-editor/UPDATE_UISCHEMA_ELEMENT'; 88 | elementUUID: string; 89 | changedProperties: { [key: string]: any }; 90 | } 91 | 92 | export interface AddDetail { 93 | type: 'jsonforms-editor/ADD_DETAIL'; 94 | uiSchemaElementId: string; 95 | detail: EditorUISchemaElement; 96 | } 97 | 98 | const setSchema = (schema: any) => ({ 99 | type: SET_SCHEMA, 100 | schema, 101 | }); 102 | 103 | const setUiSchema = (uiSchema: any) => ({ 104 | type: SET_UISCHEMA, 105 | uiSchema, 106 | }); 107 | 108 | const setSchemas = (schema: any, uiSchema: any) => ({ 109 | type: SET_SCHEMAS, 110 | schema, 111 | uiSchema, 112 | }); 113 | 114 | const addScopedElementToLayout = ( 115 | uiSchemaElement: EditorUISchemaElement, 116 | layoutUUID: string, 117 | index: number, 118 | schemaUUID: string 119 | ) => ({ 120 | type: ADD_SCOPED_ELEMENT_TO_LAYOUT, 121 | uiSchemaElement, 122 | layoutUUID, 123 | index, 124 | schemaUUID, 125 | }); 126 | 127 | const addUnscopedElementToLayout = ( 128 | uiSchemaElement: EditorUISchemaElement, 129 | layoutUUID: string, 130 | index: number 131 | ) => ({ 132 | type: ADD_UNSCOPED_ELEMENT_TO_LAYOUT, 133 | uiSchemaElement, 134 | layoutUUID, 135 | index, 136 | }); 137 | 138 | const moveUiSchemaElement = ( 139 | elementUUID: string, 140 | newContainerUUID: string, 141 | index: number, 142 | schemaUUID?: string 143 | ) => ({ 144 | type: MOVE_UISCHEMA_ELEMENT, 145 | elementUUID, 146 | newContainerUUID, 147 | index, 148 | schemaUUID, 149 | }); 150 | 151 | const removeUiSchemaElement = (elementUUID: string) => ({ 152 | type: REMOVE_UISCHEMA_ELEMENT, 153 | elementUUID, 154 | }); 155 | 156 | const updateUISchemaElement = ( 157 | elementUUID: string, 158 | changedProperties: { [key: string]: any } 159 | ) => ({ type: UPDATE_UISCHEMA_ELEMENT, elementUUID, changedProperties }); 160 | 161 | const addDetail = ( 162 | uiSchemaElementId: string, 163 | detail: EditorUISchemaElement 164 | ) => ({ 165 | type: ADD_DETAIL, 166 | uiSchemaElementId, 167 | detail, 168 | }); 169 | 170 | export const Actions = { 171 | setSchema, 172 | setUiSchema, 173 | setSchemas, 174 | addScopedElementToLayout, 175 | addUnscopedElementToLayout, 176 | moveUiSchemaElement, 177 | removeUiSchemaElement, 178 | updateUISchemaElement, 179 | addDetail, 180 | }; 181 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/model/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export * from './schema'; 9 | export * from './uischema'; 10 | export * from './reducer'; 11 | export { Actions } from './actions'; 12 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/model/schema.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | 9 | import { 10 | buildSchemaTree, 11 | getArrayContainer, 12 | getChildren, 13 | SchemaElement, 14 | } from './schema'; 15 | 16 | test('set uuids on single element', () => { 17 | const element = simplePrimitive(); 18 | const enrichedElement = buildSchemaTree(element); 19 | expect(enrichedElement).toHaveProperty('uuid'); 20 | }); 21 | 22 | test('set uuids on nested elements', () => { 23 | const object = simpleObject(); 24 | const enrichedObject = buildSchemaTree(object) as SchemaElement; 25 | expect(enrichedObject).toHaveProperty('uuid'); 26 | const children = getChildren(enrichedObject); 27 | expect(children.length).toBe(2); 28 | children.forEach((child) => { 29 | expect(child).toHaveProperty('uuid'); 30 | }); 31 | }); 32 | 33 | test('getArrayContainer', () => { 34 | const array = simpleArray(); 35 | (array as any).items.properties.nestedArray = simpleArray(); 36 | 37 | const enrichedArray = buildSchemaTree(array) as SchemaElement; 38 | expect(enrichedArray).toBeTruthy(); 39 | expect(getArrayContainer(enrichedArray!)).toBeFalsy(); 40 | 41 | const arrayChildren = getChildren(enrichedArray); 42 | expect(arrayChildren.length).toBe(1); 43 | 44 | const object = arrayChildren[0]; 45 | expect(getArrayContainer(object)).toBe(enrichedArray); 46 | 47 | const objectChildren = getChildren(object); 48 | expect(objectChildren.length).toBe(3); 49 | objectChildren.forEach((child) => { 50 | expect(getArrayContainer(child)).toBe(enrichedArray); 51 | }); 52 | 53 | const nestedArray = objectChildren[2]; 54 | 55 | const nestedArrayChildren = getChildren(nestedArray); 56 | expect(nestedArrayChildren.length).toBe(1); 57 | 58 | const nestedArrayObject = nestedArrayChildren[0]; 59 | expect(getArrayContainer(nestedArrayObject)).toBe(nestedArray); 60 | 61 | getChildren(nestedArrayObject).forEach((child) => { 62 | expect(getArrayContainer(child)).toBe(nestedArray); 63 | }); 64 | }); 65 | 66 | const simplePrimitive = () => ({ 67 | type: 'string', 68 | }); 69 | 70 | const simpleObject = () => ({ 71 | type: 'object', 72 | properties: { 73 | name: simplePrimitive(), 74 | surname: simplePrimitive(), 75 | }, 76 | }); 77 | 78 | const simpleArray = () => ({ 79 | type: 'array', 80 | items: simpleObject(), 81 | }); 82 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/model/uischema.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { ControlElement } from '@jsonforms/core'; 9 | 10 | import { 11 | createControlWithScope, 12 | createLayout, 13 | } from '../util/generators/uiSchema'; 14 | import { getRoot } from '../util/schemasUtil'; 15 | import { 16 | buildEditorUiSchemaTree, 17 | containsControls, 18 | EditorControl, 19 | EditorLayout, 20 | getDetailContainer, 21 | } from './uischema'; 22 | 23 | test('set uuids on single element', () => { 24 | const element = simpleControl(); 25 | const enrichedElement = buildEditorUiSchemaTree(element); 26 | expect(enrichedElement).toHaveProperty('uuid'); 27 | }); 28 | 29 | test('set uuids on nested elements', () => { 30 | const layout = simpleLayout(); 31 | const enrichedLayout = buildEditorUiSchemaTree(layout) as EditorLayout; 32 | expect(enrichedLayout).toHaveProperty('uuid'); 33 | expect(enrichedLayout.elements[0]).toHaveProperty('uuid'); 34 | expect(enrichedLayout.elements[1]).toHaveProperty('uuid'); 35 | }); 36 | 37 | test('set uuids on detail', () => { 38 | const controlWithDetail = simpleControl(); 39 | controlWithDetail.options = { detail: simpleLayout() }; 40 | const enrichedLayout = buildEditorUiSchemaTree( 41 | controlWithDetail 42 | ) as EditorLayout; 43 | expect(enrichedLayout).toHaveProperty('uuid'); 44 | expect(enrichedLayout.options!.detail.elements[0]).toHaveProperty('uuid'); 45 | expect(enrichedLayout.options!.detail.elements[1]).toHaveProperty('uuid'); 46 | }); 47 | 48 | test('set parent on detail', () => { 49 | const controlWithDetail = simpleControl(); 50 | controlWithDetail.options = { detail: simpleLayout() }; 51 | const enrichedLayout = buildEditorUiSchemaTree( 52 | controlWithDetail 53 | ) as EditorLayout; 54 | expect(getRoot(enrichedLayout.options!.detail)).toBe(enrichedLayout); 55 | expect(getRoot(enrichedLayout.options!.detail.elements[0])).toBe( 56 | enrichedLayout 57 | ); 58 | }); 59 | 60 | test('isInDetail', () => { 61 | const controlWithDetail = simpleControl(); 62 | controlWithDetail.options = { detail: simpleLayout() }; 63 | const enrichedControlWithDetail = buildEditorUiSchemaTree( 64 | controlWithDetail 65 | ) as EditorControl; 66 | expect(enrichedControlWithDetail).toBeDefined(); 67 | expect(getDetailContainer(enrichedControlWithDetail)).toBeFalsy(); 68 | expect(getDetailContainer(enrichedControlWithDetail.options!.detail)).toBe( 69 | enrichedControlWithDetail 70 | ); 71 | expect( 72 | getDetailContainer(enrichedControlWithDetail.options!.detail.elements[0]) 73 | ).toBe(enrichedControlWithDetail); 74 | }); 75 | 76 | test('containsControls', () => { 77 | expect(containsControls(simpleEditorControl())).toBeTruthy(); 78 | const layout = simpleEditorLayout(); 79 | expect(containsControls(layout)).toBeTruthy(); 80 | layout.elements = []; 81 | expect(containsControls(layout)).toBeFalsy(); 82 | }); 83 | 84 | const simpleControl = (): ControlElement => ({ 85 | type: 'Control', 86 | scope: '#', 87 | }); 88 | 89 | const simpleLayout = () => ({ 90 | type: 'VerticalLayout', 91 | elements: [simpleControl(), simpleControl()], 92 | }); 93 | 94 | const simpleEditorControl = () => createControlWithScope('#'); 95 | 96 | const simpleEditorLayout = (): EditorLayout => { 97 | const layout = createLayout('VerticalLayout') as EditorLayout; 98 | layout.elements = [simpleEditorControl(), simpleEditorControl()]; 99 | return layout; 100 | }; 101 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/model/uischema.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { 9 | Categorization, 10 | Category, 11 | ControlElement, 12 | isLayout, 13 | Layout, 14 | UISchemaElement, 15 | } from '@jsonforms/core'; 16 | import { cloneDeep } from 'lodash'; 17 | import { v4 as uuid } from 'uuid'; 18 | 19 | import { 20 | calculatePath, 21 | getRoot, 22 | isEditorControl, 23 | isEditorLayout, 24 | isPathError, 25 | PathError, 26 | traverse, 27 | } from '../util/schemasUtil'; 28 | import { getHierarchy, TreeElement } from '../util/tree'; 29 | 30 | export interface EditorUISchemaElement 31 | extends UISchemaElement, 32 | TreeElement { 33 | linkedSchemaElement?: string; 34 | } 35 | 36 | export interface EditorCategoryElement extends Category, EditorUISchemaElement { 37 | type: 'Category'; 38 | } 39 | 40 | export interface CategorizationLayout 41 | extends Categorization, 42 | EditorUISchemaElement { 43 | type: 'Categorization'; 44 | elements: EditorCategoryElement[]; 45 | } 46 | 47 | export interface EditorControl extends ControlElement, EditorUISchemaElement { 48 | type: 'Control'; 49 | } 50 | 51 | export interface EditorLayout extends Layout, EditorUISchemaElement { 52 | elements: EditorUISchemaElement[]; 53 | } 54 | 55 | export const getUiSchemaChildren = ( 56 | schemaElement: EditorUISchemaElement 57 | ): Array => { 58 | const children: Array = []; 59 | if (isEditorLayout(schemaElement)) { 60 | children.push(...schemaElement.elements); 61 | } 62 | return children; 63 | }; 64 | 65 | export const hasChildren = (schemaElement: EditorUISchemaElement): boolean => { 66 | return isLayout(schemaElement) && !!(schemaElement as Layout).elements.length; 67 | }; 68 | 69 | /** 70 | * Creates a copy of the given ui schema enriched with editor fields 71 | * like 'parent' and 'linked schema elements'. 72 | */ 73 | export const buildEditorUiSchemaTree = ( 74 | uiSchema: UISchemaElement 75 | ): EditorUISchemaElement => { 76 | // cast to any so we can freely modify it 77 | const editorUiSchema: any = cloneDeep(uiSchema); 78 | traverse(editorUiSchema, (current, parent) => { 79 | if (current) { 80 | current.parent = parent; 81 | current.uuid = uuid(); 82 | } 83 | }); 84 | return editorUiSchema; 85 | }; 86 | 87 | /** 88 | * Creates a copy of the given enriched ui schema and removes all editor 89 | * related fields. 90 | */ 91 | export const buildUiSchema = ( 92 | uiSchema: EditorUISchemaElement 93 | ): UISchemaElement => { 94 | const clone: EditorUISchemaElement = cloneDeep(uiSchema); 95 | traverse(clone, (current) => { 96 | delete current.parent; 97 | delete current.linkedSchemaElement; 98 | delete current.uuid; 99 | }); 100 | return clone; 101 | }; 102 | 103 | export const buildDebugUISchema = ( 104 | uiSchema: EditorUISchemaElement 105 | ): UISchemaElement => { 106 | const clone: any = cloneDeep(uiSchema); 107 | traverse(clone, (current) => { 108 | current.parent = current.parent?.uuid; 109 | }); 110 | return clone; 111 | }; 112 | 113 | export const getUISchemaPath = ( 114 | uiSchema: EditorUISchemaElement 115 | ): string | PathError => { 116 | const root = getRoot(uiSchema); 117 | const path = calculatePath(root, uiSchema); 118 | if (isPathError(path)) { 119 | return path; 120 | } 121 | // TODO should be done in a cleaner way 122 | return `/${path.join('/')}`; 123 | }; 124 | 125 | /** 126 | * Returns the closes element whose detail contains the given element 127 | */ 128 | export const getDetailContainer = ( 129 | element: EditorUISchemaElement 130 | ): EditorUISchemaElement | undefined => { 131 | const parentIsDetail = (el: EditorUISchemaElement) => 132 | el.parent?.options?.detail?.uuid === el.uuid; 133 | 134 | return getHierarchy(element).find(parentIsDetail)?.parent; 135 | }; 136 | 137 | /** 138 | * Indicates whether the given ui schema element is a control or contains controls 139 | */ 140 | export const containsControls = (element: EditorUISchemaElement): boolean => 141 | traverse( 142 | element, 143 | (el, _parent, acc) => { 144 | if (isEditorControl(el)) { 145 | acc.containsControls = true; 146 | } 147 | }, 148 | { containsControls: false } 149 | ).containsControls; 150 | 151 | export const cleanUiSchemaLinks = ( 152 | element: EditorUISchemaElement | undefined 153 | ): EditorUISchemaElement | undefined => { 154 | if (!element) { 155 | return element; 156 | } 157 | traverse(element, (current) => { 158 | delete current.linkedSchemaElement; 159 | return current; 160 | }); 161 | return element; 162 | }; 163 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/renderers/DroppableArrayControl.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | 9 | import { 10 | ArrayControlProps, 11 | isObjectArrayControl, 12 | rankWith, 13 | } from '@jsonforms/core'; 14 | import { 15 | JsonFormsDispatch, 16 | withJsonFormsArrayControlProps, 17 | } from '@jsonforms/react'; 18 | import { makeStyles, Typography } from '@material-ui/core'; 19 | import React, { useMemo } from 'react'; 20 | import { useDrop } from 'react-dnd'; 21 | 22 | import { useDispatch, useSchema } from '../context'; 23 | import { 24 | canDropIntoScope, 25 | MOVE_UI_SCHEMA_ELEMENT, 26 | NEW_UI_SCHEMA_ELEMENT, 27 | NewUISchemaElement, 28 | } from '../dnd'; 29 | import { Actions } from '../model'; 30 | import { containsControls, EditorControl } from '../model/uischema'; 31 | import { DroppableElementRegistration } from './DroppableElement'; 32 | 33 | interface StyleProps { 34 | isOver: boolean; 35 | } 36 | 37 | const useStyles = makeStyles({ 38 | root: ({ isOver }: StyleProps) => ({ 39 | padding: 10, 40 | fontSize: isOver ? '1.1em' : '1em', 41 | border: isOver ? '1px solid #D3D3D3' : 'none', 42 | }), 43 | }); 44 | 45 | interface DroppableArrayControlProps extends ArrayControlProps { 46 | uischema: EditorControl; 47 | } 48 | const DroppableArrayControl: React.FC = ({ 49 | uischema, 50 | schema, 51 | path, 52 | renderers, 53 | cells, 54 | }) => { 55 | const dispatch = useDispatch(); 56 | const rootSchema = useSchema(); 57 | const [{ isOver, uiSchemaElement }, drop] = useDrop({ 58 | accept: [NEW_UI_SCHEMA_ELEMENT, MOVE_UI_SCHEMA_ELEMENT], 59 | canDrop: (item): boolean => { 60 | switch (item.type) { 61 | case NEW_UI_SCHEMA_ELEMENT: 62 | return canDropIntoScope( 63 | item as NewUISchemaElement, 64 | rootSchema, 65 | uischema 66 | ); 67 | case MOVE_UI_SCHEMA_ELEMENT: 68 | // move as a new detail is only allowed when there are no controls 69 | return !containsControls(uiSchemaElement); 70 | } 71 | // fallback 72 | return false; 73 | }, 74 | collect: (mon) => ({ 75 | isOver: !!mon.isOver() && mon.canDrop(), 76 | uiSchemaElement: mon.getItem()?.uiSchemaElement, 77 | }), 78 | drop: (item): void => { 79 | switch (item.type) { 80 | case NEW_UI_SCHEMA_ELEMENT: 81 | dispatch(Actions.addDetail(uischema.uuid, uiSchemaElement)); 82 | break; 83 | case MOVE_UI_SCHEMA_ELEMENT: 84 | dispatch( 85 | Actions.moveUiSchemaElement(uiSchemaElement.uuid, uischema.uuid, 0) 86 | ); 87 | break; 88 | } 89 | }, 90 | }); 91 | const classes = useStyles({ isOver }); 92 | 93 | // DroppableControl removed itself before dispatching to us, we need 94 | // to re-add it for our children 95 | const renderersToUse = useMemo(() => { 96 | return renderers && [...renderers, DroppableElementRegistration]; 97 | }, [renderers]); 98 | 99 | if (!uischema.options?.detail) { 100 | return ( 101 | 102 | Default array layout. Drag and drop an item here to customize array 103 | layout. 104 | 105 | ); 106 | } 107 | return ( 108 | 115 | ); 116 | }; 117 | 118 | export const DroppableArrayControlRegistration = { 119 | tester: rankWith(40, isObjectArrayControl), // less than DroppableElement 120 | renderer: withJsonFormsArrayControlProps( 121 | DroppableArrayControl as React.FC 122 | ), 123 | }; 124 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/renderers/DroppableCategorizationLayout.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | 9 | import { 10 | Category, 11 | isCategorization, 12 | rankWith, 13 | StatePropsOfLayout, 14 | } from '@jsonforms/core'; 15 | import { JsonFormsDispatch, withJsonFormsLayoutProps } from '@jsonforms/react'; 16 | import { 17 | AppBar, 18 | Card, 19 | CardContent, 20 | CardHeader, 21 | Tab, 22 | Tabs, 23 | } from '@material-ui/core'; 24 | import { PlusOne, Tab as TabIcon } from '@material-ui/icons'; 25 | import { findIndex } from 'lodash'; 26 | import React, { useMemo, useState } from 'react'; 27 | 28 | import { useCategorizationService, useSelection } from '../../core/context'; 29 | import { CategorizationLayout } from '../model/uischema'; 30 | import { createCategory } from '../util/generators/uiSchema'; 31 | import { DroppableElementRegistration } from './DroppableElement'; 32 | 33 | interface DroppableCategorizationLayoutProps extends StatePropsOfLayout { 34 | uischema: CategorizationLayout; 35 | } 36 | 37 | const DroppableCategorizationLayout: React.FC = 38 | (props) => { 39 | const { uischema, schema, path, renderers, cells } = props; 40 | 41 | // ignoring the first selection from the tuple since it is not used 42 | const [, setSelection] = useSelection(); 43 | const categorizationService = useCategorizationService(); 44 | 45 | const categories = uischema.elements; 46 | 47 | const defaultIndex = findIndex( 48 | categories, 49 | (cat) => 50 | cat.uuid === categorizationService.getTabSelection(uischema)?.uuid 51 | ); 52 | 53 | const [currentIndex, setCurrentIndex] = useState( 54 | defaultIndex === -1 ? undefined : defaultIndex 55 | ); 56 | 57 | const indicatorColor: 'secondary' | 'primary' | undefined = 58 | categories.length === 0 ? 'primary' : 'secondary'; 59 | 60 | const setIndex = (value: number, event?: any) => { 61 | event?.stopPropagation(); 62 | if (value < categories.length) { 63 | const selectedUuid = categories[value].uuid; 64 | 65 | categorizationService.setTabSelection(uischema, { 66 | uuid: selectedUuid, 67 | }); 68 | setSelection({ uuid: selectedUuid }); 69 | setCurrentIndex(value); 70 | } 71 | }; 72 | 73 | // DroppableControl removed itself before dispatching to us, we need 74 | // to re-add it for our children 75 | const renderersToUse = useMemo(() => { 76 | return renderers && [...renderers, DroppableElementRegistration]; 77 | }, [renderers]); 78 | 79 | const handleChange = (event: any, value: any) => { 80 | if (typeof value === 'number') { 81 | setIndex(value, event); 82 | } 83 | }; 84 | 85 | const addTab = (event: any) => { 86 | const tab = createCategory('New Tab ' + (categories.length + 1)); 87 | tab.parent = uischema; 88 | 89 | categories.push(tab); 90 | setIndex(categories.length - 1, event); 91 | }; 92 | 93 | if (currentIndex !== undefined) { 94 | // in case we have tab that was deleted then we will use the memorized index to determine the previous tab that we are going to select automatically 95 | 96 | if (categories.length === 0) { 97 | // reset the index since we do not have anything to select 98 | setCurrentIndex(undefined); 99 | } else if (currentIndex > categories.length - 1) { 100 | // check if currentIndex is out of bound because of delete 101 | setIndex(categories.length - 1); 102 | } else if (currentIndex !== defaultIndex) { 103 | // check if current index is out of sync with the service 104 | setIndex(currentIndex); 105 | } 106 | } 107 | 108 | return ( 109 | 110 | ( 112 | 113 | 119 | {categories.map((e: Category, idx: number) => ( 120 | 121 | ))} 122 | 126 | 127 | 128 | 129 | } 130 | onClick={addTab} 131 | /> 132 | 133 | 134 | )} 135 | > 136 | 137 | {categories.length > 0 && currentIndex !== undefined ? ( 138 | 145 | ) : ( 146 | categories.length === 0 && ( 147 | 148 | {'No Category. Use '} 149 | 150 | 151 | {' to add a new tab.'} 152 | 153 | ) 154 | )} 155 | 156 | 157 | ); 158 | }; 159 | 160 | export const DroppableCategorizationLayoutRegistration = { 161 | tester: rankWith(40, isCategorization), // less than DroppableElement 162 | renderer: withJsonFormsLayoutProps( 163 | DroppableCategorizationLayout as React.FC 164 | ), 165 | }; 166 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/renderers/DroppableCategoryLayout.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { Category, LayoutProps, rankWith, uiTypeIs } from '@jsonforms/core'; 9 | import { withJsonFormsLayoutProps } from '@jsonforms/react'; 10 | import { Card, CardContent } from '@material-ui/core'; 11 | import React from 'react'; 12 | 13 | import { EditorLayout } from '../model/uischema'; 14 | import { DroppableLayout } from './DroppableLayout'; 15 | 16 | const CategoryLayout: React.FC = (props) => { 17 | const { uischema } = props; 18 | const categoryLayout = uischema as Category & EditorLayout; 19 | return ( 20 | 21 | 22 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | export const DroppableCategoryLayoutRegistration = { 33 | tester: rankWith(45, uiTypeIs('Category')), 34 | renderer: withJsonFormsLayoutProps(CategoryLayout), 35 | }; 36 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/renderers/DroppableElement.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | 9 | import { ControlProps, rankWith } from '@jsonforms/core'; 10 | import { ResolvedJsonFormsDispatch } from '@jsonforms/react'; 11 | import { omit } from 'lodash'; 12 | import React, { useMemo } from 'react'; 13 | 14 | import { EditorElement } from '../../editor/components/EditorElement'; 15 | import { EditorControl } from '../model/uischema'; 16 | 17 | interface DroppableElementProps extends ControlProps { 18 | uischema: EditorControl; 19 | } 20 | const DroppableElement: React.FC = ({ 21 | uischema, 22 | schema, 23 | path, 24 | renderers, 25 | cells, 26 | }) => { 27 | const editorUiSchema = useMemo(() => omit(uischema, ['rule']), [uischema]); 28 | return ( 29 | 30 | r.renderer !== DroppableElementRenderer 36 | )} 37 | cells={cells} 38 | /> 39 | 40 | ); 41 | }; 42 | const DroppableElementRenderer = DroppableElement; 43 | export const DroppableElementRegistration = { 44 | tester: rankWith(50, () => true), 45 | renderer: DroppableElementRenderer, 46 | }; 47 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/renderers/DroppableGroupLayout.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { GroupLayout, LayoutProps, rankWith, uiTypeIs } from '@jsonforms/core'; 9 | import { withJsonFormsLayoutProps } from '@jsonforms/react'; 10 | import { 11 | Card, 12 | CardContent, 13 | CardHeader, 14 | Grid, 15 | makeStyles, 16 | Typography, 17 | } from '@material-ui/core'; 18 | import React from 'react'; 19 | 20 | import { EditorLayout } from '../model/uischema'; 21 | import { DroppableLayout } from './DroppableLayout'; 22 | 23 | const useStyles = makeStyles((theme) => ({ 24 | groupLabel: { 25 | padding: theme.spacing(2), 26 | alignItems: 'baseline', 27 | }, 28 | labelPlaceholder: { 29 | fontStyle: 'italic', 30 | fontWeight: 'lighter', 31 | color: '#9e9e9e', 32 | }, 33 | groupLabelInput: { 34 | fontSize: theme.typography.h6.fontSize, 35 | }, 36 | })); 37 | 38 | const Group: React.FC = (props) => { 39 | const { uischema } = props; 40 | const groupLayout = uischema as GroupLayout & EditorLayout; 41 | const classes = useStyles(); 42 | return ( 43 | 44 | ( 46 | 52 | 53 | Label: 54 | 55 | 56 | 62 | {groupLayout.label ?? 'no label'} 63 | 64 | 65 | 66 | )} 67 | > 68 | 69 | 70 | 71 | 72 | ); 73 | }; 74 | 75 | export const DroppableGroupLayoutRegistration = { 76 | tester: rankWith(45, uiTypeIs('Group')), 77 | renderer: withJsonFormsLayoutProps(Group), 78 | }; 79 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/selection/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export * from './selection'; 9 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/selection/selection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export type SelectedElement = { uuid: string } | undefined; 9 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/util/clipboard.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export const copyToClipBoard = (text: string) => { 9 | const textArea = document.createElement('textarea'); 10 | document.body.appendChild(textArea); 11 | textArea.value = text; 12 | textArea.select(); 13 | document.execCommand('copy'); 14 | document.body.removeChild(textArea); 15 | }; 16 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/util/clone.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { cloneDeep } from 'lodash'; 9 | 10 | // Error imports needed for declaration generation (declaration:true in tsconfig) 11 | import { findByUUID, isUUIDError, UUIDError } from './schemasUtil'; 12 | 13 | /** 14 | * Clones the whole root tree, matches the element by UUID in the new tree and returns a handle to it. 15 | * Returns an error when the clone process didn't work or the cloned root, if no uuid was provided. 16 | */ 17 | export const cloneTree = (root: T, uuid?: string): T | UUIDError => { 18 | const clonedRoot = cloneDeep(root); 19 | return uuid ? findByUUID(clonedRoot, uuid) : clonedRoot; 20 | }; 21 | 22 | export const withCloneTree = ( 23 | rootTree: T, 24 | elementUUID: string | undefined, 25 | fallback: R, 26 | process: (clonedElement: T) => R 27 | ) => { 28 | const clonedElement = cloneTree(rootTree, elementUUID); 29 | if (isUUIDError(clonedElement)) { 30 | console.error( 31 | 'An error occured when cloning element with UUID', 32 | elementUUID 33 | ); 34 | // Do nothing 35 | return fallback; 36 | } 37 | return process(clonedElement); 38 | }; 39 | 40 | /** 41 | * Convenience wrapper to clone two trees at the same time. 42 | */ 43 | export const withCloneTrees = ( 44 | rootTree1: T1, 45 | uuid1: string | undefined, 46 | rootTree2: T2, 47 | uuid2: string | undefined, 48 | fallback: R, 49 | process: (clonedElement1: T1, clonedElement2: T2) => R 50 | ) => 51 | withCloneTree(rootTree1, uuid1, fallback, (clonedElement1) => 52 | withCloneTree(rootTree2, uuid2, fallback, (clonedElement2) => 53 | process(clonedElement1, clonedElement2) 54 | ) 55 | ); 56 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/util/generators/uiSchema.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { ControlElement, LabelElement, Layout } from '@jsonforms/core'; 9 | import { v4 as uuid } from 'uuid'; 10 | 11 | import { getScope, SchemaElement } from '../../model'; 12 | import { 13 | CategorizationLayout, 14 | EditorCategoryElement, 15 | EditorUISchemaElement, 16 | } from '../../model/uischema'; 17 | 18 | export const createControl = ( 19 | schemaElement: SchemaElement 20 | ): ControlElement & EditorUISchemaElement => { 21 | return createControlWithScope(`#${getScope(schemaElement)}`); 22 | }; 23 | 24 | export const createControlWithScope = ( 25 | scope: string 26 | ): ControlElement & EditorUISchemaElement => { 27 | return { 28 | type: 'Control', 29 | scope: scope, 30 | uuid: uuid(), 31 | } as ControlElement & EditorUISchemaElement; 32 | }; 33 | 34 | export const createLayout = (type: string): Layout & EditorUISchemaElement => { 35 | return { 36 | type: type, 37 | elements: [], 38 | uuid: uuid(), 39 | } as Layout & EditorUISchemaElement; 40 | }; 41 | 42 | export const createLabel = ( 43 | text?: string 44 | ): LabelElement & EditorUISchemaElement => { 45 | return { 46 | type: 'Label', 47 | text: text, 48 | uuid: uuid(), 49 | } as LabelElement & EditorUISchemaElement; 50 | }; 51 | 52 | export const createCategory = (label?: string): EditorCategoryElement => { 53 | return { 54 | type: 'Category', 55 | elements: [], 56 | label: label, 57 | uuid: uuid(), 58 | } as EditorCategoryElement; 59 | }; 60 | 61 | export const createCategorization = (label?: string): CategorizationLayout => { 62 | return { 63 | type: 'Categorization', 64 | label: label, 65 | uuid: uuid(), 66 | elements: [], 67 | } as CategorizationLayout; 68 | }; 69 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/util/hooks.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { useCallback, useEffect, useRef, useState } from 'react'; 9 | 10 | import { useSchema, useUiSchema } from '../context'; 11 | import { buildJsonSchema, SchemaElement } from '../model'; 12 | import { buildUiSchema, EditorUISchemaElement } from '../model/uischema'; 13 | 14 | const doBuildJsonSchema = (schema: SchemaElement | undefined) => 15 | schema ? buildJsonSchema(schema) : schema; 16 | 17 | const doBuildUiSchema = (uiSchema: EditorUISchemaElement | undefined) => 18 | uiSchema ? buildUiSchema(uiSchema) : undefined; 19 | 20 | /** 21 | * Json Schema for export 22 | */ 23 | export const useExportSchema = () => { 24 | const schema = useSchema(); 25 | return useTransform(schema, doBuildJsonSchema); 26 | }; 27 | 28 | /** 29 | * Ui Schema for export 30 | */ 31 | export const useExportUiSchema = () => { 32 | const uiSchema = useUiSchema(); 33 | return useTransform(uiSchema, doBuildUiSchema); 34 | }; 35 | 36 | /** 37 | * Transforms the given element whenever it changes. 38 | */ 39 | export const useTransform = ( 40 | element: T1, 41 | transform: (el: T1) => T2 42 | ) => { 43 | const [transformedElement, setTransformedElement] = useState( 44 | transform(element) 45 | ); 46 | useEffectAfterInit( 47 | () => setTransformedElement(transform(element)), 48 | [element, transform] 49 | ); 50 | return transformedElement; 51 | }; 52 | 53 | /** 54 | * Hook similar to `useEffect` with the difference that the effect 55 | * is only executed from the second call onwards. 56 | */ 57 | const useEffectAfterInit = (effect: () => void, dependencies: Array) => { 58 | const firstExecution = useRef(true); 59 | useEffect(() => { 60 | if (firstExecution.current) { 61 | firstExecution.current = false; 62 | return; 63 | } 64 | effect(); 65 | // eslint-disable-next-line react-hooks/exhaustive-deps 66 | }, [...dependencies]); 67 | }; 68 | 69 | /** Force a rerender */ 70 | export const useUpdate = () => { 71 | const [, setCount] = useState(0); 72 | const update = useCallback(() => { 73 | setCount((count) => count + 1); 74 | }, []); 75 | return update; 76 | }; 77 | 78 | /** Executes the callback and forces a rerender whenever the callback changes */ 79 | export const useEffectWithUpdate = (effectCallback: () => void) => { 80 | const update = useUpdate(); 81 | useEffect(() => { 82 | effectCallback(); 83 | update(); 84 | }, [effectCallback, update]); 85 | }; 86 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/util/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export * from './generators/uiSchema'; 9 | export * from './clipboard'; 10 | export * from './clone'; 11 | export * from './hooks'; 12 | export * from './schemasUtil'; 13 | export * from './tree'; 14 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/util/schemasUtil.test.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { ControlElement } from '@jsonforms/core'; 9 | 10 | import { buildSchemaTree, getChildren, SchemaElement } from '../model'; 11 | import { buildEditorUiSchemaTree } from '../model/uischema'; 12 | import { linkSchemas } from './schemasUtil'; 13 | 14 | describe('build and link ui schema', () => { 15 | test('buildAndLinkUISchema should not fail for undefined parameters', () => { 16 | const state = linkSchemas(undefined, undefined); 17 | expect(state).toBeDefined(); 18 | expect(state.schema).toBeUndefined(); 19 | expect(state.uiSchema).toBeUndefined(); 20 | }); 21 | 22 | test('schema and ui schema should be linked for control', () => { 23 | const { schema, uiSchema } = linkSchemas( 24 | buildSchemaTree({ 25 | type: 'object', 26 | properties: { 27 | name: { type: 'string' }, 28 | }, 29 | }), 30 | buildEditorUiSchemaTree({ 31 | type: 'Control', 32 | scope: '#/properties/name', 33 | } as ControlElement) 34 | ); 35 | expect(schema).toBeDefined(); 36 | const nameProperty = getChildren(schema as SchemaElement)[0]; 37 | expect(nameProperty?.uuid).toBeDefined(); 38 | expect(uiSchema).toBeDefined(); 39 | expect(uiSchema?.uuid).toBeDefined(); 40 | expect(uiSchema?.linkedSchemaElement).toBe(nameProperty?.uuid); 41 | expect(nameProperty?.linkedUISchemaElements?.size).toBe(1); 42 | expect(nameProperty?.linkedUISchemaElements?.values().next().value).toBe( 43 | uiSchema?.uuid 44 | ); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /jsonforms-editor/src/core/util/tree.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export interface Parentable { 9 | parent?: T; 10 | } 11 | 12 | export interface Identifiable { 13 | uuid: string; 14 | } 15 | 16 | export interface TreeElement extends Parentable, Identifiable {} 17 | 18 | /** 19 | * Returns an array starting with the current element followed by its parents 20 | */ 21 | export const getHierarchy = >( 22 | element: T | undefined 23 | ): T[] => (!element ? [] : [element, ...getHierarchy(element.parent)]); 24 | -------------------------------------------------------------------------------- /jsonforms-editor/src/editor/components/Editor.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { JsonFormsRendererRegistryEntry } from '@jsonforms/core'; 9 | import { materialCells } from '@jsonforms/material-renderers'; 10 | import { JsonForms } from '@jsonforms/react'; 11 | import { createMuiTheme, Grid, ThemeProvider } from '@material-ui/core'; 12 | import React from 'react'; 13 | 14 | import { useUiSchema } from '../../core/context'; 15 | import { useExportSchema } from '../../core/util/hooks'; 16 | import { EmptyEditor } from './EmptyEditor'; 17 | 18 | const theme = createMuiTheme({ 19 | overrides: { 20 | MuiFormControl: { 21 | root: { 22 | overflow: 'hidden', 23 | }, 24 | }, 25 | }, 26 | }); 27 | 28 | export interface EditorProps { 29 | editorRenderers: JsonFormsRendererRegistryEntry[]; 30 | } 31 | export const Editor: React.FC = ({ editorRenderers }) => { 32 | const schema = useExportSchema(); 33 | const uiSchema = useUiSchema(); 34 | return uiSchema ? ( 35 | 36 | 37 | 44 | 45 | 46 | ) : ( 47 | 48 | ); 49 | }; 50 | -------------------------------------------------------------------------------- /jsonforms-editor/src/editor/components/EditorElement.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | 9 | import { Grid, IconButton, makeStyles, Typography } from '@material-ui/core'; 10 | import DeleteIcon from '@material-ui/icons/Delete'; 11 | import React from 'react'; 12 | import { useDrag } from 'react-dnd'; 13 | 14 | import { OkCancelDialog } from '../../core/components/OkCancelDialog'; 15 | import { useDispatch, useSchema, useSelection } from '../../core/context'; 16 | import { DndItems } from '../../core/dnd'; 17 | import { SchemaIcon, UISchemaIcon } from '../../core/icons'; 18 | import { Actions } from '../../core/model'; 19 | import { 20 | EditorUISchemaElement, 21 | getUISchemaPath, 22 | hasChildren, 23 | } from '../../core/model/uischema'; 24 | import { isEditorControl, tryFindByUUID } from '../../core/util/schemasUtil'; 25 | 26 | const useEditorElementStyles = makeStyles((theme) => ({ 27 | editorElement: { 28 | border: '1px solid #d3d3d3', 29 | padding: theme.spacing(1), 30 | opacity: 1, 31 | backgroundColor: '#fafafa', 32 | width: '100%', 33 | alignSelf: 'baseline', 34 | minWidth: 'fit-content', 35 | }, 36 | elementDragging: { 37 | opacity: 0.5, 38 | }, 39 | elementSelected: { 40 | border: '1px solid #a9a9a9', 41 | backgroundColor: 'rgba(63, 81, 181, 0.08)', 42 | }, 43 | elementHeader: { 44 | '&:hover $elementControls': { 45 | opacity: 1, 46 | }, 47 | }, 48 | elementControls: { 49 | opacity: 0, 50 | }, 51 | rule: { 52 | fontWeight: 'bolder', 53 | color: theme.palette.text.primary, 54 | marginRight: theme.spacing(0.5), 55 | marginLeft: theme.spacing(1), 56 | }, 57 | ruleEffect: { fontStyle: 'italic', color: theme.palette.text.secondary }, 58 | })); 59 | 60 | export interface EditorElementProps { 61 | wrappedElement: EditorUISchemaElement; 62 | elementIcon?: React.ReactNode; 63 | } 64 | 65 | export const EditorElement: React.FC = ({ 66 | wrappedElement, 67 | elementIcon, 68 | children, 69 | }) => { 70 | const schema = useSchema(); 71 | const [selection, setSelection] = useSelection(); 72 | const dispatch = useDispatch(); 73 | const [openConfirmRemoveDialog, setOpenConfirmRemoveDialog] = 74 | React.useState(false); 75 | const elementSchema = tryFindByUUID( 76 | schema, 77 | wrappedElement.linkedSchemaElement 78 | ); 79 | const [{ isDragging }, drag] = useDrag({ 80 | item: DndItems.moveUISchemaElement(wrappedElement, elementSchema), 81 | collect: (monitor) => ({ 82 | isDragging: !!monitor.isDragging(), 83 | }), 84 | }); 85 | const classes = useEditorElementStyles(); 86 | 87 | const uiPath = getUISchemaPath(wrappedElement); 88 | const isSelected = selection?.uuid === wrappedElement.uuid; 89 | const ruleEffect = wrappedElement.rule?.effect.toLocaleUpperCase(); 90 | 91 | const icon = 92 | elementIcon ?? 93 | (elementSchema ? ( 94 | 95 | ) : ( 96 | 97 | )); 98 | return ( 99 | { 107 | event.stopPropagation(); 108 | const newSelection = { uuid: wrappedElement.uuid }; 109 | setSelection(newSelection); 110 | }} 111 | > 112 | 120 | 121 | {icon} 122 | {ruleEffect ? ( 123 | 131 | 132 | {'R'} 133 | 134 | {`(${ruleEffect})`} 138 | 139 | ) : null} 140 | {isEditorControl(wrappedElement) && ( 141 | 149 | 150 | {wrappedElement.scope} 151 | 152 | 153 | )} 154 | 155 | 163 | { 167 | hasChildren(wrappedElement) 168 | ? setOpenConfirmRemoveDialog(true) 169 | : dispatch(Actions.removeUiSchemaElement(wrappedElement.uuid)); 170 | }} 171 | > 172 | 173 | 174 | 175 | { 179 | dispatch(Actions.removeUiSchemaElement(wrappedElement.uuid)); 180 | setOpenConfirmRemoveDialog(false); 181 | }} 182 | onCancel={() => setOpenConfirmRemoveDialog(false)} 183 | /> 184 | 185 | 186 | {children} 187 | 188 | ); 189 | }; 190 | -------------------------------------------------------------------------------- /jsonforms-editor/src/editor/components/EditorPanel.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { JsonFormsRendererRegistryEntry } from '@jsonforms/core'; 9 | import { makeStyles, Tab, Tabs } from '@material-ui/core'; 10 | import React, { useState } from 'react'; 11 | 12 | import { TabContent } from '../../core/components'; 13 | import { Editor } from './Editor'; 14 | 15 | const useStyles = makeStyles(() => ({ 16 | editorPanel: { 17 | height: '100%', 18 | display: 'grid', 19 | gridTemplateColumns: '1fr', 20 | gridTemplateRows: 'auto 1fr ', 21 | }, 22 | })); 23 | 24 | export interface EditorTab { 25 | name: string; 26 | Component: React.ComponentType; 27 | } 28 | 29 | interface EditorPanelProps { 30 | editorTabs?: EditorTab[]; 31 | editorRenderers: JsonFormsRendererRegistryEntry[]; 32 | } 33 | export const EditorPanel: React.FC = ({ 34 | editorTabs, 35 | editorRenderers, 36 | }) => { 37 | const [selectedTab, setSelectedTab] = useState(0); 38 | const handleTabChange = (event: React.ChangeEvent<{}>, newValue: number) => { 39 | setSelectedTab(newValue); 40 | }; 41 | const classes = useStyles(); 42 | return ( 43 |
44 | 45 | 46 | {editorTabs 47 | ? editorTabs.map((tab) => ( 48 | 49 | )) 50 | : null} 51 | 52 | 53 | 54 | 55 | {editorTabs 56 | ? editorTabs.map((tab, index) => ( 57 | 62 | 63 | 64 | )) 65 | : null} 66 |
67 | ); 68 | }; 69 | -------------------------------------------------------------------------------- /jsonforms-editor/src/editor/components/EditorPreview.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import React from 'react'; 9 | 10 | import { useExportSchema, useExportUiSchema } from '../../core/util/hooks'; 11 | 12 | declare global { 13 | namespace JSX { 14 | interface IntrinsicElements { 15 | 'ng-jsonforms': any; 16 | } 17 | } 18 | } 19 | 20 | export const EditorPreview: React.FC = () => { 21 | const schema = useExportSchema(); 22 | const uiSchema = useExportUiSchema(); 23 | 24 | const inputSchema = JSON.stringify(schema); 25 | const inputUISchema = JSON.stringify(uiSchema); 26 | 27 | return inputUISchema && inputSchema ? ( 28 |
29 | 33 |
34 | ) : null; 35 | }; 36 | -------------------------------------------------------------------------------- /jsonforms-editor/src/editor/components/EmptyEditor.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { Typography } from '@material-ui/core'; 9 | import { makeStyles } from '@material-ui/core/styles'; 10 | import React from 'react'; 11 | import { useDrop } from 'react-dnd'; 12 | 13 | import { useDispatch } from '../../core/context'; 14 | import { NEW_UI_SCHEMA_ELEMENT } from '../../core/dnd'; 15 | import { Actions } from '../../core/model'; 16 | 17 | const useStyles = makeStyles({ 18 | root: (props: any) => ({ 19 | padding: 10, 20 | fontSize: props.isOver ? '1.1em' : '1em', 21 | border: props.isOver ? '1px solid #D3D3D3' : 'none', 22 | height: '100%', 23 | }), 24 | }); 25 | 26 | export const EmptyEditor: React.FC = () => { 27 | const dispatch = useDispatch(); 28 | const [{ isOver, uiSchemaElement }, drop] = useDrop({ 29 | accept: NEW_UI_SCHEMA_ELEMENT, 30 | collect: (mon) => ({ 31 | isOver: !!mon.isOver(), 32 | uiSchemaElement: mon.getItem()?.uiSchemaElement, 33 | }), 34 | drop: (): any => { 35 | dispatch(Actions.setUiSchema(uiSchemaElement)); 36 | }, 37 | }); 38 | const classes = useStyles({ isOver }); 39 | return ( 40 |
41 | 42 | Drag and drop an element from the Palette to begin. 43 | 44 |
45 | ); 46 | }; 47 | -------------------------------------------------------------------------------- /jsonforms-editor/src/editor/components/preview/ReactMaterialPreview.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { createAjv } from '@jsonforms/core'; 9 | import { 10 | materialCells, 11 | materialRenderers, 12 | } from '@jsonforms/material-renderers'; 13 | import { JsonForms } from '@jsonforms/react'; 14 | import React, { useMemo } from 'react'; 15 | 16 | import { useSchema } from '../../../core/context'; 17 | import { generateEmptyData } from '../../../core/model'; 18 | import { useExportSchema, useExportUiSchema } from '../../../core/util/hooks'; 19 | import { previewOptions } from './options'; 20 | 21 | export const ReactMaterialPreview: React.FC = () => { 22 | const schema = useExportSchema(); 23 | const uischema = useExportUiSchema(); 24 | const editorSchema = useSchema(); 25 | const previewData = useMemo( 26 | () => (editorSchema ? generateEmptyData(editorSchema) : {}), 27 | [editorSchema] 28 | ); 29 | const ajv = createAjv(previewOptions); 30 | return ( 31 | 39 | ); 40 | }; 41 | -------------------------------------------------------------------------------- /jsonforms-editor/src/editor/components/preview/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export { previewOptions } from './options'; 9 | export * from './ReactMaterialPreview'; 10 | -------------------------------------------------------------------------------- /jsonforms-editor/src/editor/components/preview/options.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import AJV from 'ajv'; 9 | export const previewOptions: AJV.Options = { useDefaults: true }; 10 | -------------------------------------------------------------------------------- /jsonforms-editor/src/editor/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { JsonFormsRendererRegistryEntry } from '@jsonforms/core'; 9 | import { materialRenderers } from '@jsonforms/material-renderers'; 10 | 11 | import { DroppableArrayControlRegistration } from '../core/renderers/DroppableArrayControl'; 12 | import { DroppableCategorizationLayoutRegistration } from '../core/renderers/DroppableCategorizationLayout'; 13 | import { DroppableCategoryLayoutRegistration } from '../core/renderers/DroppableCategoryLayout'; 14 | import { DroppableElementRegistration } from '../core/renderers/DroppableElement'; 15 | import { DroppableGroupLayoutRegistration } from '../core/renderers/DroppableGroupLayout'; 16 | import { 17 | DroppableHorizontalLayoutRegistration, 18 | DroppableVerticalLayoutRegistration, 19 | } from '../core/renderers/DroppableLayout'; 20 | import { EditorTab } from './components/EditorPanel'; 21 | import { ReactMaterialPreview } from './components/preview'; 22 | 23 | export * from './components/EditorPanel'; 24 | export { EditorElement } from './components/EditorElement'; 25 | 26 | export const defaultEditorTabs: EditorTab[] = [ 27 | { name: 'Preview', Component: ReactMaterialPreview }, 28 | ]; 29 | 30 | export const defaultEditorRenderers: JsonFormsRendererRegistryEntry[] = [ 31 | ...materialRenderers, 32 | DroppableHorizontalLayoutRegistration, 33 | DroppableVerticalLayoutRegistration, 34 | DroppableElementRegistration, 35 | DroppableGroupLayoutRegistration, 36 | DroppableCategoryLayoutRegistration, 37 | DroppableArrayControlRegistration, 38 | DroppableCategorizationLayoutRegistration, 39 | ]; 40 | -------------------------------------------------------------------------------- /jsonforms-editor/src/env.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export const env = () => { 9 | const { REACT_APP_DEBUG: DEBUG = 'false', NODE_ENV } = process.env; 10 | return { NODE_ENV, DEBUG }; 11 | }; 12 | -------------------------------------------------------------------------------- /jsonforms-editor/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | 9 | export { JsonFormsEditor } from './JsonFormsEditor'; 10 | export * from './properties'; 11 | export * from './core/api'; 12 | export * from './core/components'; 13 | export * from './core/context'; 14 | export * from './core/dnd'; 15 | export * from './core/icons'; 16 | export * from './core/jsonschema'; 17 | export * from './core/model'; 18 | export * from './core/selection'; 19 | export * from './core/util'; 20 | export * from './editor/components/preview'; 21 | export * from './editor'; 22 | export * from './text-editor'; 23 | export * from './palette-panel'; 24 | -------------------------------------------------------------------------------- /jsonforms-editor/src/palette-panel/components/JsonSchemaPanel.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import React from 'react'; 9 | 10 | import { useDispatch, useSchema } from '../../core/context'; 11 | import { Actions, SchemaElement, toPrintableObject } from '../../core/model'; 12 | import { jsonToText, useExportSchema } from '../../core/util'; 13 | import { env } from '../../env'; 14 | import { SchemaJson, UpdateResult } from './SchemaJson'; 15 | 16 | export interface JsonSchemaPanelProps { 17 | title?: string; 18 | } 19 | export const JsonSchemaPanel: React.FC = ({ 20 | title = 'JSON Schema', 21 | }) => { 22 | const dispatch = useDispatch(); 23 | const exportSchema = useExportSchema(); 24 | const schema: SchemaElement | undefined = useSchema(); 25 | const showDebugSchema = env().DEBUG === 'true'; 26 | const handleSchemaUpdate = (newSchema: string): UpdateResult => { 27 | try { 28 | const newSchemaObject = JSON.parse(newSchema); 29 | dispatch(Actions.setSchema(newSchemaObject)); 30 | return { 31 | success: true, 32 | }; 33 | } catch (error) { 34 | if (error instanceof SyntaxError) { 35 | return { 36 | success: false, 37 | message: error.message, 38 | }; 39 | } 40 | // unknown error type 41 | throw error; 42 | } 43 | }; 44 | return ( 45 | 56 | ); 57 | }; 58 | -------------------------------------------------------------------------------- /jsonforms-editor/src/palette-panel/components/PalletePanel.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { makeStyles, Tab, Tabs } from '@material-ui/core'; 9 | import React, { useState } from 'react'; 10 | 11 | import { TabContent } from '../../core/components'; 12 | import { usePaletteService, useSchema } from '../../core/context'; 13 | import { SchemaElement } from '../../core/model'; 14 | import { JsonSchemaPanel } from './JsonSchemaPanel'; 15 | import { SchemaTreeView } from './SchemaTree'; 16 | import { UIElementsTree } from './UIElementsTree'; 17 | import { UISchemaPanel } from './UISchemaPanel'; 18 | 19 | const useStyles = makeStyles((theme) => ({ 20 | uiElementsTree: { 21 | marginBottom: theme.spacing(1), 22 | }, 23 | palettePanel: { 24 | height: '100%', 25 | display: 'flex', 26 | flexDirection: 'column', 27 | }, 28 | })); 29 | 30 | export interface PaletteTab { 31 | name: string; 32 | Component: React.ReactElement; 33 | } 34 | 35 | export interface PalettePanelProps { 36 | paletteTabs?: PaletteTab[]; 37 | } 38 | 39 | export const defaultPalettePanelTabs: PaletteTab[] = [ 40 | { 41 | name: 'JSON Schema', 42 | Component: , 43 | }, 44 | { name: 'UI Schema', Component: }, 45 | ]; 46 | 47 | export const PalettePanel: React.FC = ({ paletteTabs }) => { 48 | const [selectedTab, setSelectedTab] = useState(0); 49 | const handleTabChange = (event: React.ChangeEvent<{}>, newValue: number) => { 50 | setSelectedTab(newValue); 51 | }; 52 | const schema: SchemaElement | undefined = useSchema(); 53 | const paletteService = usePaletteService(); 54 | const classes = useStyles(); 55 | return ( 56 |
57 | 58 | 59 | {paletteTabs 60 | ? paletteTabs.map((tab) => ( 61 | 66 | )) 67 | : null} 68 | 69 | 70 | 74 | 75 | 76 | {paletteTabs 77 | ? paletteTabs.map((tab, index) => ( 78 | 83 | {tab.Component} 84 | 85 | )) 86 | : null} 87 |
88 | ); 89 | }; 90 | -------------------------------------------------------------------------------- /jsonforms-editor/src/palette-panel/components/SchemaJson.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { 9 | FormControlLabel, 10 | IconButton, 11 | Switch, 12 | Toolbar, 13 | } from '@material-ui/core'; 14 | import EditIcon from '@material-ui/icons/Edit'; 15 | import FileCopyIcon from '@material-ui/icons/FileCopy'; 16 | import React, { useState } from 'react'; 17 | 18 | import { ErrorDialog } from '../../core/components/ErrorDialog'; 19 | import { copyToClipBoard } from '../../core/util/clipboard'; 20 | import { env } from '../../env'; 21 | import { JsonEditorDialog, TextType } from '../../text-editor'; 22 | 23 | interface UpdateOk { 24 | success: true; 25 | } 26 | interface UpdateFail { 27 | success: false; 28 | message: string; 29 | } 30 | 31 | export type UpdateResult = UpdateOk | UpdateFail; 32 | 33 | interface SchemaJsonProps { 34 | title: string; 35 | schema: string; 36 | debugSchema?: string; 37 | type: TextType; 38 | updateSchema: (schema: any) => UpdateResult; 39 | } 40 | 41 | export const SchemaJson: React.FC = ({ 42 | title, 43 | schema, 44 | debugSchema, 45 | type, 46 | updateSchema, 47 | }) => { 48 | const [showSchemaEditor, setShowSchemaEditor] = useState(false); 49 | const [updateErrorText, setUpdateErrorText] = useState(''); 50 | const showDebugControls = debugSchema && env().DEBUG === 'true'; 51 | const [showDebugSchema, setShowDebugSchema] = useState( 52 | !!showDebugControls 53 | ); 54 | const showErrorDialog = Boolean(updateErrorText); 55 | const onApply = (newSchema: string) => { 56 | const updateResult = updateSchema(newSchema); 57 | if (updateResult.success) { 58 | setShowSchemaEditor(false); 59 | return; 60 | } 61 | setUpdateErrorText(updateResult.message); 62 | }; 63 | return ( 64 | <> 65 | 66 | 68 | copyToClipBoard( 69 | showDebugSchema && debugSchema ? debugSchema : schema 70 | ) 71 | } 72 | data-cy='copy-clipboard' 73 | > 74 | 75 | 76 | setShowSchemaEditor(true)} 78 | data-cy='edit-schema' 79 | > 80 | 81 | 82 | {showDebugControls ? ( 83 | setShowDebugSchema((showDebug) => !showDebug)} 89 | color='primary' 90 | /> 91 | } 92 | label='Debug' 93 | /> 94 | ) : null} 95 | 96 |
{showDebugSchema ? debugSchema : schema}
97 | {showSchemaEditor && ( 98 | setShowSchemaEditor(false)} 104 | onApply={onApply} 105 | /> 106 | )} 107 | {showErrorDialog && ( 108 | setUpdateErrorText('')} 113 | /> 114 | )} 115 | 116 | ); 117 | }; 118 | -------------------------------------------------------------------------------- /jsonforms-editor/src/palette-panel/components/SchemaTree.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import Typography from '@material-ui/core/Typography'; 9 | import React from 'react'; 10 | import { useDrag } from 'react-dnd'; 11 | 12 | import { DndItems } from '../../core/dnd'; 13 | import { SchemaIcon } from '../../core/icons'; 14 | import { 15 | getChildren, 16 | getLabel, 17 | getPath, 18 | isArrayElement, 19 | isObjectElement, 20 | SchemaElement, 21 | } from '../../core/model/schema'; 22 | import { EditorUISchemaElement } from '../../core/model/uischema'; 23 | import { createControl } from '../../core/util/generators/uiSchema'; 24 | import { StyledTreeItem, StyledTreeView } from './Tree'; 25 | 26 | interface SchemaTreeItemProps { 27 | schemaElement: SchemaElement; 28 | } 29 | 30 | const SchemaTreeItem: React.FC = ({ schemaElement }) => { 31 | const uiSchemaElement: EditorUISchemaElement = createControl(schemaElement); 32 | 33 | const [{ isDragging }, drag] = useDrag({ 34 | item: DndItems.newUISchemaElement(uiSchemaElement, schemaElement.uuid), 35 | canDrag: () => { 36 | return schemaElement.schema.type !== 'object'; 37 | }, 38 | collect: (monitor) => ({ 39 | isDragging: !!monitor.isDragging(), 40 | }), 41 | }); 42 | const schemaElementPath = getPath(schemaElement); 43 | return ( 44 |
45 | } 50 | isDragging={isDragging} 51 | > 52 | {getChildrenToRender(schemaElement).map((child) => ( 53 | 54 | ))} 55 | 56 |
57 | ); 58 | }; 59 | 60 | const getChildrenToRender = (schemaElement: SchemaElement) => { 61 | return getChildren(schemaElement).flatMap((child) => { 62 | // if the child is the only item of an array, use its children instead 63 | if ( 64 | isObjectElement(child) && 65 | isArrayElement(child.parent) && 66 | child.parent.items === child 67 | ) { 68 | return getChildren(child); 69 | } 70 | return [child]; 71 | }); 72 | }; 73 | 74 | export const SchemaTreeView: React.FC<{ 75 | schema: SchemaElement | undefined; 76 | }> = ({ schema }) => ( 77 | <> 78 | 79 | Controls 80 | 81 | {schema !== undefined ? ( 82 | 83 | 84 | 85 | ) : ( 86 | 87 | )} 88 | 89 | ); 90 | 91 | const NoSchema = () =>
No JSON Schema available
; 92 | -------------------------------------------------------------------------------- /jsonforms-editor/src/palette-panel/components/Tree.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { 9 | createStyles, 10 | fade, 11 | styled, 12 | Theme, 13 | WithStyles, 14 | withStyles, 15 | } from '@material-ui/core'; 16 | import Collapse from '@material-ui/core/Collapse'; 17 | import { TransitionProps } from '@material-ui/core/transitions'; 18 | import TreeItem, { TreeItemProps } from '@material-ui/lab/TreeItem'; 19 | import TreeView from '@material-ui/lab/TreeView'; 20 | import React from 'react'; 21 | import { animated, useSpring } from 'react-spring/web.cjs'; // web.cjs is required for IE 11 support 22 | 23 | const PaletteTransitionComponent = (props: TransitionProps) => { 24 | const style = useSpring({ 25 | from: { 26 | opacity: 0, 27 | transform: 'translate3d(20px,0,0)', 28 | filter: 'blur(0)', 29 | }, 30 | to: { 31 | opacity: props.in ? 1 : 0, 32 | transform: `translate3d(${props.in ? 0 : 20}px,0,0)`, 33 | filter: 'blur(0)', 34 | }, 35 | }); 36 | return ( 37 | 38 | 39 | 40 | ); 41 | }; 42 | 43 | export const StyledTreeView = styled(TreeView)({ flexGrow: 1, maxWidth: 400 }); 44 | 45 | const treeItemStyles = (theme: Theme) => 46 | createStyles({ 47 | root: (props: { isDragging: boolean }) => ({ 48 | opacity: props.isDragging ? 0.5 : 1, 49 | }), 50 | iconContainer: { 51 | '& .close': { 52 | opacity: 0.3, 53 | }, 54 | }, 55 | group: { 56 | marginLeft: theme.spacing(1), 57 | paddingLeft: theme.spacing(2), 58 | borderLeft: `1px dashed ${fade(theme.palette.text.primary, 0.4)}`, 59 | }, 60 | }); 61 | 62 | interface StyledTreeItemProps extends WithStyles { 63 | isDragging: boolean; 64 | } 65 | 66 | export const StyledTreeItem = withStyles(treeItemStyles)( 67 | ({ isDragging, ...props }: StyledTreeItemProps & TreeItemProps) => ( 68 | 69 | ) 70 | ); 71 | -------------------------------------------------------------------------------- /jsonforms-editor/src/palette-panel/components/UIElementsTree.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import Typography from '@material-ui/core/Typography'; 9 | import React from 'react'; 10 | import { useDrag } from 'react-dnd'; 11 | 12 | import { PaletteElement } from '../../core/api/paletteService'; 13 | import { DndItems } from '../../core/dnd'; 14 | import { EditorUISchemaElement } from '../../core/model/uischema'; 15 | import { StyledTreeItem, StyledTreeView } from './Tree'; 16 | 17 | interface UiSchemaTreeItemProps { 18 | uiSchemaElementProvider: () => EditorUISchemaElement; 19 | type: string; 20 | label: string; 21 | icon?: React.ReactNode; 22 | } 23 | 24 | const UiSchemaTreeItem: React.FC = ({ 25 | uiSchemaElementProvider, 26 | type, 27 | label, 28 | icon, 29 | }) => { 30 | const [{ isDragging }, drag] = useDrag({ 31 | item: DndItems.newUISchemaElement(uiSchemaElementProvider()), 32 | collect: (monitor) => ({ 33 | isDragging: !!monitor.isDragging(), 34 | }), 35 | }); 36 | return ( 37 |
38 | 45 |
46 | ); 47 | }; 48 | 49 | interface UIElementsTreeProps { 50 | className?: string; 51 | elements: PaletteElement[]; 52 | } 53 | 54 | export const UIElementsTree: React.FC = ({ 55 | className, 56 | elements, 57 | }) => { 58 | return ( 59 |
60 | 61 | Layouts & Other 62 | 63 | 64 | {elements.map(({ type, label, icon, uiSchemaElementProvider }) => ( 65 | 72 | ))} 73 | 74 |
75 | ); 76 | }; 77 | -------------------------------------------------------------------------------- /jsonforms-editor/src/palette-panel/components/UISchemaPanel.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import React from 'react'; 9 | 10 | import { useDispatch, useUiSchema } from '../../core/context'; 11 | import { Actions } from '../../core/model'; 12 | import { buildDebugUISchema } from '../../core/model/uischema'; 13 | import { jsonToText, useExportUiSchema } from '../../core/util'; 14 | import { env } from '../../env'; 15 | import { SchemaJson, UpdateResult } from './SchemaJson'; 16 | 17 | export interface UISchemaPanelProps { 18 | title?: string; 19 | } 20 | export const UISchemaPanel: React.FC = ({ 21 | title = 'UI Schema', 22 | }) => { 23 | const dispatch = useDispatch(); 24 | const exportUiSchema = useExportUiSchema(); 25 | const uiSchema = useUiSchema(); 26 | const showDebugSchema = env().DEBUG === 'true'; 27 | const handleUiSchemaUpdate = (newUiSchema: string): UpdateResult => { 28 | try { 29 | const newUiSchemaObject = JSON.parse(newUiSchema); 30 | dispatch(Actions.setUiSchema(newUiSchemaObject)); 31 | return { 32 | success: true, 33 | }; 34 | } catch (error) { 35 | if (error instanceof SyntaxError) { 36 | return { 37 | success: false, 38 | message: error.message, 39 | }; 40 | } 41 | // unknown error type 42 | throw error; 43 | } 44 | }; 45 | return ( 46 | 57 | ); 58 | }; 59 | -------------------------------------------------------------------------------- /jsonforms-editor/src/palette-panel/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | 9 | export type { PaletteTab } from './components/PalletePanel'; 10 | export * from './components/PalletePanel'; 11 | export { JsonSchemaPanel } from './components/JsonSchemaPanel'; 12 | export * from './components/UISchemaPanel'; 13 | -------------------------------------------------------------------------------- /jsonforms-editor/src/properties/components/Properties.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { JsonFormsRendererRegistryEntry } from '@jsonforms/core'; 9 | import { materialCells } from '@jsonforms/material-renderers'; 10 | import { JsonForms } from '@jsonforms/react'; 11 | import { isEqual, omit } from 'lodash'; 12 | import React, { useCallback, useEffect, useMemo, useState } from 'react'; 13 | 14 | import { 15 | useDispatch, 16 | usePropertiesService, 17 | useSchema, 18 | useSelection, 19 | useUiSchema, 20 | } from '../../core/context'; 21 | import { Actions } from '../../core/model'; 22 | import { EditorUISchemaElement } from '../../core/model/uischema'; 23 | import { tryFindByUUID } from '../../core/util/schemasUtil'; 24 | import { PropertySchemas } from '../propertiesService'; 25 | 26 | export interface PropertiesProps { 27 | propertyRenderers: JsonFormsRendererRegistryEntry[]; 28 | } 29 | export const Properties: React.FC = ({ 30 | propertyRenderers, 31 | }) => { 32 | const [selection] = useSelection(); 33 | const uiSchema = useUiSchema(); 34 | const schema = useSchema(); 35 | const dispatch = useDispatch(); 36 | 37 | const uiElement: EditorUISchemaElement | undefined = useMemo( 38 | () => tryFindByUUID(uiSchema, selection?.uuid), 39 | [selection, uiSchema] 40 | ); 41 | 42 | const data = useMemo( 43 | () => 44 | omit(uiElement, [ 45 | 'uuid', 46 | 'parent', 47 | 'elements', 48 | 'linkedSchemaElement', 49 | 'options.detail', 50 | ]), 51 | [uiElement] 52 | ); 53 | 54 | const updateProperties = useCallback( 55 | ({ data: updatedProperties }) => { 56 | if (uiElement && !isEqual(data, updatedProperties)) { 57 | dispatch( 58 | Actions.updateUISchemaElement(uiElement.uuid, updatedProperties) 59 | ); 60 | } 61 | }, 62 | [data, dispatch, uiElement] 63 | ); 64 | const propertiesService = usePropertiesService(); 65 | const [properties, setProperties] = useState(); 66 | useEffect(() => { 67 | if (!uiElement) { 68 | return; 69 | } 70 | const linkedSchemaUUID = uiElement.linkedSchemaElement; 71 | const elementSchema = 72 | linkedSchemaUUID && schema 73 | ? tryFindByUUID(schema, linkedSchemaUUID) 74 | : undefined; 75 | setProperties(propertiesService.getProperties(uiElement, elementSchema)); 76 | }, [propertiesService, schema, uiElement]); 77 | 78 | if (!selection) return ; 79 | 80 | return properties ? ( 81 | 89 | ) : ( 90 | 91 | ); 92 | }; 93 | const NoSelection = () =>
No selection
; 94 | const NoProperties = () => ( 95 |
Selected element does not have any configurable properties.
96 | ); 97 | -------------------------------------------------------------------------------- /jsonforms-editor/src/properties/components/PropertiesPanel.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { JsonFormsRendererRegistryEntry } from '@jsonforms/core'; 9 | import { Typography } from '@material-ui/core'; 10 | import React from 'react'; 11 | 12 | import { Properties } from './Properties'; 13 | 14 | export interface PropertiesPanelProps { 15 | propertyRenderers: JsonFormsRendererRegistryEntry[]; 16 | } 17 | export const PropertiesPanel: React.FC = ({ 18 | propertyRenderers, 19 | }) => { 20 | return ( 21 | <> 22 | 23 | Properties 24 | 25 | 26 | 27 | ); 28 | }; 29 | -------------------------------------------------------------------------------- /jsonforms-editor/src/properties/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { materialRenderers } from '@jsonforms/material-renderers'; 9 | 10 | import { RuleEditorRendererRegistration } from './renderers/RuleEditorRenderer'; 11 | 12 | export { PropertiesPanel } from './components/PropertiesPanel'; 13 | 14 | export * from './schemaDecorators'; 15 | export * from './schemaProviders'; 16 | export * from './propertiesService'; 17 | 18 | export const defaultPropertyRenderers = [ 19 | ...materialRenderers, 20 | RuleEditorRendererRegistration, 21 | ]; 22 | -------------------------------------------------------------------------------- /jsonforms-editor/src/properties/propertiesService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { JsonSchema, UISchemaElement } from '@jsonforms/core'; 9 | import { maxBy } from 'lodash'; 10 | 11 | import { SchemaElement } from '../core/model'; 12 | import { EditorUISchemaElement } from '../core/model/uischema'; 13 | 14 | export interface PropertiesService { 15 | getProperties( 16 | uiElement: any, 17 | schemaElement: any 18 | ): PropertySchemas | undefined; 19 | } 20 | 21 | /** 22 | * Schemas describing the properties view of an editor element. 23 | */ 24 | export interface PropertySchemas { 25 | schema: JsonSchema; 26 | uiSchema?: UISchemaElement; 27 | } 28 | 29 | /** 30 | * Decorator for the PropertySchemas of an EditorUISchemaElement. 31 | */ 32 | export interface PropertySchemasDecorator { 33 | ( 34 | schemas: PropertySchemas, 35 | uiElement: EditorUISchemaElement, 36 | schemaElement?: SchemaElement 37 | ): PropertySchemas; 38 | } 39 | 40 | /** 41 | * Constant that indicates that a tester is not capable of handling 42 | * an EditorUISchemaElement. 43 | */ 44 | export const NOT_APPLICABLE = -1; 45 | 46 | /** 47 | * Returns a PropertySchemas object for an EditorUISchemaElement. The tester will return a ranking 48 | * or NOT_APPLICABLE if the provider cannot supply any schema for the given editor element. */ 49 | export interface PropertySchemasProvider { 50 | tester: (uiElement: EditorUISchemaElement) => number; 51 | getPropertiesSchemas: ( 52 | uiElement: EditorUISchemaElement, 53 | schemaElement?: SchemaElement 54 | ) => PropertySchemas; 55 | } 56 | export class PropertiesServiceImpl implements PropertiesService { 57 | constructor( 58 | private schemaProviders: PropertySchemasProvider[], 59 | private schemaDecorators: PropertySchemasDecorator[] 60 | ) {} 61 | getProperties = ( 62 | uiElement: EditorUISchemaElement, 63 | schemaElement: SchemaElement | undefined 64 | ): PropertySchemas | undefined => { 65 | const provider = maxBy(this.schemaProviders, (p) => p.tester(uiElement)); 66 | if (!provider || provider.tester(uiElement) === NOT_APPLICABLE) { 67 | return undefined; 68 | } 69 | const elementSchemas = provider.getPropertiesSchemas( 70 | uiElement, 71 | schemaElement 72 | ); 73 | if (!elementSchemas) { 74 | return undefined; 75 | } 76 | const decoratedSchemas = this.schemaDecorators.reduce( 77 | (schemas, decorator) => decorator(schemas, uiElement, schemaElement), 78 | elementSchemas 79 | ); 80 | return decoratedSchemas; 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /jsonforms-editor/src/properties/renderers/RuleEditorRenderer.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | 9 | import { ControlProps, rankWith, scopeEndsWith } from '@jsonforms/core'; 10 | import { withJsonFormsControlProps } from '@jsonforms/react'; 11 | import { 12 | Accordion, 13 | AccordionDetails, 14 | AccordionSummary, 15 | Button, 16 | FormHelperText, 17 | Grid, 18 | makeStyles, 19 | Theme, 20 | Typography, 21 | } from '@material-ui/core'; 22 | import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; 23 | import { Uri } from 'monaco-editor/esm/vs/editor/editor.api'; 24 | import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; 25 | import React, { useCallback, useMemo, useState } from 'react'; 26 | import MonacoEditor from 'react-monaco-editor'; 27 | 28 | import { ShowMoreLess } from '../../core/components'; 29 | import { 30 | configureRuleSchemaValidation, 31 | EditorApi, 32 | getMonacoModelForUri, 33 | } from '../../text-editor/jsonSchemaValidation'; 34 | 35 | const invalidJsonMessage = 'Not a valid rule JSON.'; 36 | const ruleDescription = 37 | 'Define conditions and effects that can dynamically control features of the UI based on data.'; 38 | 39 | const ruleExample = ( 40 |
41 |

Example

42 |

43 | A rule that hides the UI Element it is contained in, when the value of the 44 | control with the scope '#/properties/name' is 'foo': 45 |

46 |
 47 |       {JSON.stringify(
 48 |         {
 49 |           effect: 'HIDE',
 50 |           condition: {
 51 |             type: 'LEAF',
 52 |             scope: '#/properties/name',
 53 |             expectedValue: 'foo',
 54 |           },
 55 |         },
 56 |         null,
 57 |         2
 58 |       )}
 59 |     
60 |

61 | Visit the{' '} 62 | 63 | JSON Forms documentation 64 | {' '} 65 | for more info. 66 |

67 |
68 | ); 69 | const isValidRule = (rule: any) => { 70 | return !rule || (rule.effect && rule.condition); 71 | }; 72 | 73 | const useStyles = makeStyles((theme: Theme) => ({ 74 | editorRoot: { 75 | width: '100%', 76 | }, 77 | showMore: { 78 | paddingBottom: theme.spacing(2), 79 | }, 80 | })); 81 | const RuleEditor: React.FC = (props) => { 82 | const { data, path, handleChange, errors } = props; 83 | const [invalidJson, setInvalidJson] = useState(false); 84 | const modelUri = Uri.parse('json://core/specification/rules.json'); 85 | 86 | const configureEditor = useCallback( 87 | (editor: EditorApi) => { 88 | configureRuleSchemaValidation(editor, modelUri); 89 | }, 90 | [modelUri] 91 | ); 92 | 93 | const model = useMemo( 94 | () => getMonacoModelForUri(modelUri, JSON.stringify(data, null, 2)), 95 | [data, modelUri] 96 | ); 97 | 98 | const setModel = useCallback( 99 | (editor: monaco.editor.IStandaloneCodeEditor) => { 100 | if (!model.isDisposed()) { 101 | editor.setModel(model); 102 | } 103 | }, 104 | [model] 105 | ); 106 | 107 | const onSubmitRule = useCallback(() => { 108 | try { 109 | const value = model.getValue(); 110 | const rule = value ? JSON.parse(value) : undefined; 111 | if (isValidRule(rule)) { 112 | setInvalidJson(false); 113 | handleChange(path, rule); 114 | } else { 115 | setInvalidJson(true); 116 | } 117 | } catch (error) { 118 | setInvalidJson(true); 119 | } 120 | }, [handleChange, model, path]); 121 | 122 | const isValid = errors.length === 0 && !invalidJson; 123 | const classes = useStyles(); 124 | return ( 125 | 126 | }> 127 | Rule 128 | 129 | 130 |
131 | {ruleDescription} 132 | 133 | {ruleExample} 134 | 135 | 136 | 147 | 148 | 149 | 152 | 153 | 154 | 157 | 158 | 159 |
160 |
161 |
162 | ); 163 | }; 164 | 165 | const RuleEditorRenderer = RuleEditor; 166 | export const RuleEditorRendererRegistration = { 167 | tester: rankWith(100, scopeEndsWith('rule')), 168 | renderer: withJsonFormsControlProps(RuleEditorRenderer), 169 | }; 170 | -------------------------------------------------------------------------------- /jsonforms-editor/src/properties/schemaDecorators.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { ControlElement, JsonSchema, Layout } from '@jsonforms/core'; 9 | import { assign } from 'lodash'; 10 | 11 | import { SchemaElement } from '../core/model'; 12 | import { EditorUISchemaElement } from '../core/model/uischema'; 13 | import { PropertySchemas, PropertySchemasDecorator } from './propertiesService'; 14 | 15 | export const multilineStringOptionDecorator: PropertySchemasDecorator = ( 16 | schemas: PropertySchemas, 17 | uiElement: EditorUISchemaElement, 18 | schemaElement?: SchemaElement 19 | ) => { 20 | if ( 21 | schemaElement?.schema.type === 'string' && 22 | !schemaElement?.schema.format && 23 | uiElement.type === 'Control' 24 | ) { 25 | addSchemaOptionsProperty(schemas.schema, { 26 | multi: { type: 'boolean' }, 27 | }); 28 | (schemas.uiSchema as Layout).elements.push( 29 | createPropertyControl('#/properties/options/properties/multi') 30 | ); 31 | } 32 | return schemas; 33 | }; 34 | 35 | export const labelUIElementDecorator: PropertySchemasDecorator = ( 36 | schemas: PropertySchemas, 37 | uiElement: EditorUISchemaElement 38 | ) => { 39 | if (uiElement?.type === 'Label') { 40 | assign(schemas.schema.properties, { text: { type: 'string' } }); 41 | 42 | (schemas.uiSchema as Layout).elements.push( 43 | createPropertyControl('#/properties/text') 44 | ); 45 | } 46 | return schemas; 47 | }; 48 | 49 | export const ruleDecorator: PropertySchemasDecorator = ( 50 | schemas: PropertySchemas 51 | ) => { 52 | assign(schemas.schema.properties, { 53 | rule: { 54 | type: 'object', 55 | }, 56 | }); 57 | (schemas.uiSchema as Layout).elements.push( 58 | createPropertyControl('#/properties/rule') 59 | ); 60 | return schemas; 61 | }; 62 | 63 | export const labelDecorator: PropertySchemasDecorator = ( 64 | schemas: PropertySchemas, 65 | uiElement: EditorUISchemaElement 66 | ) => { 67 | if ( 68 | ['Group', 'Control', 'Categorization', 'Category'].includes(uiElement?.type) 69 | ) { 70 | if (!schemas.schema.properties) { 71 | schemas.schema.properties = {}; 72 | } 73 | assign(schemas.schema.properties, { label: { type: 'string' } }); 74 | 75 | (schemas.uiSchema as Layout).elements.push( 76 | createPropertyControl('#/properties/label') 77 | ); 78 | } 79 | return schemas; 80 | }; 81 | 82 | export const addSchemaOptionsProperty = ( 83 | schema: JsonSchema, 84 | newOption: { 85 | [property: string]: JsonSchema; 86 | } 87 | ) => { 88 | if (!schema.properties) { 89 | schema.properties = {}; 90 | } 91 | if (!schema.properties.options) { 92 | schema.properties.options = { 93 | type: 'object', 94 | properties: {}, 95 | }; 96 | } 97 | assign(schema.properties.options.properties, newOption); 98 | }; 99 | 100 | export const createPropertyControl = ( 101 | controlScope: string 102 | ): ControlElement => ({ 103 | type: 'Control', 104 | scope: controlScope, 105 | }); 106 | 107 | export const defaultSchemaDecorators: PropertySchemasDecorator[] = [ 108 | labelDecorator, 109 | multilineStringOptionDecorator, 110 | labelUIElementDecorator, 111 | ruleDecorator, 112 | ]; 113 | -------------------------------------------------------------------------------- /jsonforms-editor/src/properties/schemaProviders.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { Layout } from '@jsonforms/core'; 9 | 10 | import { EditorUISchemaElement } from '../core/model/uischema'; 11 | import { 12 | NOT_APPLICABLE, 13 | PropertySchemas, 14 | PropertySchemasProvider, 15 | } from './propertiesService'; 16 | 17 | export const propertySchemaProvider: PropertySchemasProvider = { 18 | tester: (uiElement: EditorUISchemaElement): number => { 19 | if (uiElement) { 20 | // default schema provider 21 | return 1; 22 | } 23 | return NOT_APPLICABLE; 24 | }, 25 | getPropertiesSchemas: (): PropertySchemas => ({ 26 | schema: { 27 | type: 'object', 28 | properties: {}, 29 | }, 30 | uiSchema: { 31 | type: 'VerticalLayout', 32 | elements: [], 33 | } as Layout, 34 | }), 35 | }; 36 | 37 | export const defaultSchemaProviders = [propertySchemaProvider]; 38 | -------------------------------------------------------------------------------- /jsonforms-editor/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import '@testing-library/jest-dom/extend-expect'; 9 | -------------------------------------------------------------------------------- /jsonforms-editor/src/text-editor/components/JsonEditorDialog.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import { DialogContent, Fade, Typography } from '@material-ui/core'; 9 | import AppBar from '@material-ui/core/AppBar'; 10 | import Button from '@material-ui/core/Button'; 11 | import Dialog from '@material-ui/core/Dialog'; 12 | import IconButton from '@material-ui/core/IconButton'; 13 | import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; 14 | import Toolbar from '@material-ui/core/Toolbar'; 15 | import { TransitionProps } from '@material-ui/core/transitions'; 16 | import CloseIcon from '@material-ui/icons/Close'; 17 | import { Uri } from 'monaco-editor/esm/vs/editor/editor.api'; 18 | import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; 19 | import React, { useCallback, useMemo } from 'react'; 20 | import MonacoEditor from 'react-monaco-editor'; 21 | 22 | import { 23 | configureJsonSchemaValidation, 24 | EditorApi, 25 | getMonacoModelForUri, 26 | TextType, 27 | } from '../jsonSchemaValidation'; 28 | 29 | const useStyles = makeStyles((theme: Theme) => 30 | createStyles({ 31 | appBar: { 32 | position: 'relative', 33 | }, 34 | toolbar: { 35 | display: 'flex', 36 | justifyContent: 'space-between', 37 | }, 38 | title: { 39 | marginLeft: theme.spacing(2), 40 | flex: 1, 41 | }, 42 | dialogPaper: { 43 | height: '100%', // 'MonacoEditor' uses height to grow 44 | minHeight: '95vh', 45 | maxHeight: '95vh', 46 | }, 47 | dialogContent: { 48 | overflow: 'hidden', 49 | marginTop: theme.spacing(2), 50 | flex: 1, 51 | }, 52 | }) 53 | ); 54 | 55 | const Transition = React.forwardRef(function Transition( 56 | props: TransitionProps & { children?: React.ReactElement }, 57 | ref: React.Ref 58 | ) { 59 | return ; 60 | }); 61 | 62 | interface JsonEditorDialogProps { 63 | open: boolean; 64 | title: string; 65 | initialContent: any; 66 | type: TextType; 67 | onApply: (newContent: any) => void; 68 | onCancel: () => void; 69 | } 70 | 71 | export const JsonEditorDialog: React.FC = ({ 72 | open, 73 | title, 74 | initialContent, 75 | type, 76 | onApply, 77 | onCancel, 78 | }) => { 79 | const classes = useStyles(); 80 | 81 | const modelUri = Uri.parse('json://core/specification/schema.json'); 82 | 83 | const configureEditor = useCallback( 84 | (editor: EditorApi) => { 85 | if (type === 'JSON Schema') { 86 | configureJsonSchemaValidation(editor, modelUri); 87 | } 88 | }, 89 | [type, modelUri] 90 | ); 91 | 92 | const model = useMemo( 93 | () => getMonacoModelForUri(modelUri, initialContent), 94 | [initialContent, modelUri] 95 | ); 96 | 97 | const setModel = useCallback( 98 | (editor: monaco.editor.IStandaloneCodeEditor) => { 99 | if (!model.isDisposed()) { 100 | editor.setModel(model); 101 | } 102 | }, 103 | [model] 104 | ); 105 | 106 | return ( 107 | 115 | 116 | 117 | 124 | 125 | 126 | 127 | {title} Text Edit 128 | 129 | 136 | 137 | 138 | 139 | { 142 | setModel(editor); 143 | editor.focus(); 144 | }} 145 | editorWillMount={configureEditor} 146 | /> 147 | 148 | 149 | ); 150 | }; 151 | -------------------------------------------------------------------------------- /jsonforms-editor/src/text-editor/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | export { JsonEditorDialog } from './components/JsonEditorDialog'; 9 | export type { TextType, EditorApi } from './jsonSchemaValidation'; 10 | export { addSchema } from './jsonSchemaValidation'; 11 | -------------------------------------------------------------------------------- /jsonforms-editor/src/text-editor/jsonSchemaValidation.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * --------------------------------------------------------------------- 3 | * Copyright (c) 2021 EclipseSource Munich 4 | * Licensed under MIT 5 | * https://github.com/eclipsesource/jsonforms-editor/blob/master/LICENSE 6 | * --------------------------------------------------------------------- 7 | */ 8 | import editorApi from 'monaco-editor/esm/vs/editor/editor.api'; 9 | import { Uri } from 'monaco-editor/esm/vs/editor/editor.api'; 10 | import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; 11 | 12 | import { jsonSchemaDraft7, ruleSchema } from '../core/jsonschema'; 13 | 14 | export type EditorApi = typeof editorApi; 15 | export type TextType = 'JSON' | 'JSON Schema' | 'UI Schema'; 16 | 17 | /** 18 | * Register a new schema for the Json language, if it isn't already registered. 19 | * Schemas are identified by their uri and fileMatch rule, so that they don't 20 | * leak into unrelated Json editors. 21 | * @param editor 22 | * The monaco editor 23 | * @param schemas 24 | * Schemas to register 25 | */ 26 | export const addSchema = ( 27 | editor: EditorApi, 28 | schemas: { 29 | uri: string; 30 | fileMatch?: string[]; 31 | schema?: any; 32 | }[] 33 | ) => { 34 | const registeredSchemas = 35 | editor.languages.json.jsonDefaults.diagnosticsOptions.schemas; 36 | if (registeredSchemas === undefined) { 37 | editor.languages.json.jsonDefaults.setDiagnosticsOptions({ 38 | validate: true, 39 | schemas: [...schemas], 40 | }); 41 | } else { 42 | for (const schema of schemas) { 43 | const fileMatch = schema.fileMatch; 44 | 45 | const gridSchema = registeredSchemas.find( 46 | (registeredSchema) => 47 | registeredSchema.fileMatch === fileMatch && 48 | registeredSchema.uri === schema.uri 49 | ); 50 | if (!gridSchema) { 51 | registeredSchemas.push({ ...schema }); 52 | } 53 | } 54 | } 55 | }; 56 | 57 | /** 58 | * Configures the Monaco Editor to validate the input against JSON Schema Draft 7. 59 | */ 60 | export const configureJsonSchemaValidation = ( 61 | editor: EditorApi, 62 | modelUri: Uri 63 | ) => { 64 | /** Note that the Monaco Editor only supports JSON Schema Draft 7 itself, 65 | * so if we also want to support a later standard we still have to formalize 66 | * it in JSON Schema Draft 7*/ 67 | addSchema(editor, [ 68 | { ...jsonSchemaDraft7, fileMatch: [modelUri.toString()] }, 69 | ]); 70 | }; 71 | 72 | /** 73 | * Configures the Monaco Editor to validate the input against the Rule UI Schema meta-schema. 74 | */ 75 | export const configureRuleSchemaValidation = ( 76 | editor: EditorApi, 77 | modelUri: Uri 78 | ) => { 79 | /** Note that the Monaco Editor only supports JSON Schema Draft 7 itself, 80 | * so if we also want to support a later standard we still have to formalize 81 | * it in JSON Schema Draft 7*/ 82 | addSchema(editor, [ 83 | { ...jsonSchemaDraft7 }, 84 | { ...ruleSchema, fileMatch: [modelUri.toString()] }, 85 | ]); 86 | }; 87 | 88 | export const getMonacoModelForUri = ( 89 | modelUri: Uri, 90 | initialValue: string | undefined 91 | ) => { 92 | const value = initialValue ?? ''; 93 | let model = monaco.editor.getModel(modelUri); 94 | if (model) { 95 | model.setValue(value); 96 | } else { 97 | model = monaco.editor.createModel(value, 'json', modelUri); 98 | } 99 | return model; 100 | }; 101 | -------------------------------------------------------------------------------- /jsonforms-editor/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig", 3 | "exclude": [ 4 | "**/*.test.ts*", 5 | "**/*.spec.ts*", 6 | "**/__tests__/**/*.ts*", 7 | "node_modules" 8 | ], 9 | "files": ["./src/index.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /jsonforms-editor/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "declarationDir": "dist", 4 | "declaration": true, 5 | "sourceMap": true, 6 | "target": "es5", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "allowJs": true, 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "strict": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "react", 19 | }, 20 | "include": ["src"], 21 | "exclude": [ 22 | "node_modules", 23 | "dist" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": ["app", "jsonforms-editor", "testapp"], 3 | "version": "0.1.0" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "root", 3 | "scripts": { 4 | "lerna": "lerna", 5 | "init": "lerna bootstrap --hoist", 6 | "build": "lerna run build", 7 | "test": "lerna run test", 8 | "lint": "lerna run lint", 9 | "start": "cd app && npm run start", 10 | "watch": "cd jsonforms-editor && npm run watch", 11 | "cypress:open": "cd app && npm run cypress:open", 12 | "cypress:ci": "cd app && npm run cypress:ci" 13 | }, 14 | "devDependencies": { 15 | "jest-environment-jsdom-fourteen": "^1.0.1", 16 | "lerna": "^4.0.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /testapp/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /testapp/README.md: -------------------------------------------------------------------------------- 1 | # testapp 2 | 3 | This test app consumes the library component in the same way a regular user could as barebones as possible. 4 | The app is built and smoke tested by the ci process. 5 | -------------------------------------------------------------------------------- /testapp/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testapp", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@jsonforms/editor": "0.1.0", 7 | "@testing-library/jest-dom": "^4.2.4", 8 | "@testing-library/react": "^9.3.2", 9 | "@testing-library/user-event": "^7.1.2", 10 | "react": "^16.13.1", 11 | "react-dom": "^16.13.1", 12 | "react-scripts": "^4.0.0" 13 | }, 14 | "scripts": { 15 | "start": "react-scripts start", 16 | "ci:build": "react-scripts build", 17 | "ci:test": "react-scripts test --watchAll=false", 18 | "eject": "react-scripts eject" 19 | }, 20 | "eslintConfig": { 21 | "extends": "react-app" 22 | }, 23 | "browserslist": { 24 | "production": [ 25 | ">0.2%", 26 | "not dead", 27 | "not op_mini all" 28 | ], 29 | "development": [ 30 | "last 1 chrome version", 31 | "last 1 firefox version", 32 | "last 1 safari version" 33 | ] 34 | }, 35 | "jest": { 36 | "transformIgnorePatterns": [ 37 | "node_modules/(?!(monaco-editor)/)" 38 | ], 39 | "moduleNameMapper": { 40 | "^.+\\.(css|scss)$": "identity-obj-proxy", 41 | "monaco-editor": "/../node_modules/react-monaco-editor" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /testapp/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipsesource/jsonforms-editor/a64f24d3c305a47c989625c8992450754ac4eaad/testapp/public/favicon.ico -------------------------------------------------------------------------------- /testapp/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /testapp/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipsesource/jsonforms-editor/a64f24d3c305a47c989625c8992450754ac4eaad/testapp/public/logo192.png -------------------------------------------------------------------------------- /testapp/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eclipsesource/jsonforms-editor/a64f24d3c305a47c989625c8992450754ac4eaad/testapp/public/logo512.png -------------------------------------------------------------------------------- /testapp/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /testapp/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /testapp/src/App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import { 4 | JsonFormsEditor, 5 | defaultSchemaDecorators, 6 | defaultSchemaProviders, 7 | } from '@jsonforms/editor'; 8 | 9 | export const App = () => ( 10 | 14 | ); 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /testapp/src/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { act, render } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders header', async () => { 6 | // components with 'useEffect' need to be awaited 7 | const container = render(
); 8 | await act(async () => { 9 | render(, container); 10 | }); 11 | const titleElement = container.getByText(/JSON Forms Editor/i); 12 | expect(titleElement).toBeInTheDocument(); 13 | }); 14 | -------------------------------------------------------------------------------- /testapp/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /testapp/src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom/extend-expect'; 6 | --------------------------------------------------------------------------------