Skip to content

Commit

Permalink
Rename editor package to material-tree-renderer
Browse files Browse the repository at this point in the history
  • Loading branch information
edgarmueller committed Jul 10, 2018
0 parents commit 2f5cc1f
Show file tree
Hide file tree
Showing 42 changed files with 5,444 additions and 0 deletions.
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# JSONForms - More Forms. Less Code
### Complex Forms in the blink of an eye

JSONForms eliminates the tedious task of writing fully-featured forms by hand by leveraging the capabilities of JSON, JSON Schema and Javascript.

# Editor Package
This repository contains a re-usable tree component that renders a tree-master-detail JSON editor.
The repository contains an IDE webcomponent that additionally configures 3 buttons to access the data shown in the tree:
- A download button
- An export button that shows the data in a dialog
- A load button that opens a native file selection dialog to load a file from the user's harddrive

Additionally, the package contains a small runtime demo showing an editor for users and tasks.

## Build
Run `npm install` to install dependencies.
Run `npm run build` to build the module. The build results are located in `/dist/`.

## Run Demo
Run `npm run dev` to start the standalone editor. It is available at http://localhost:8080/

# License
The JSONForms project is licensed under the MIT License. See the [LICENSE file](https://github.com/eclipsesource/jsonforms/blob/master/LICENSE) for more information.

# Roadmap
Our current roadmap is available [here](https://github.com/eclipsesource/jsonforms/blob/master/ROADMAP.md).

# Development
JSONForms is developed by [EclipseSource](https://eclipsesource.com).
We are always very happy to have contributions, whether for trivial cleanups or big new features.

# Migration
If you are already using JSONForms 1, check our [migration guide](https://github.com/eclipsesource/jsonforms/blob/master/MIGRATION.md).
29 changes: 29 additions & 0 deletions example/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as React from 'react';
import { Provider } from 'react-redux';
import EditorIde from '../src/ThemedTreeWithDetail';
import EditorBar from './app-bar/EditorBar';
import {
getData,
getSchema,
getUiSchema
} from '@jsonforms/core';

const App = ({store, filterPredicate, labelProvider, imageProvider}) => (
<Provider store={store}>
<React.Fragment>
<EditorBar
schema={getSchema(store.getState())}
rootData={getData(store.getState())}
/>
<EditorIde
filterPredicate={filterPredicate}
labelProvider={labelProvider}
imageProvider={imageProvider}
schema={getSchema(store.getState())}
uischema={getUiSchema(store.getState())}
/>
</React.Fragment>
</Provider>
);

export default App;
182 changes: 182 additions & 0 deletions example/app-bar/EditorBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
import * as React from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import * as _ from 'lodash';
import { StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles';
import AppBar from '@material-ui/core/AppBar';
import Toolbar from '@material-ui/core/Toolbar';
import Typography from '@material-ui/core/Typography';
import Button from '@material-ui/core/Button';
import FileDownload from '@material-ui/icons/FileDownload';
import FolderOpen from '@material-ui/icons/FolderOpen';
import ImportExport from '@material-ui/icons/ImportExport';
import ModelSchemaDialog from './dialogs/ModelSchemaDialog';
import { Actions, getData, getSchema } from '@jsonforms/core';
import { createAjv } from '@jsonforms/core/lib/util/validator';

const ajv = createAjv();

const styles: StyleRulesCallback<'root' | 'flex' | 'rightIcon' | 'button'> = theme => ({
root: {
flexGrow: 1,
},
flex: {
flex: 1,
},
rightIcon: {
marginLeft: theme.spacing.unit,
},
button: {
margin: theme.spacing.unit,
}
});

interface EditorBarProps {
schema: any;
rootData: any;
updateRootData?: any;
}

interface EditorBarState {
exportDialog: {
open: boolean
};
}

class EditorBar extends
React.Component<EditorBarProps & WithStyles<'root' | 'flex' | 'rightIcon' | 'button'>,
EditorBarState> {
constructor(props) {
super(props);
this.state = {
exportDialog: {
open: false
}
};
}

handleExportDialogOpen = () => {
this.setState({
exportDialog: {
open: true
}
});
}

handleExportDialogClose = () => {
this.setState({
exportDialog: {
open: false
}
});
}

handleDownload = () => {
const a = document.createElement('a');
const file = new Blob([JSON.stringify(this.props.rootData, null, 2)],
{type: 'application/json'});
a.href = URL.createObjectURL(file);
a.download = 'download.json';
a.click();
}

handleFileUpload = event => {
// triggered after a file was selected
const schema = this.props.schema;
const target = event.target as HTMLInputElement;
const files = target.files;
if (_.isEmpty(files) || files.length > 1) {
return;
}
const file = files[0];
const reader = new FileReader();

// Callback when the file was loaded
reader.onload = () => {
if (reader.result === undefined || reader.result === null) {
console.error('Could not read data');
}
let readData;
try {
readData = JSON.parse(reader.result);
} catch (err) {
console.error('The loaded file did not contain valid JSON.', err);
alert(`The selected file '${file.name}' does not contain valid JSON`);

return;
}
if (!_.isEmpty(readData)) {
const valid = ajv.validate(schema, readData);
if (valid) {
this.props.updateRootData(readData);
} else {
alert('Loaded data does not adhere to the specified schema.');
console.error('Loaded data does not adhere to the specified schema.');

return;
}
}
};

reader.readAsText(file);
}

render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<AppBar position='static'>
<Toolbar>
<Typography variant='title' color='inherit' className={classes.flex}>
User and Task Editor
</Typography>
<Button component='label' className={classes.button} color='inherit'>
Open Data File
<FolderOpen className={classes.rightIcon} />
<input
onChange={this.handleFileUpload}
style={{ display: 'none' }}
type='file'
/>
</Button>
<Button
className={classes.button}
color='inherit'
onClick={this.handleExportDialogOpen}
>
Export Model
<ImportExport className={classes.rightIcon} />
</Button>
<ModelSchemaDialog
open={this.state.exportDialog.open}
onClose={this.handleExportDialogClose}
/>
<Button className={classes.button} color='inherit' onClick={this.handleDownload}>
Download Model
<FileDownload className={classes.rightIcon} />
</Button>

</Toolbar>
</AppBar>
</div>
);
}
}

const mapStateToProps = state => {
return {
schema: getSchema(state),
rootData: getData(state)
};
};

const mapDispatchToProps = dispatch => ({
updateRootData(data: Object) {
dispatch(Actions.update('', () => data));
}
});

export default compose(
withStyles(styles, { name: 'EditorBar' }),
connect(mapStateToProps, mapDispatchToProps)
)(EditorBar);
98 changes: 98 additions & 0 deletions example/app-bar/dialogs/ModelSchemaDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import * as React from 'react';
import { connect } from 'react-redux';
import { compose } from 'recompose';
import { StyleRulesCallback, withStyles, WithStyles } from '@material-ui/core/styles';
import DialogTitle from '@material-ui/core/DialogTitle';
import DialogContent from '@material-ui/core/DialogContent';
import DialogActions from '@material-ui/core/DialogActions';
import Dialog from '@material-ui/core/Dialog';
import Button from '@material-ui/core/Button';
import TextField from '@material-ui/core/TextField';
import withMobileDialog from '@material-ui/core/withMobileDialog';
import { getData, getUiSchema } from '@jsonforms/core';

const styles: StyleRulesCallback<'textarea'> = () => ({
textarea: {
width: 400,
height: 600,
whiteSpace: 'pre-wrap',
overflowWrap: 'normal',
overflowX: 'scroll'
}
});

interface ModelSchemaDialogProps {
onClose: any;
readOnly: boolean;
open: boolean;
fullScreen?: boolean;
rootData?: any;
}

class ModelSchemaDialog extends
React.Component<ModelSchemaDialogProps & WithStyles<'textarea'>, {}> {
private textInput = React.createRef<HTMLInputElement>();

handleCancel = () => {
this.props.onClose();
}

handleCopy = () => {
this.textInput.current.select();
document.execCommand('copy');
}

render() {
const { classes, fullScreen, open, rootData } = this.props;
const textFieldData = JSON.stringify(rootData, null, 2);

return (
<Dialog open={open} fullScreen={fullScreen}>
<DialogTitle id='model-schema-dialog'>
Model Data
</DialogTitle>
<DialogContent>
<TextField
id='model-schema-textfield'
className={classes.textarea}
label='Model Data'
multiline
value={textFieldData}
margin='normal'
rowsMax={25}
inputProps={{
readOnly: true
}}
inputRef={this.textInput}
/>
</DialogContent>
<DialogActions>
<Button onClick={this.handleCancel} color='primary'>
Cancel
</Button>
<Button onClick={this.handleCopy} color='primary'>
Copy
</Button>
</DialogActions>
</Dialog>
);
}
}

const mapStateToProps = (state, ownProps) => {
const rootData = getData(state);

return {
rootData,
classes: ownProps.classes,
onClose: ownProps.onClose,
open: ownProps.open,
uischema: getUiSchema(state)
};
};

export default compose(
withStyles(styles, { name: 'ModelSchemaDialog' }),
withMobileDialog(),
connect(mapStateToProps)
)(ModelSchemaDialog);
28 changes: 28 additions & 0 deletions example/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { taskView, userGroupView, userView } from './uischemata';

export const labelProvider = {
'#user': 'name',
'#userGroup': 'label',
'#task': 'name',
};

export const imageProvider = {
'#task': 'task',
'#user': 'user',
'#userGroup': 'userGroup'
};

export const modelMapping = {
'attribute': '_type',
'mapping': {
'task': '#task',
'user': '#user',
'userGroup': '#userGroup'
}
};

export const detailSchemata = {
'#task': taskView,
'#user': userView,
'#userGroup': userGroupView,
};
9 changes: 9 additions & 0 deletions example/example.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.icon.user {
background-image: url('./User.png');
}
.icon.userGroup {
background-image: url('./UserGroup.png');
}
.icon.task {
background-image: url('./Task.png');
}
Binary file added example/icons/Task.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/icons/User.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/icons/UserGroup.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 2f5cc1f

Please sign in to comment.