├── .eslintignore ├── __mocks__ ├── fileMock.js └── styleMock.js ├── assets ├── APLogo.png └── SearchAlternate.svg ├── src ├── selectors │ ├── modelSelectors.js │ ├── clauseTemplateSelectors.js │ ├── templatesSelectors.js │ └── contractSelectors.js ├── utilities │ ├── test │ │ ├── jestEnzyme.js │ │ └── sagaTest.js │ ├── onClauseUpdated.js │ └── navigateClause.js ├── index.js ├── sagas │ ├── rootSaga.js │ ├── actions.js │ ├── tests │ │ ├── modelSaga.test.js │ │ └── templatesSaga.test.js │ ├── modelSaga.js │ ├── templatesSaga.js │ └── contractSaga.js ├── containers │ ├── LeftNav │ │ ├── SubHeadingBtn.js │ │ ├── ClauseNav.js │ │ └── index.js │ ├── App │ │ ├── __snapshots__ │ │ │ └── index.test.js.snap │ │ ├── index.test.js │ │ ├── themeConstants.js │ │ └── index.js │ ├── ErrorModal │ │ ├── index.test.js │ │ ├── __snapshots__ │ │ │ └── index.test.js.snap │ │ └── index.js │ ├── Error │ │ ├── errorReducer.js │ │ └── index.js │ ├── BaseEditors │ │ ├── ErgoEditor │ │ │ ├── index.js │ │ │ └── ergo.js │ │ ├── ConcertoEditor │ │ │ ├── index.js │ │ │ └── concerto.js │ │ ├── JsonEditor │ │ │ └── index.js │ │ └── CodeEditor │ │ │ └── index.js │ ├── ClauseTemplate │ │ ├── ClauseMetadataEditor.js │ │ ├── ClauseGrammarEditor.js │ │ ├── ClauseExampleTextEditor.js │ │ ├── ClauseModelEditor.js │ │ └── ClauseLogicEditor.js │ ├── CurrentEditor │ │ └── index.js │ ├── Header │ │ └── index.js │ ├── ContractEditor │ │ └── index.js │ ├── TemplateLibrary │ │ └── index.js │ └── Welcome │ │ ├── styles.js │ │ └── index.js ├── reducers │ ├── initialState.json │ ├── modelReducer.js │ ├── templatesReducer.js │ ├── appReducer.js │ ├── contractReducer.js │ └── clauseTemplatesReducer.js ├── actions │ ├── modelActions.js │ ├── appActions.js │ ├── templatesActions.js │ ├── clauseTemplatesActions.js │ ├── contractActions.js │ └── constants.js ├── components │ └── TextButton.js ├── store.js ├── index.html └── index.css ├── public ├── favicon.png ├── img │ └── logo.png └── fonts │ ├── Graphik-400.woff │ ├── Graphik-400.woff2 │ ├── Graphik-600.woff │ ├── Graphik-600.woff2 │ ├── Graphik-400-Italic.woff │ ├── Graphik-400-Italic.woff2 │ ├── Graphik-600-Italic.woff │ └── Graphik-600-Italic.woff2 ├── .github ├── ISSUE_TEMPLATE │ ├── custom-issue.md │ ├── feature_request.md │ └── bug_report.md └── pull_request_template.md ├── .babelrc ├── .eslintrc ├── markdown-license.txt ├── .gitignore ├── webpack.config.js ├── package.json ├── DEVELOPERS.md ├── CHARTER.md ├── CONTRIBUTING.md ├── LICENSE └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules -------------------------------------------------------------------------------- /__mocks__/fileMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /__mocks__/styleMock.js: -------------------------------------------------------------------------------- 1 | module.exports = {}; 2 | -------------------------------------------------------------------------------- /assets/APLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accordproject/template-studio-v2/HEAD/assets/APLogo.png -------------------------------------------------------------------------------- /src/selectors/modelSelectors.js: -------------------------------------------------------------------------------- 1 | export const modelFiles = state => state.modelState.modelFiles; 2 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accordproject/template-studio-v2/HEAD/public/favicon.png -------------------------------------------------------------------------------- /public/img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accordproject/template-studio-v2/HEAD/public/img/logo.png -------------------------------------------------------------------------------- /src/selectors/clauseTemplateSelectors.js: -------------------------------------------------------------------------------- 1 | export const clauseTemplates = state => state.clauseTemplatesState; 2 | -------------------------------------------------------------------------------- /src/selectors/templatesSelectors.js: -------------------------------------------------------------------------------- 1 | export const templateObjects = state => state.templatesState.templateObjs; 2 | -------------------------------------------------------------------------------- /public/fonts/Graphik-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accordproject/template-studio-v2/HEAD/public/fonts/Graphik-400.woff -------------------------------------------------------------------------------- /public/fonts/Graphik-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accordproject/template-studio-v2/HEAD/public/fonts/Graphik-400.woff2 -------------------------------------------------------------------------------- /public/fonts/Graphik-600.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accordproject/template-studio-v2/HEAD/public/fonts/Graphik-600.woff -------------------------------------------------------------------------------- /public/fonts/Graphik-600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accordproject/template-studio-v2/HEAD/public/fonts/Graphik-600.woff2 -------------------------------------------------------------------------------- /public/fonts/Graphik-400-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accordproject/template-studio-v2/HEAD/public/fonts/Graphik-400-Italic.woff -------------------------------------------------------------------------------- /public/fonts/Graphik-400-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accordproject/template-studio-v2/HEAD/public/fonts/Graphik-400-Italic.woff2 -------------------------------------------------------------------------------- /public/fonts/Graphik-600-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accordproject/template-studio-v2/HEAD/public/fonts/Graphik-600-Italic.woff -------------------------------------------------------------------------------- /public/fonts/Graphik-600-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/accordproject/template-studio-v2/HEAD/public/fonts/Graphik-600-Italic.woff2 -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom-issue.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue 3 | about: Describe this issue's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/selectors/contractSelectors.js: -------------------------------------------------------------------------------- 1 | export const markdown = state => state.contractState.markdown; 2 | 3 | export const slateValue = state => state.contractState.slateValue; 4 | 5 | export const clauses = state => state.contractState.clauses; 6 | -------------------------------------------------------------------------------- /src/utilities/test/jestEnzyme.js: -------------------------------------------------------------------------------- 1 | import Enzyme from 'enzyme'; // eslint-disable-line import/no-extraneous-dependencies 2 | import Adapter from 'enzyme-adapter-react-16'; // eslint-disable-line import/no-extraneous-dependencies 3 | 4 | Enzyme.configure({ adapter: new Adapter() }); 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": [ 4 | "@babel/plugin-transform-runtime", 5 | "@babel/plugin-proposal-class-properties", 6 | "@babel/plugin-syntax-dynamic-import", 7 | "babel-plugin-styled-components", 8 | ] 9 | } -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Provider } from 'react-redux'; 4 | 5 | import MainApp from './containers/App'; 6 | import store from './store'; 7 | import './index.css'; 8 | 9 | render(, document.querySelector('#root')); 10 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@clausehq/eslint-config"], 3 | "parser": "babel-eslint", 4 | "settings": { 5 | "import/resolver": { 6 | "node": { 7 | "paths": ["src"], 8 | "extensions": [".js", ".jsx", ".ts", ".tsx"] 9 | } 10 | }, 11 | "react": { 12 | "version": "detect" 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /markdown-license.txt: -------------------------------------------------------------------------------- 1 | ## License 2 | Accord Project source code files are made available under the Apache License, Version 2.0 (Apache-2.0), located in the LICENSE file. Accord Project documentation files are made available under the Creative Commons Attribution 4.0 International License (CC-BY-4.0), available at http://creativecommons.org/licenses/by/4.0/. -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Issue # 2 | 3 | 4 | ### Changes 5 | - 6 | - 7 | - 8 | 9 | ### Flags 10 | - 11 | 12 | ### Related Issues 13 | - Issue # 14 | - Pull Request # 15 | -------------------------------------------------------------------------------- /src/sagas/rootSaga.js: -------------------------------------------------------------------------------- 1 | import { all } from 'redux-saga/effects'; 2 | import { templatesSaga } from './templatesSaga'; 3 | import { modelSaga } from './modelSaga'; 4 | import { contractSaga } from './contractSaga'; 5 | 6 | /** 7 | * saga to yield all others 8 | */ 9 | export default function* rootSaga() { 10 | yield all([ 11 | ...templatesSaga, 12 | ...modelSaga, 13 | ...contractSaga, 14 | ]); 15 | } 16 | -------------------------------------------------------------------------------- /.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 | /log 11 | 12 | # production 13 | /build 14 | /dist 15 | 16 | # misc 17 | .DS_Store 18 | .env.local 19 | .env.development.local 20 | .env.test.local 21 | .env.production.local 22 | thumbs.db 23 | 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | -------------------------------------------------------------------------------- /src/containers/LeftNav/SubHeadingBtn.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import TextButton from '../../components/TextButton'; 3 | import { AP_THEME } from '../App/themeConstants'; 4 | 5 | const SubHeadingBtn = styled(TextButton)` 6 | color: ${AP_THEME.GRAY}; 7 | font-size: 1em; 8 | line-height: 24px; 9 | text-decoration: none; 10 | text-align: left; 11 | margin: 6px; 12 | display: block; 13 | padding-left: 10px; 14 | `; 15 | 16 | export default SubHeadingBtn; 17 | -------------------------------------------------------------------------------- /src/reducers/initialState.json: -------------------------------------------------------------------------------- 1 | { 2 | "object": "value", 3 | "document": { 4 | "object": "document", 5 | "data": null, 6 | "nodes": [ 7 | { 8 | "object": "block", 9 | "type": "paragraph", 10 | "data": null, 11 | "nodes": [ 12 | { 13 | "object": "text", 14 | "text": "Welcome to Template Studio! Edit this text to get started.", 15 | "marks": [] 16 | } 17 | ] 18 | } 19 | ] 20 | } 21 | } -------------------------------------------------------------------------------- /src/containers/App/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` on initialization renders page correctly 1`] = ` 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | `; 17 | -------------------------------------------------------------------------------- /src/sagas/actions.js: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | export const headingClause = node => node.type === 'clause'; 4 | const headingOne = node => node.type === 'heading_one'; 5 | const headingTwo = node => node.type === 'heading_two'; 6 | const headingThree = node => node.type === 'heading_three'; 7 | 8 | export const headingExists = R.anyPass([headingOne, headingTwo, headingThree]); 9 | 10 | export const clauseDisplayNameFinder = src => R 11 | .path(['metadata', 'packageJson', 'displayName'], src); 12 | 13 | export const clauseNameFinder = src => R 14 | .path(['metadata', 'packageJson', 'name'], src); 15 | -------------------------------------------------------------------------------- /src/actions/modelActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | UPDATE_MODEL_ERROR_SUCCEEDED, 3 | UPDATE_MODEL_MANAGER_SUCCEEDED, 4 | VALIDATE_CLAUSE_MODEL_FILES, 5 | } from './constants'; 6 | 7 | export const updateModelManagerSuccess = modelManager => ({ 8 | type: UPDATE_MODEL_MANAGER_SUCCEEDED, 9 | modelManager, 10 | }); 11 | 12 | export const updateModelManagerError = (error, clauseTemplateId) => ({ 13 | type: UPDATE_MODEL_ERROR_SUCCEEDED, 14 | error, 15 | clauseTemplateId 16 | }); 17 | 18 | export const validateClauseModelFilesAction = (clauseTemplateId, fileName) => ({ 19 | type: VALIDATE_CLAUSE_MODEL_FILES, 20 | clauseTemplateId, 21 | fileName 22 | }); 23 | -------------------------------------------------------------------------------- /src/utilities/test/sagaTest.js: -------------------------------------------------------------------------------- 1 | import { runSaga } from 'redux-saga'; 2 | 3 | /** 4 | * saga to start given saga outside middleware with an action 5 | * --> the options object is used to define behavior of side effects 6 | * --> dispatch here fulfils put effects 7 | * --> adds dispatched actions to a list, then returns after saga is finished 8 | */ 9 | export async function recordSaga(saga, initialAction, state) { 10 | const dispatched = []; 11 | await runSaga( 12 | { 13 | dispatch: action => dispatched.push(action), 14 | getState: () => state, 15 | }, 16 | saga, 17 | initialAction, 18 | ).toPromise(); 19 | 20 | return dispatched; 21 | } 22 | -------------------------------------------------------------------------------- /src/containers/ErrorModal/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import toJson from 'enzyme-to-json'; 4 | 5 | import { ErrorModal } from './index'; 6 | 7 | 8 | const props = { 9 | errorName: 'Error', 10 | errorMessage: 'Error message', 11 | errorDescription: 'Unexpected error', 12 | closeErrorModal: () => null, 13 | }; 14 | 15 | describe('', () => { 16 | describe('on initialization', () => { 17 | it('renders page correctly', () => { 18 | const component = shallow(); 19 | const tree = toJson(component); 20 | expect(tree).toMatchSnapshot(); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /src/actions/appActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | TOGGLE_WELCOME, 3 | ADD_APP_ERROR, 4 | REMOVE_APP_ERROR, 5 | SET_CURRENT_EDITOR, 6 | } from './constants'; 7 | 8 | export const addAppError = (errorDescription, error) => ({ 9 | type: ADD_APP_ERROR, 10 | error: { 11 | errorDescription, 12 | errorName: error.name, 13 | errorMessage: error.message, 14 | } 15 | }); 16 | 17 | export const toggleWelcome = (toggle) => ({ 18 | type: TOGGLE_WELCOME, 19 | toggle, 20 | }); 21 | 22 | export const removeAppError = () => ({ 23 | type: REMOVE_APP_ERROR, 24 | }); 25 | 26 | export const setCurrentEditorAction = (id, editor) => ({ 27 | type: SET_CURRENT_EDITOR, 28 | id, 29 | editor, 30 | }); 31 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /src/containers/Error/errorReducer.js: -------------------------------------------------------------------------------- 1 | import * as R from 'ramda'; 2 | 3 | const modelErrorGather = state => R.toPairs(R.path(['modelState', 'error'], state) || {}) 4 | .map(([clauseTemplateId, modelError]) => ({ clauseTemplateId, modelError })) 5 | .filter(({ modelError }) => !R.isNil(modelError)); 6 | 7 | const parseErrorGather = state => R.toPairs(R.path(['contractState', 'clauses'], state) || {}) 8 | .map(([clauseId, clause]) => ({ clauseId, parseError: clause.parseError })) 9 | .filter(({ parseError }) => !R.isNil(parseError)); 10 | 11 | const errorsGenerator = state => ({ ...R.indexBy(R.prop('clauseTemplateId'), modelErrorGather(state)), ...R.indexBy(R.prop('clauseId'), parseErrorGather(state))}); 12 | 13 | export default errorsGenerator; 14 | -------------------------------------------------------------------------------- /src/reducers/modelReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | UPDATE_MODEL_ERROR_SUCCEEDED, 3 | UPDATE_MODEL_MANAGER_SUCCEEDED, 4 | } from '../actions/constants'; 5 | 6 | const initialState = { 7 | modelManager: null, 8 | error: null, 9 | }; 10 | 11 | const reducer = (state = initialState, action) => { 12 | switch (action.type) { 13 | case UPDATE_MODEL_MANAGER_SUCCEEDED: 14 | return { ...state, modelManager: action.modelManager }; 15 | case UPDATE_MODEL_ERROR_SUCCEEDED: 16 | return { 17 | ...state, 18 | error: { 19 | ...state.error, 20 | [action.clauseTemplateId]: 21 | action.error 22 | } 23 | }; 24 | default: 25 | return state; 26 | } 27 | }; 28 | 29 | export default reducer; 30 | -------------------------------------------------------------------------------- /src/containers/ErrorModal/__snapshots__/index.test.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[` on initialization renders page correctly 1`] = ` 4 | 14 |
17 | 18 | Unexpected error 19 | Error message 20 | 21 | 22 | 30 | 31 | 32 | `; 33 | -------------------------------------------------------------------------------- /src/containers/BaseEditors/ErgoEditor/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import CodeEditor from '../CodeEditor'; 4 | import { ERGO_FORMAT, ERGO_THEME } from './ergo'; 5 | 6 | /** 7 | * A code editing component for Ergo files 8 | * @param {*} props the properties for the component 9 | */ 10 | function ErgoEditor(props) { 11 | return (); 20 | } 21 | 22 | ErgoEditor.propTypes = { 23 | handleSubmit: PropTypes.func.isRequired, 24 | textValue: PropTypes.string.isRequired, 25 | }; 26 | 27 | export default ErgoEditor; 28 | -------------------------------------------------------------------------------- /src/reducers/templatesReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_NEW_TEMPLATE_SUCCEEDED, 3 | GET_AP_TEMPLATES_SUCEEDED, 4 | LOAD_TEMPLATE_OBJECT_SUCCEEDED, 5 | } from '../actions/constants'; 6 | 7 | const initialState = { 8 | templatesAP: [], 9 | templateObjs: {}, 10 | }; 11 | 12 | const reducer = (state = initialState, action) => { 13 | switch (action.type) { 14 | case GET_AP_TEMPLATES_SUCEEDED: 15 | return { ...state, templatesAP: action.templates }; 16 | case ADD_NEW_TEMPLATE_SUCCEEDED: 17 | return { ...state, templatesAP: [...state.templatesAP, action.template] }; 18 | case LOAD_TEMPLATE_OBJECT_SUCCEEDED: 19 | return { 20 | ...state, 21 | templateObjs: { ...state.templateObjs, [action.uri]: action.templateObj } 22 | }; 23 | default: 24 | return state; 25 | } 26 | }; 27 | 28 | export default reducer; 29 | -------------------------------------------------------------------------------- /src/containers/App/index.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import toJson from 'enzyme-to-json'; 4 | 5 | import { App } from './index'; 6 | 7 | const templates = []; 8 | const fetchAPTemplates = () => null; 9 | const addNewTemplate = () => null; 10 | const updateLogicMock = () => null; 11 | const updateModelFile = () => null; 12 | const updateSampleMock = () => null; 13 | 14 | const props = { 15 | templates, 16 | fetchAPTemplates, 17 | addNewTemplate, 18 | updateLogicMock, 19 | updateModelFile, 20 | updateSampleMock, 21 | }; 22 | 23 | describe('', () => { 24 | describe('on initialization', () => { 25 | it('renders page correctly', () => { 26 | const component = shallow(); 27 | const tree = toJson(component); 28 | expect(tree).toMatchSnapshot(); 29 | }); 30 | }); 31 | }); 32 | -------------------------------------------------------------------------------- /src/containers/BaseEditors/ConcertoEditor/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import CodeEditor from '../CodeEditor'; 4 | import { CONCERTO_FORMAT, CONCERTO_THEME } from './concerto'; 5 | 6 | /** 7 | * A code editing component for Concerto files 8 | * @param {*} props the properties for the component 9 | */ 10 | function ConcertoEditor(props) { 11 | return (); 20 | } 21 | 22 | ConcertoEditor.propTypes = { 23 | handleSubmit: PropTypes.func.isRequired, 24 | textValue: PropTypes.string.isRequired, 25 | }; 26 | 27 | export default ConcertoEditor; 28 | -------------------------------------------------------------------------------- /src/components/TextButton.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | import { AP_THEME } from '../containers/App/themeConstants'; 4 | 5 | const TextButton = styled.button` 6 | margin: 0; 7 | padding: 0; 8 | border: 0; 9 | background: transparent; 10 | font-size: ${props => (props.fontSize ? props.fontSize : 'inherit')}; 11 | color: ${props => (props.disabled ? '#B5BABE' : AP_THEME.GRAY)}; 12 | display: ${props => (props.display ? props.display : 'inline-block')}; 13 | cursor: pointer; 14 | text-decoration: underline; 15 | &:hover { 16 | color: ${AP_THEME.WHITE}; 17 | text-decoration: underline; 18 | } 19 | &:focus { 20 | outline: none; 21 | color: ${AP_THEME.WHITE}; 22 | text-decoration: underline; 23 | } 24 | &:active { 25 | color: ${AP_THEME.WHITE}; 26 | text-decoration: underline; 27 | } 28 | `; 29 | 30 | export default TextButton; 31 | -------------------------------------------------------------------------------- /src/reducers/appReducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_APP_ERROR, 3 | REMOVE_APP_ERROR, 4 | SET_CURRENT_EDITOR, 5 | TOGGLE_WELCOME, 6 | } from '../actions/constants'; 7 | 8 | 9 | 10 | const initialState = { 11 | error: null, 12 | id: null, 13 | editor: 'contract', 14 | welcome: true, 15 | }; 16 | 17 | 18 | 19 | const reducer = (state = initialState, action) => { 20 | switch (action.type) { 21 | case ADD_APP_ERROR: 22 | return { ...state, error: action.error }; 23 | case REMOVE_APP_ERROR: 24 | return { ...state, error: null }; 25 | case SET_CURRENT_EDITOR: 26 | return { 27 | ...state, 28 | id: action.id, 29 | editor: action.editor, 30 | }; 31 | case TOGGLE_WELCOME: 32 | return { 33 | ...state, 34 | welcome: action.toggle 35 | }; 36 | default: 37 | return state; 38 | } 39 | }; 40 | 41 | export default reducer; 42 | -------------------------------------------------------------------------------- /src/containers/Error/index.js: -------------------------------------------------------------------------------- 1 | /* React */ 2 | import React from 'react'; 3 | import PropTypes from 'prop-types'; 4 | import { connect } from 'react-redux'; 5 | 6 | /* Components */ 7 | import { ErrorLogger } from '@accordproject/cicero-ui'; 8 | 9 | /* Styling */ 10 | import styled from 'styled-components'; 11 | 12 | /* Utilities */ 13 | import errorsGenerator from './errorReducer'; 14 | 15 | /* Actions */ 16 | import navigateToClauseError from '../../utilities/navigateClause'; 17 | 18 | const ErrorWrapper = styled.div` 19 | width: 100%; 20 | `; 21 | 22 | const ErrorContainer = props => ( 23 | 24 | 25 | 26 | ); 27 | 28 | ErrorContainer.propTypes = { 29 | errors: PropTypes.array.isRequired, 30 | }; 31 | 32 | const mapStateToProps = state => ({ 33 | errors: errorsGenerator(state), 34 | }); 35 | 36 | export default connect(mapStateToProps)(ErrorContainer); 37 | -------------------------------------------------------------------------------- /src/actions/templatesActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_NEW_TEMPLATE, 3 | ADD_NEW_TEMPLATE_SUCCEEDED, 4 | GET_AP_TEMPLATES, 5 | GET_AP_TEMPLATES_SUCEEDED, 6 | LOAD_TEMPLATE_OBJECT, 7 | LOAD_TEMPLATE_OBJECT_SUCCEEDED, 8 | } from './constants'; 9 | 10 | export const getTemplatesAction = () => ({ 11 | type: GET_AP_TEMPLATES, 12 | }); 13 | 14 | export const getTemplatesSuccess = templateIndexArray => ({ 15 | type: GET_AP_TEMPLATES_SUCEEDED, 16 | templates: templateIndexArray, 17 | }); 18 | 19 | export const addNewTemplateAction = () => ({ 20 | type: ADD_NEW_TEMPLATE, 21 | }); 22 | 23 | export const addNewTemplateSuccess = template => ({ 24 | type: ADD_NEW_TEMPLATE_SUCCEEDED, 25 | template, 26 | }); 27 | 28 | export const loadTemplateObjectAction = uri => ({ 29 | type: LOAD_TEMPLATE_OBJECT, 30 | uri, 31 | }); 32 | 33 | export const loadTemplateObjectSuccess = (uri, templateObj) => ({ 34 | type: LOAD_TEMPLATE_OBJECT_SUCCEEDED, 35 | uri, 36 | templateObj, 37 | }); 38 | -------------------------------------------------------------------------------- /assets/SearchAlternate.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import { combineReducers, createStore, applyMiddleware } from 'redux'; 2 | import createSagaMiddleware from 'redux-saga'; 3 | import logger from 'redux-logger'; 4 | 5 | import appReducer from './reducers/appReducer'; 6 | import templatesReducer from './reducers/templatesReducer'; 7 | import modelReducer from './reducers/modelReducer'; 8 | import contractReducer from './reducers/contractReducer'; 9 | import clauseTemplatesReducer from './reducers/clauseTemplatesReducer'; 10 | import rootSaga from './sagas/rootSaga'; 11 | 12 | const sagaMiddleware = createSagaMiddleware(); 13 | const middlewares = [sagaMiddleware]; 14 | 15 | if (process.env.NODE_ENV === 'development') { 16 | middlewares.push(logger); 17 | } 18 | 19 | const rootReducer = combineReducers({ 20 | appState: appReducer, 21 | templatesState: templatesReducer, 22 | modelState: modelReducer, 23 | contractState: contractReducer, 24 | clauseTemplatesState: clauseTemplatesReducer, 25 | }); 26 | 27 | const store = createStore( 28 | rootReducer, 29 | applyMiddleware(...middlewares), 30 | ); 31 | sagaMiddleware.run(rootSaga); 32 | 33 | export default store; 34 | -------------------------------------------------------------------------------- /src/containers/BaseEditors/JsonEditor/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import ReactJson from 'react-json-view'; 4 | 5 | /** 6 | * A code editing component for JSON files 7 | * @param {*} props the properties for the component 8 | */ 9 | function JsonEditor(props) { 10 | const onEdit = (evt) => { 11 | console.log(evt.updated_src); 12 | props.handleSubmit(evt.updated_src); 13 | return true; 14 | }; 15 | 16 | const onDelete = (evt) => { 17 | console.log(evt.updated_src); 18 | props.handleSubmit(evt.updated_src); 19 | return true; 20 | }; 21 | 22 | const onAdd = (evt) => { 23 | console.log(evt.updated_src); 24 | props.handleSubmit(evt.updated_src); 25 | return true; 26 | }; 27 | 28 | return ( 29 | 36 | ); 37 | } 38 | 39 | JsonEditor.propTypes = { 40 | handleSubmit: PropTypes.func.isRequired, 41 | jsonObject: PropTypes.object.isRequired, 42 | }; 43 | 44 | export default JsonEditor; 45 | -------------------------------------------------------------------------------- /src/containers/ClauseTemplate/ClauseMetadataEditor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | import { editClausePackageJsonAction } from '../../actions/clauseTemplatesActions'; 5 | import JsonEditor from '../BaseEditors/JsonEditor'; 6 | 7 | /** 8 | * A code editing component for package.json files 9 | * which wraps the Json editor 10 | * @param {*} props the properties for the component 11 | */ 12 | const ClauseMetadataEditor = props => ( 13 | props.onClausePackageJsonChange(props.clauseTemplateid, value)} 15 | jsonObject={props.value} 16 | /> 17 | ); 18 | 19 | ClauseMetadataEditor.propTypes = { 20 | onClausePackageJsonChange: PropTypes.func.isRequired, 21 | value: PropTypes.object, 22 | clauseTemplateid: PropTypes.string, 23 | }; 24 | 25 | const mapStateToProps = state => ({ 26 | value: state.clauseTemplatesState[state.appState.id].metadata.packageJson, 27 | clauseTemplateid: state.appState.id, 28 | }); 29 | 30 | const mapDispatchToProps = dispatch => ({ 31 | onClausePackageJsonChange: (...args) => dispatch(editClausePackageJsonAction(...args)) 32 | }); 33 | 34 | export default connect(mapStateToProps, mapDispatchToProps)(React.memo(ClauseMetadataEditor)); 35 | -------------------------------------------------------------------------------- /src/containers/App/themeConstants.js: -------------------------------------------------------------------------------- 1 | export const AP_THEME = { 2 | DARK_BLUE: '#141F3C', 3 | DARK_BLUE_MEDIUM: '#182444', 4 | DARK_BLUE_LIGHT: '#1E2D53', 5 | CYAN: '#19C6C7', 6 | GRAY: '#B9BCC4', 7 | LIGHT_GRAY: '#F0F0F0', 8 | WHITE: '#FFFFFF', 9 | }; 10 | 11 | export const CONTENT_BACKGROUND = AP_THEME.DARK_BLUE; 12 | 13 | export const FILE_BAR = { 14 | OPTION_BUTTONS: AP_THEME.WHITE, 15 | OPTION_BUTTONS_HOVER: AP_THEME.CYAN, 16 | BACKGROUND: AP_THEME.DARK_BLUE_LIGHT, 17 | }; 18 | 19 | export const LEFT_NAV = { 20 | HEADING: AP_THEME.GRAY, 21 | }; 22 | 23 | export const CONTRACT_EDITOR = { 24 | BUTTON_BACKGROUND_INACTIVE: AP_THEME.DARK_BLUE_LIGHT, 25 | BUTTON_BACKGROUND_ACTIVE: '#364C77', 26 | BUTTON_BACKGROUND_HOVER: '#364C77', 27 | BUTTON_SYMBOL_INACTIVE: '#949CA2', 28 | BUTTON_SYMBOL_ACTIVE: AP_THEME.WHITE, 29 | TOOLBAR_BACKGROUND: AP_THEME.DARK_BLUE_LIGHT, 30 | TOOLTIP_BACKGROUND: AP_THEME.CYAN, 31 | TOOLTIP: AP_THEME.DARK_BLUE_MEDIUM, 32 | DIVIDER: '#46608E', 33 | WIDTH: '620px', 34 | }; 35 | 36 | export const TEMPLATE_LIBRARY = { 37 | HEADER_TITLE: '#939EBA', 38 | ACTION_BUTTON: AP_THEME.CYAN, 39 | ACTION_BUTTON_BACKGROUND: AP_THEME.DARK_BLUE_MEDIUM, 40 | ACTION_BUTTON_BORDER: '#7B8FAD', 41 | TEMPLATE_BACKGROUND: AP_THEME.DARK_BLUE_LIGHT, 42 | TEMPLATE_TITLE: AP_THEME.GRAY, 43 | TEMPLATE_DESCRIPTION: AP_THEME.WHITE, 44 | }; 45 | -------------------------------------------------------------------------------- /src/containers/CurrentEditor/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { connect } from 'react-redux'; 4 | 5 | import ClauseTemplateGrammarEditor from '../ClauseTemplate/ClauseGrammarEditor'; 6 | import ClauseExampleTextEditor from '../ClauseTemplate/ClauseExampleTextEditor'; 7 | import ContractEditor from '../ContractEditor'; 8 | import ClauseModelEditor from '../ClauseTemplate/ClauseModelEditor'; 9 | import ClauseLogicEditor from '../ClauseTemplate/ClauseLogicEditor'; 10 | import ClauseMetadataEditor from '../ClauseTemplate/ClauseMetadataEditor'; 11 | 12 | const CurrentEditor = (props) => { 13 | const { editor } = props; 14 | switch (editor) { 15 | case 'contract': 16 | return (); 17 | case 'clauseTemplateGrammar': 18 | return (); 19 | case 'clauseExampleText': 20 | return (); 21 | case 'clauseModel': 22 | return (); 23 | case 'clauseLogic': 24 | return (); 25 | case 'clauseMetadata': 26 | return (); 27 | default: 28 | return ; 29 | } 30 | }; 31 | 32 | CurrentEditor.propTypes = { 33 | editor: PropTypes.string.isRequired, 34 | }; 35 | 36 | const mapStateToProps = state => ({ 37 | editor: state.appState.editor, 38 | }); 39 | 40 | export default connect(mapStateToProps)(CurrentEditor); 41 | -------------------------------------------------------------------------------- /src/containers/Header/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import styled from 'styled-components'; 3 | 4 | import image from '../../../public/img/logo.png'; 5 | 6 | import { AP_THEME } from '../App/themeConstants'; 7 | 8 | const HeaderWrapper = styled.div` 9 | display: flex; 10 | height: 37px; 11 | width: 100%; 12 | background-color: ${AP_THEME.DARK_BLUE}; 13 | color: white; 14 | justify-content:space-between; 15 | padding: 0 15px; 16 | `; 17 | 18 | const HeaderTitle = styled.div` 19 | display: flex; 20 | align-items: center; 21 | margin-left: 30px; 22 | padding-right: 20px; 23 | text-align: center; 24 | `; 25 | 26 | const HeaderLeft = styled.div` 27 | display: flex; 28 | `; 29 | 30 | const HeaderImage = styled.img` 31 | height: 27px; 32 | `; 33 | 34 | 35 | const HeaderLink = styled.a` 36 | display:flex; 37 | color: white; 38 | align-items: center; 39 | `; 40 | const Header = () => ( 41 | 42 | 43 | 44 | 45 | 46 | Template Studio 47 | 48 | Contribute on Github 49 | 50 | 51 | ); 52 | 53 | export default Header; 54 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Accord Project Template Studio 12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | -------------------------------------------------------------------------------- /src/actions/clauseTemplatesActions.js: -------------------------------------------------------------------------------- 1 | import { 2 | ADD_CLAUSE_TEMPLATE, 3 | REMOVE_CLAUSE_TEMPLATE, 4 | EDIT_CLAUSE_GRAMMAR, 5 | EDIT_CLAUSE_LOGIC, 6 | EDIT_CLAUSE_MODEL, 7 | EDIT_CLAUSE_MODEL_SUCCESS, 8 | EDIT_CLAUSE_PACKAGE_JSON, 9 | EDIT_CLAUSE_SAMPLE, 10 | } from './constants'; 11 | 12 | export const addClauseTemplate = clauseTemplate => ({ 13 | type: ADD_CLAUSE_TEMPLATE, 14 | clauseTemplate, 15 | }); 16 | 17 | export const removeClauseTemplate = clauseId => ({ 18 | type: REMOVE_CLAUSE_TEMPLATE, 19 | clauseId 20 | }); 21 | 22 | export const editClauseGrammarAction = (clauseTemplateId, grammar) => ({ 23 | type: EDIT_CLAUSE_GRAMMAR, 24 | clauseTemplateId, 25 | grammar, 26 | }); 27 | 28 | export const editClauseSampleAction = (clauseTemplateId, sample) => ({ 29 | type: EDIT_CLAUSE_SAMPLE, 30 | clauseTemplateId, 31 | sample, 32 | }); 33 | 34 | export const editClauseModelAction = (clauseTemplateId, fileName, content) => ({ 35 | type: EDIT_CLAUSE_MODEL, 36 | clauseTemplateId, 37 | fileName, 38 | content, 39 | }); 40 | 41 | export const editClauseModelSuccess = (clauseTemplateId, fileName, content) => ({ 42 | type: EDIT_CLAUSE_MODEL_SUCCESS, 43 | clauseTemplateId, 44 | fileName, 45 | content, 46 | }); 47 | 48 | export const editClauseLogicAction = (clauseTemplateId, fileName, content) => ({ 49 | type: EDIT_CLAUSE_LOGIC, 50 | clauseTemplateId, 51 | fileName, 52 | content, 53 | }); 54 | 55 | export const editClausePackageJsonAction = (clauseTemplateId, packageJson) => ({ 56 | type: EDIT_CLAUSE_PACKAGE_JSON, 57 | clauseTemplateId, 58 | packageJson, 59 | }); 60 | -------------------------------------------------------------------------------- /src/containers/BaseEditors/CodeEditor/index.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import MonacoEditor from 'react-monaco-editor'; 3 | import PropTypes from 'prop-types'; 4 | 5 | /** 6 | * A code editing component which wraps the Monaco editor 7 | * @param {*} props the properties for the component 8 | */ 9 | function CodeEditor(props) { 10 | const [code, setCode] = useState(null); 11 | 12 | const editorWillMount = ((monaco) => { 13 | monaco.languages.register({ id: props.languageId }); 14 | monaco.languages.setMonarchTokensProvider(props.languageId, props.languageFormat); 15 | monaco.editor.defineTheme(props.themeId, props.theme); 16 | }); 17 | 18 | const editorDidMount = ((editor, monaco) => { 19 | editor.focus(); 20 | }); 21 | 22 | const onChange = (newValue, e) => { 23 | setCode(newValue); 24 | props.handleSubmit(newValue); 25 | }; 26 | 27 | return ( 28 | ); 37 | } 38 | 39 | CodeEditor.propTypes = { 40 | handleSubmit: PropTypes.func.isRequired, 41 | textValue: PropTypes.string.isRequired, 42 | languageId: PropTypes.string.isRequired, 43 | languageFormat: PropTypes.object, 44 | themeId: PropTypes.string.isRequired, 45 | theme: PropTypes.object, 46 | monacoOptions: PropTypes.object, 47 | debounceInterval: PropTypes.number.isRequired, 48 | }; 49 | 50 | export default CodeEditor; 51 | -------------------------------------------------------------------------------- /src/containers/ErrorModal/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import propTypes from 'prop-types'; 4 | import styled from 'styled-components'; 5 | import { Modal, Header, Button } from 'semantic-ui-react'; 6 | import { removeAppError } from '../../actions/appActions'; 7 | 8 | const StyledContent = styled(Modal.Content)` 9 | white-space: pre; 10 | `; 11 | 12 | export const ErrorModal = props => ( 13 | 18 |
19 | 20 | {`${props.errorDescription}\n${props.errorMessage}`} 21 | 22 | 23 | 26 | 27 | 28 | ); 29 | 30 | ErrorModal.propTypes = { 31 | errorMessage: propTypes.string, 32 | errorName: propTypes.string, 33 | errorDescription: propTypes.string, 34 | closeErrorModal: propTypes.func.isRequired, 35 | }; 36 | 37 | const mapStateToProps = state => ({ 38 | errorName: state.appState.error ? state.appState.error.errorName : null, 39 | errorMessage: state.appState.error ? state.appState.error.errorMessage : null, 40 | errorDescription: state.appState.error ? state.appState.error.errorDescription : null, 41 | }); 42 | 43 | const mapDispatchToProps = dispatch => ({ 44 | closeErrorModal: () => dispatch(removeAppError()), 45 | }); 46 | 47 | export default connect(mapStateToProps, mapDispatchToProps)(React.memo(ErrorModal)); 48 | -------------------------------------------------------------------------------- /src/containers/ClauseTemplate/ClauseGrammarEditor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styled from 'styled-components'; 4 | import { connect } from 'react-redux'; 5 | import { TextArea } from 'semantic-ui-react'; 6 | import { editClauseGrammarAction } from '../../actions/clauseTemplatesActions'; 7 | 8 | const EditorWrapper = styled.div` 9 | overflow-y: auto; 10 | overflow-x: hidden; 11 | scrollbar-color: rgba(0,0,0,.25) rgba(0,0,0,.1); 12 | &::-webkit-scrollbar { 13 | width: 6px; 14 | background: transparent; 15 | }; 16 | justify-self: center; 17 | width: 594px; 18 | `; 19 | 20 | /** 21 | * A text editing component for clause template grammar 22 | * @param {*} props the properties for the component 23 | */ 24 | const ClauseGrammarEditor = props => ( 25 | 26 |