├── .gitignore
├── Dockerfile
├── Procfile
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
└── src
├── App.css
├── App.js
├── App.test.js
├── Components
├── Attribute.jsx
├── BottomNavigation.jsx
├── Children.jsx
├── Condition.jsx
├── CurrentRules.jsx
├── CustomRowAction.jsx
├── MyModal.jsx
├── Property.jsx
├── Rule.jsx
└── TopNavigation.jsx
├── data.js
├── index.css
├── index.js
├── logo.svg
├── pollyfills.js
└── registerServiceWorker.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # build:
2 |
3 | FROM node:10
4 |
5 | # The Dockerfile's author
6 | LABEL Alec Maly
7 |
8 | # Create app directory
9 | WORKDIR /usr
10 |
11 |
12 | # Install app dependencies
13 | # A wildcard is used to ensure both package.json AND package-lock.json are copied
14 | # where available (npm@5+)
15 | COPY package.json ./
16 | COPY ./public ./public
17 | COPY ./src ./src
18 |
19 | RUN npm install
20 | # If you are building your code for production
21 | # RUN npm ci --only=production
22 |
23 | # RUN npm run
24 |
25 | # Bundle app source
26 | # COPY . .
27 |
28 | EXPOSE 3000
29 | CMD [ "npm", "start" ]
30 |
31 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: npm start
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Website
2 | Site is hosted on [Heroku](http://sharepoint-json-helper.alecmaly.com/)
3 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "sharepoint-helper",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "bootstrap": "^4.4.1",
7 | "react": "^16.4.1",
8 | "react-dom": "^16.13.1",
9 | "react-popper": "^1.0.0",
10 | "react-router-dom": "^4.3.1",
11 | "react-scripts": "1.1.4",
12 | "reactstrap": "^6.2.0"
13 | },
14 | "scripts": {
15 | "start": "react-scripts start",
16 | "build": "react-scripts build",
17 | "test": "react-scripts test --env=jsdom",
18 | "eject": "react-scripts eject"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/alecmaly/SharePoint-JSON-Helper/14f5103593494f973593cd5791c9119263b1e327/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
28 |
29 |
30 |
39 | SharePoint JSON Formatter
40 |
41 |
42 |
43 | You need to enable JavaScript to run this app.
44 |
45 |
46 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/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 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-title {
18 | font-size: 1.5em;
19 | }
20 |
21 | .App-intro {
22 | font-size: large;
23 | }
24 |
25 | .banner {
26 | padding-top: 3em;
27 | }
28 |
29 | body {
30 | padding-top: 5px;
31 | padding-bottom: 65px;
32 | background: linear-gradient(to bottom right, lightblue, lightseagreen) fixed;
33 | }
34 |
35 | .App {
36 | border-radius: 10px;
37 | background-color: #CBCBCB;
38 | background: linear-gradient(to bottom right, #CBCBCB, lightgrey);
39 | }
40 |
41 | .text-center {
42 | text-align: center;
43 | }
44 |
45 | .rule {
46 | cursor: pointer;
47 | padding-top: 3px;
48 | padding-bottom: 3px;
49 | border-style: solid;
50 | border-color: #606060;
51 | user-select: none;
52 | }
53 |
54 | .rules-container {
55 | border: 2px solid black;
56 | height: 150px;
57 | overflow: auto;
58 | }
59 |
60 | .delete-property {
61 | color: #ff6666;
62 | font-size: 200%;
63 | padding-right: .5em;
64 | }
65 |
66 | .delete-property:hover {
67 | color: #cc0000;
68 | }
69 |
70 | .operator {
71 | text-align: center;
72 | min-width: 5em;
73 | max-width: 5em;
74 | }
75 |
76 | .operand::placeholder {
77 | color: #b4b4b4;
78 | }
79 |
80 | .output {
81 | height: 22em;
82 | }
83 |
84 | .color {
85 | min-width: 6em;
86 | max-width: 10em;
87 | }
88 |
89 | .column {
90 | max-width: 5em;
91 | }
92 |
93 | .display-block {
94 | display: block;
95 | }
96 |
97 | .center-input {
98 | margin-left: auto;
99 | margin-right: auto;
100 | display: block;
101 | }
102 |
103 | .icon {
104 | cursor: pointer;
105 | user-select: none;
106 | }
107 |
108 | .new-property-link {
109 | font-size: 19px;
110 | color: #5078FF;
111 | text-decoration: underline;
112 | cursor: pointer;
113 | }
114 |
115 | .wrap-value {
116 | height: 2.5em;
117 | }
118 |
119 | .help-link {
120 | font-size: 19px;
121 | color: #5078FF;
122 | cursor: pointer;
123 | }
124 |
125 | .new-property-link:hover {
126 | color: blue;
127 | }
128 |
129 | .remove-text-highlighting {
130 | user-select: none;
131 | }
132 |
133 | .label {
134 | font-size: 13pt;
135 | padding-top: .5em;
136 | }
137 |
138 | .nav-bar {
139 | font-size: 15pt;
140 | text-decoration: none;
141 | color: grey;
142 | user-select: none;
143 | }
144 |
145 |
146 | .property:nth-child(odd) {
147 | background-color: lightgrey;
148 | }
149 |
150 | .property:nth-child(even) {
151 | background-color: #B8B8B8;
152 | }
153 |
154 | .nav-bar-top {
155 | padding-left: 1em;
156 | padding-right: 1em;
157 | font-size: 14pt;
158 | }
159 |
160 | .nav-button {
161 | background-color: #d3d3d3;
162 | border-radius: 10px;
163 | min-width: 7em;
164 | margin-left: 1em;
165 | margin-right: 1em;
166 | }
167 |
168 | .nav-button:hover {
169 | background-color: #B8B8B8;
170 | }
171 |
172 | .field-type {
173 | max-width: 15em;
174 | }
175 |
176 | .text-content {
177 | max-width: 15em;
178 | }
179 |
180 | .padded-row {
181 | padding: .75em .75em;
182 | }
183 |
184 | .add-remove-property-button {
185 | min-width: 12em;
186 | margin: 4px;
187 | }
188 |
189 | .condition-button {
190 | margin: 4px;
191 | margin-left: auto;
192 | margin-right: auto;
193 | display: block;
194 | }
195 |
196 | .modal-ok-button {
197 | width: 7em;
198 | }
199 |
200 | .fa {
201 | padding: .5em;
202 | font-size: 25px;
203 | width: 1.75em;
204 | height: 1.75em;
205 | text-align: center;
206 | text-decoration: none !important;
207 | margin: 4px;
208 | margin-bottom: 0px;
209 | border-radius: 50%;
210 | }
211 |
212 | .fa:hover {
213 | opacity: 0.7;
214 | }
215 |
216 | .fa-facebook {
217 | background: #3B5998;
218 | color: white;
219 | }
220 |
221 | .fa-twitter {
222 | background: #55ACEE;
223 | color: white;
224 | }
225 |
226 | .fa-linkedin {
227 | background: #007bb5;
228 | color: white;
229 | }
230 |
231 | .side-info {
232 | background-color: lightblue;
233 | width: 1.5em;
234 | min-height: 6em;
235 | position: relative;
236 | cursor: default;
237 | }
238 |
239 | .child-nav {
240 | border: 2px solid rgb(175, 144, 230);
241 | background-color: rgb(182, 161, 219);
242 | border-radius: 15px;
243 | width: 7em;
244 |
245 | }
246 |
247 | .move-button {
248 | width: 10em;
249 | border: 2px solid rgb(175, 144, 230);
250 | background-color: rgb(182, 161, 219);
251 | }
252 |
253 | .side-info span {
254 | /* Change to flexbox later */
255 | top: 50%;
256 | left: .75em;
257 | transform-origin: left;
258 | position: absolute;
259 | text-overflow: hidden;
260 | white-space: nowrap;
261 | transform: rotate(-90deg) translate(-2em, 0);
262 | }
263 |
264 | .properties-color {
265 | border-radius: 5px;
266 | background-color: lightblue;
267 | }
268 |
269 | .attributes-color {
270 | border-radius: 5px;
271 | background-color: lightcoral;
272 | }
273 |
274 | .children-color {
275 | border-radius: 10px;
276 | background: linear-gradient(ivory, lightpink);
277 | background-color: lightpink;
278 | }
279 |
280 |
281 | .customRowActions-color {
282 | border-radius: 5px;
283 | background-color: lightgreen;
284 | }
285 |
286 |
287 | @media (min-width: 992px){
288 | .nav-button {
289 | width: 15em;
290 | margin-left : 5em;
291 | margin-right : 5em;
292 | }
293 | }
294 |
295 | @media (max-width: 500px){
296 | .nav-button {
297 | margin-left: .5em;
298 | margin-right: .5em;
299 | }
300 |
301 | .nav-button.copy {
302 | width: 9em;
303 | }
304 | }
305 |
306 | @keyframes App-logo-spin {
307 | from { transform: rotate(0deg); }
308 | to { transform: rotate(360deg); }
309 | }
310 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import './App.css';
4 | import TopNavigation from './Components/TopNavigation.jsx';
5 | import BottomNavigation from './Components/BottomNavigation.jsx';
6 | import CurrentRules from './Components/CurrentRules.jsx';
7 | import Attribute from './Components/Attribute.jsx';
8 | import Property from './Components/Property.jsx';
9 | import Children from './Components/Children.jsx';
10 | import CustomRowAction from './Components/CustomRowAction.jsx';
11 | import Condition from './Components/Condition.jsx';
12 | import MyModal from './Components/MyModal.jsx';
13 |
14 |
15 | // polyfill for .repeat function in IE11
16 | import './pollyfills.js';
17 |
18 | // data
19 | import data from './data.js';
20 |
21 | import {
22 | Container,
23 | Jumbotron,
24 | Navbar,
25 | NavbarBrand,
26 | Nav,
27 | NavItem,
28 | Row,
29 | Col,
30 | Button,
31 | Input,
32 | Label,
33 | Modal,
34 | ModalHeader,
35 | ModalBody,
36 | ModalFooter
37 | } from 'reactstrap';
38 | import { SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER } from 'constants';
39 |
40 | class App extends Component {
41 |
42 | constructor(props) {
43 | super(props);
44 | this.navButtonClick = this.navButtonClick.bind(this);
45 | this.newKey = this.newKey.bind(this);
46 | this.updateKey = this.updateKey.bind(this);
47 | this.buildKey = this.buildKey.bind(this);
48 | this.deleteKey = this.deleteKey.bind(this);
49 | this.clearAllKeys = this.clearAllKeys.bind(this);
50 | this.handleInputChange = this.handleInputChange.bind(this);
51 | this.resetForm = this.resetForm.bind(this);
52 | this.toggleModal = this.toggleModal.bind(this);
53 | this.displayModal = this.displayModal.bind(this);
54 | this.moveChild = this.moveChild.bind(this);
55 |
56 | this.buildJSON = this.buildJSON.bind(this);
57 | // helper functions for buildJSON()
58 | this.buildValue = this.buildValue.bind(this);
59 | this.parseString = this.parseString.bind(this);
60 | this.parseFunctions = this.parseFunctions.bind(this);
61 | this.parseMathOperations = this.parseMathOperations.bind(this);
62 | this.validParen = this.validParen.bind(this);
63 |
64 |
65 | //temp
66 | this.updateAttribute = this.updateAttribute.bind(this);
67 | this.updateProperty = this.updateProperty.bind(this);
68 | this.updateCRA = this.updateCRA.bind(this);
69 | this.updateChildren = this.updateChildren.bind(this);
70 | // end temp
71 |
72 |
73 | this.state = {
74 | attributes: [],
75 | properties: [],
76 | customRowActions: [],
77 | children: [],
78 | JSON: '',
79 | elmType: 'div',
80 | fieldType: 'Choice',
81 | textContent: '@currentField',
82 | modal: false,
83 | modalHeader: '',
84 | modalBody: '',
85 | modalTab: '1',
86 | attributeChoices: data.Attributes,
87 | propertyChoices: data.CSSProperties,
88 | customRowActionChoices: data.customRowActions,
89 | colors: data.customColors
90 | };
91 | }
92 |
93 | componentDidMount() {
94 | this.buildJSON();
95 | }
96 |
97 |
98 | moveChild(ele, index, obj) {
99 | let arr = this.state.children.slice();
100 | let temp = arr[index];
101 |
102 |
103 | switch (ele.target.attributes.value.value) {
104 | case 'top':
105 | arr.splice(index, 1);
106 | arr.splice(0, 0, temp);
107 | break;
108 | case 'up':
109 | arr.splice(index, 1);
110 | arr.splice((index === 0 ? 0 : index-1), 0, temp);
111 | break;
112 | case 'down':
113 | arr.splice(index, 1);
114 | arr.splice(index+1, 0, temp);
115 | break;
116 | case 'bottom':
117 | arr.splice(index, 1);
118 | arr.splice(arr.length, 0, temp);
119 | break;
120 | }
121 | //arr.splice(index, 0, temp);
122 |
123 | this.setState({
124 | children: arr
125 | }, this.buildJSON())
126 |
127 | }
128 |
129 | newKey(key) {
130 | let arr = [];
131 | if (key === 'addChildren') {
132 | key = 'children';
133 | arr = this.state[key].slice();
134 | arr.push({'name':'', 'value':[]});
135 | }
136 | else {
137 | arr = this.state[key].slice();
138 | console.log(arr);
139 | arr.splice(0,0, {'name': '', 'value':[]});
140 | }
141 |
142 | if (key === 'children')
143 | this.setState({ textContent: '' });
144 |
145 | this.buildKey(key, arr);
146 | }
147 |
148 | updateKey(key, index, name, value) {
149 | let arr = this.state[key].slice();
150 | arr[index] = ({'name': name, 'value': value});
151 | this.setState({
152 | [key]: arr
153 | }, () => { this.buildJSON() });
154 | }
155 |
156 | clearAllKeys(key) {
157 | this.setState({
158 | [key]: []
159 | }, () => { this.buildJSON() } );
160 | }
161 |
162 | // This function builds properties from arr input.
163 | // Needed to build this way for state to reset properly.
164 | updateAttribute(index, key, value) {
165 | this.updateKey('attributes', index, key, value);
166 | }
167 |
168 | updateProperty(index, key, value) {
169 | this.updateKey('properties', index, key, value);
170 | }
171 |
172 | updateCRA(index, key, value) {
173 | this.updateKey('customRowActions', index, key, value);
174 | }
175 |
176 | updateChildren(index, key, value) {
177 | this.updateKey('children', index, key, value);
178 | }
179 |
180 |
181 | buildKey(key, arr) {
182 | this.setState({
183 | [key]: []
184 | }, () => {
185 | this.setState({
186 | [key]: arr
187 | }, () => { this.buildJSON() })
188 | });
189 | }
190 |
191 | deleteKey(key, index) {
192 | var arr = this.state[key].slice();
193 |
194 | var deleteAtt = arr[index];
195 | arr.splice(index, 1);
196 | this.setState({
197 | [key]: []
198 | }, () => {
199 | this.setState({
200 | [key]: arr
201 | }, () => { this.buildJSON() });
202 | });
203 | }
204 |
205 |
206 | navButtonClick(event) {
207 | let arr = [];
208 | switch (event.target.name) {
209 | case 'copy to clipboard':
210 | var copyText = document.querySelector(".output");
211 | copyText.select();
212 | document.execCommand("copy");
213 | this.setState({
214 | modalHeader: 'Copy to Clipboard',
215 | modalBody: ' JSON has been copied to the clipboard!'
216 | })
217 | this.toggleModal();
218 | break;
219 | case 'reset':
220 | this.resetForm();
221 | break;
222 | case 'attribute':
223 | arr = this.state.attributes.slice();
224 | arr.splice(0, 0, {'name': event.target.value, 'value': event.target.title});
225 | this.buildKey('attributes', arr);
226 | break;
227 | case 'property':
228 | arr = this.state.properties.slice();
229 | arr.splice(0, 0, {'name': event.target.value, 'value': event.target.title});
230 | this.buildKey('properties', arr);
231 | break;
232 | case 'CRA':
233 | arr = this.state.customRowActions.slice();
234 | arr.splice(0, 0, {'name': "actionParams", 'value': "{\\\"id\\\": \\\"FLOW_ID\\\"}"});
235 | arr.splice(0, 0, {'name': "action", 'value': "executeFlow"});
236 | this.buildKey('customRowActions', arr);
237 | break;
238 |
239 |
240 |
241 | case 'template':
242 | switch (event.target.value) {
243 | case 'Completed/In Progress/Late':
244 | this.resetForm();
245 | arr = data.template_completedInProgressLate;
246 | this.buildKey('properties', arr.properties);
247 | break;
248 | case 'Data Bars 1':
249 | this.resetForm();
250 | arr = data.template_dataBars_one;
251 |
252 | this.buildKey('properties', arr.properties);
253 | this.buildKey('attributes', arr.attributes);
254 | break;
255 | case 'Data Bars 100':
256 | this.resetForm();
257 | arr = data.template_dataBars_hundred;
258 |
259 | this.buildKey('properties', arr.properties);
260 | this.buildKey('attributes', arr.attributes);
261 | break;
262 | case 'Button with link + icon':
263 | this.resetForm();
264 | arr = data.template_buttonWithLinkandIcon;
265 |
266 | this.setState({'elmType': 'button', textContent: ''})
267 | this.buildKey('properties', arr.properties);
268 | this.buildKey('children', arr.children)
269 | break;
270 | }
271 | }
272 | }
273 |
274 | resetForm() {
275 | this.setState({
276 | elmType: 'div',
277 | fieldType: 'Choice',
278 | textContent: '@currentField'
279 | });
280 | this.clearAllKeys('attributes');
281 | this.clearAllKeys('properties');
282 | this.clearAllKeys('customRowActions');
283 | this.clearAllKeys('children');
284 | window.scrollTo(0, 0); // scroll to top of screen
285 | }
286 |
287 |
288 | parseMathOperations(operator, str, indent) {
289 | let value = str;
290 | switch (operator) {
291 | case '//':
292 | if (str.includes('https://') || str.includes('http://'))
293 | break;
294 | default:
295 | if (str.toString().includes(operator)) {
296 | let temp_value = '\t'.repeat(++indent) + '"operator": "' + operator.charAt(1) + '",\n' + '\t'.repeat(++indent) + ' "operands": [\n';
297 | indent++;
298 | str.split(operator).forEach((val, i) => { temp_value = temp_value + (this.state.fieldType !== 'Number' ? '\t'.repeat(indent) + this.parseString(val, indent) : this.parseString(val, indent).slice(1, -1)) + ',\n' });
299 | indent--;
300 | // remove , from last item in list and add closing brackets
301 | value = '{\n' + temp_value.slice(0,-2) + (str.toString().split(operator.charAt(1)).length === 1 ? ',\n' + '\t'.repeat(indent + 1) + '""' : '') + '\n' + '\t'.repeat(indent) + ']\n}';
302 | }
303 | }
304 |
305 | return value;
306 | }
307 |
308 | parseFunctions(str, indent) {
309 | let f = '';
310 | let value = str;
311 | let acceptedFunctions = ['toString', 'Number', 'Date', 'cos', 'sin', 'toLocaleString', 'toLocaleDateString', 'toLocaleTimeString'];
312 |
313 | // set index to innermost '('
314 | let index = str.lastIndexOf('(');
315 |
316 | // before(inner)after
317 | let before = str.substring(0, index - f.length + 1);
318 | let inner = str.substring(index + 1, str.indexOf(')', index));
319 | let after = str.substring(str.indexOf(')', index) + 1, str.length);
320 |
321 | // check what function is being called, remove function name from 'before' variable
322 | for (var i = 0; i < acceptedFunctions.length; i++) {
323 | if (before.slice(-acceptedFunctions[i].length - 1, -1) === acceptedFunctions[i]) {
324 | f = acceptedFunctions[i];
325 | before = before.slice(0, -acceptedFunctions[i].length - 1);
326 | break;
327 | }
328 | }
329 |
330 | // check what function is being called - f === '' is no funciton - () order of operations
331 | if (f === '') {
332 | before = before.slice(0, -1);
333 | str = this.parseString(inner, indent);//this.parseString(inner, index) + after;
334 |
335 | value = this.parseString(before + '~' + after, indent).replace('~', str);
336 | //value = value.slice(1, -1);
337 | } else { // order of operations w/ () - not a function call
338 | let prefix = '\t'.repeat(++indent) + '"operator": "' + f + '()",\n' + '\t'.repeat(++indent) + ' "operands": [\n';
339 |
340 | str = this.parseString(inner, indent);//this.parseString(inner, index) + after;
341 | str = prefix + str;
342 |
343 | value = '{\n' + '\t'.repeat(++indent) + str + '\n' + '\t'.repeat(indent) + ']\n}';
344 | value = this.parseString(before + '~' + after, indent).replace('~', value);
345 | }
346 |
347 | return value;
348 | }
349 |
350 | // validate proper function (prevents stack overflow)
351 | validParen(str) {
352 | let arr = [];
353 | for (let i = 0; i < str.length; i++) {
354 | if (str.charAt(i) === '(')
355 | arr.push('(');
356 | else if (str.charAt(i) === ')')
357 | arr.pop();
358 | }
359 | return (arr.length === 0 ? true : false);
360 | }
361 |
362 | parseString(str, indent) {
363 |
364 | // replace '%5F' with '_' inside variable names
365 | if (str !== undefined) {
366 | str = str.replace(/\[\$(.*?)%5F(.*?)\]/g, (match) =>
367 | {
368 | return match.replace(/%5F/g, '_')
369 | });
370 | }
371 |
372 | let value = '"' + str + '"';
373 |
374 | // parses functions and () order of operations
375 | if (this.validParen(str) && str.indexOf('(') >= 0) {
376 | value = value.replace(str, this.parseFunctions(str, indent));
377 | }
378 | value = value.replace(str, this.parseMathOperations('--', str, indent));
379 | value = value.replace(str, this.parseMathOperations('++', str, indent));
380 | value = value.replace(str, this.parseMathOperations('//', str, indent));
381 | value = value.replace(str, this.parseMathOperations('**', str, indent));
382 |
383 |
384 | // backslashes for Flow actionParameters command
385 | if ( value.charAt(1) === '{' || value === '"~"') {
386 | value = value.slice(1, -1);
387 | }
388 |
389 |
390 | // add " back to Flow Parameters
391 | if (value.includes("{\\\"id\\\": \\\""))
392 | value = '"' + value + '"';
393 |
394 | return value;
395 | }
396 |
397 | buildValue(type, obj, indent) {
398 | let output = '';
399 | let key = '';
400 | switch (type) {
401 | case 'attribute':
402 | output = '\t'.repeat(indent) + '"attributes": {';
403 | break;
404 | case 'property':
405 | output = '\t'.repeat(indent) + '"style": {';
406 | break;
407 | case 'customRowActions':
408 | output = output = '\t'.repeat(indent) + '"customRowAction": {';
409 | break;
410 | case 'children':
411 | output = output = '\t'.repeat(indent) + '"children": [';
412 | this.state[type].forEach((obj) => {
413 | output = output + obj.value + ', ';
414 | });
415 | if (this.state[type].length > 0)
416 | output = output.slice(0, -2);
417 | output = output + ']';
418 | break;
419 | }
420 |
421 | if (type !== 'children') {
422 |
423 | obj.forEach((ele, i) => {
424 | // NEED TO CHECK IF RULES/CONDITIONS ARE APPLIED OR NOT (FIX HERE)
425 | // craft value
426 | let value = '';
427 |
428 |
429 | if (typeof ele.value === 'string') {
430 | if (!(ele.name === 'class' || ele.name === 'iconName')) {
431 | value = this.parseString(ele.value, indent);
432 | } else {
433 | value = '"' + ele.value + '"';
434 | }
435 | }
436 |
437 | // craft value
438 | output = output + `
439 | "` + ele.name + '": ' + value;
440 |
441 |
442 | if (typeof ele.value === 'object') {
443 | ele.value.forEach( (condition) => {
444 | output = output + `\n
445 | {
446 | "operator": "?",
447 | "operands": [
448 | {
449 | "operator": "` + condition.operator + `",
450 | "operands": [
451 | ` + (this.state.fieldType !== 'Number' ? this.parseString(condition.operand, indent) : this.parseString(condition.operand, indent).slice(1, -1)) + `,
452 | ` + (this.state.fieldType !== 'Number' ? this.parseString(condition.operand2, indent) : this.parseString(condition.operand2, indent).slice(1, -1)) + `
453 | ]
454 | },
455 | ` + this.parseString(condition.value, indent) + `, `
456 | })
457 | }
458 |
459 | //
460 | output = output + '""'.repeat( (typeof ele.value === 'object' ? 1 : 0) );
461 |
462 | // add closing brackets based on number of properties being evaluated
463 | output = output + `\n\t]
464 | }`.repeat( (typeof ele.value === 'string' ? 0 : ele.value.length) );
465 |
466 | // add commas for all properties until the last one
467 | output = output + ','.repeat( (i !== obj.length - 1 ? 1 : 0) );
468 | });
469 |
470 | output = output + '\n' + '\t'.repeat(indent) + '}';
471 | }
472 |
473 | return output;
474 | }
475 |
476 |
477 | buildJSON() {
478 | // console.log(this.state)
479 | let indent = 0;
480 | var JSON_Body = ``;
481 | var JSON_Header =
482 | `{
483 | "elmType": "` + this.state.elmType + `",
484 | "txtContent": ` + this.parseString(this.state.textContent, indent) + `,
485 | `;
486 | var JSON_Footer = ``;
487 | var JSON_Properties = '';
488 | var JSON_Attributes = '';
489 | var JSON_CustomRowActions = '';
490 | var JSON_Children = '';
491 |
492 | // BUILD JSON HERE in forEach loop for ATTRIBUTES
493 | indent++;
494 |
495 | JSON_Properties = JSON_Properties + this.buildValue('property', this.state.properties, indent);
496 | JSON_Attributes = JSON_Attributes + this.buildValue('attribute', this.state.attributes, indent);
497 | JSON_CustomRowActions = JSON_CustomRowActions + this.buildValue('customRowActions', this.state.customRowActions, indent);
498 | JSON_Children = JSON_Children + this.buildValue('children', this.state.children, indent);
499 |
500 | // JSON Footer
501 | JSON_Footer = `\n}`;
502 |
503 | // build body of properties and attributes
504 | JSON_Body = JSON_Attributes + ",\n" + JSON_Properties + ",\n" + JSON_CustomRowActions + ",\n" + JSON_Children ;
505 |
506 | // Set Output
507 | this.setState({
508 | JSON: JSON_Header + JSON_Body + JSON_Footer
509 | });
510 | }
511 |
512 | handleInputChange(event) {
513 | const target = event.target;
514 | const value = target.type === 'checkbox' ? target.checked : target.value;
515 | const name = target.name;
516 | this.setState({
517 | [name]: value
518 | }, () => { this.buildJSON() });
519 | }
520 |
521 | toggleModal() {
522 | this.setState({
523 | modal: !this.state.modal
524 | });
525 | }
526 |
527 | displayModal(event) {
528 | switch(event.target.attributes.value.value) {
529 | case 'text content help':
530 | this.setState({
531 | modalHeader: 'Text Context Help',
532 | modalBody: ' This value will be displayed in each cell',
533 | modalTab: '1'
534 | }, () => { this.toggleModal() } )
535 | break;
536 | case 'iconName':
537 | this.setState({
538 | modalHeader: 'iconName Help',
539 | modalBody: `
540 | Use SharePoint Fabric Icons .
541 |
542 | Don't forget to check the left nav for more icons!
543 | Brand icons
544 | Localization `,
545 | modalTab: '1'
546 | }, () => { this.toggleModal() } )
547 | break;
548 | case 'class':
549 | this.setState({
550 | modalHeader: 'class Help',
551 | modalBody: `
552 | Here you can use SharePoint Framework classes.
553 | Don't forget about Animations and
554 | Typography !
555 | `,
556 | modalTab: '1'
557 | }, () => { this.toggleModal() } )
558 | break;
559 | case 'Operand':
560 | this.setState({
561 | modalHeader: 'Operand Help',
562 | modalBody: ' This value will be compared with Operand2, uses Value when true.',
563 | modalTab: '1'
564 | }, () => { this.toggleModal() } )
565 | break;
566 | case 'Operand2':
567 | this.setState({
568 | modalHeader: 'Operand Help',
569 | modalBody: ' This value will be compared with Operand, uses Value when true.',
570 | modalTab: '1'
571 | }, () => { this.toggleModal() } )
572 | break;
573 |
574 | default:
575 | this.setState({
576 | modalHeader: 'Value Help',
577 | modalBody: ` Enter the value to use.`,
578 | modalTab: '1'
579 | }, () => { this.toggleModal() } )
580 | break;
581 |
582 | }
583 | }
584 |
585 | render() {
586 | return (
587 |
588 |
589 |
590 |
591 |
592 |
593 | SharePoint Helper
594 | Used for building conditional formatting JSON - YouTube Tutorial
595 | Note: Rules are read from top to bottom.
596 |
597 |
598 |
599 |
600 |
Share
601 |
602 |
603 |
604 |
605 |
606 |
607 |
608 |
609 |
610 |
611 |
612 |
613 |
614 |
615 |
616 |
617 | {/* */}
618 |
619 | Element Type
620 |
621 | div
622 | span
623 | a
624 | img
625 | svg
626 | path
627 | button
628 |
629 |
630 |
631 | {/*
632 |
633 | Field Type
634 |
635 | Choice
636 | Text
637 | Number
638 | Date
639 |
640 |
641 | */}
642 |
643 | {/* */}
644 |
645 | Text Content (help)
646 |
647 |
648 |
649 |
650 |
651 | {/* Attributes */}
652 |
653 |
654 | Attributes
655 |
656 |
657 |
658 |
659 | this.newKey('attributes')}>New Attribute
660 |
661 |
662 | 0 ? 'Visible' : 'hidden'}} onClick={() => this.clearAllKeys('attributes')}>Clear All Attributes
663 |
664 |
665 |
666 |
667 | {Object.keys(this.state.attributes).map((key, i) => {
668 | return ( )
669 | })}
670 |
671 |
672 |
673 |
674 |
675 | {/* Properties */}
676 |
677 |
678 | Properties
679 |
680 |
681 |
682 |
683 | this.newKey('properties')}>New CSS Property
684 |
685 |
686 | 0 ? 'Visible' : 'hidden'}} onClick={() => this.clearAllKeys('properties')}>Clear All CSS Properties
687 |
688 |
689 |
690 |
691 | {Object.keys(this.state.properties).map((key, i) => {
692 | return ( )
693 | })}
694 |
695 |
696 |
697 |
698 |
699 | {/* Custom Row Action */}
700 |
701 |
702 | Row Actions
703 |
704 |
705 |
706 |
707 | this.newKey('customRowActions')}>New Custom Row Action
708 |
709 |
710 | 0 ? 'Visible' : 'hidden'}} onClick={() => this.clearAllKeys('customRowActions')}>Clear All Custom Row Actions
711 |
712 |
713 |
714 |
715 | {Object.keys(this.state.customRowActions).map((key, i) => {
716 | return (
717 | )
718 | })}
719 |
720 |
721 |
722 |
723 |
724 | {/* Children */}
725 |
726 |
727 | {this.state.children.length !== 0 ?
728 |
729 |
730 | this.newKey('children')}>Insert Child
731 |
732 |
733 | 0 ? 'Visible' : 'hidden'}} onClick={() => this.clearAllKeys('children')}>Clear All Children
734 |
735 |
736 |
737 | : '' }
738 |
739 |
740 |
741 | {Object.keys(this.state.children).map((key, i) => {
742 | return ( )
743 | })}
744 |
745 |
746 |
747 |
748 |
749 | this.newKey('addChildren')}>New Child
750 |
751 |
752 | 0 ? 'Visible' : 'hidden'}} onClick={() => this.clearAllKeys('children')}>Clear All Children
753 |
754 |
755 |
756 |
757 |
758 |
759 |
760 |
761 |
762 |
763 |
764 |
765 |
766 |
767 |
768 |
769 | );
770 | }
771 | }
772 |
773 | export default App;
774 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render( , div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/Components/Attribute.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | Row,
4 | Col,
5 | Label,
6 | Input,
7 | InputGroup,
8 | InputGroupButtonDropdown,
9 | DropdownMenu,
10 | DropdownToggle,
11 | DropdownItem,
12 | Container,
13 | Button
14 | } from 'reactstrap';
15 | import Condition from './Condition.jsx';
16 |
17 |
18 |
19 | class Attribute extends Component {
20 | constructor(props) {
21 | super(props);
22 | this.setValue = this.setValue.bind(this);
23 | this.handleInputChange = this.handleInputChange.bind(this);
24 | this.toggleAttributeDropdownOpen = this.toggleAttributeDropdownOpen.bind(this);
25 | this.toggleValueDropdownOpen = this.toggleValueDropdownOpen.bind(this);
26 | this.toggleConditionals = this.toggleConditionals.bind(this);
27 | this.moveRule = this.moveRule.bind(this);
28 | this.state = {
29 | key: this.props.attributes[this.props.index].name,
30 | value: this.props.attributes[this.props.index].value,
31 | conditionalFlag: (typeof this.props.attributes[this.props.index].value === 'object' && this.props.attributes[this.props.index].value.length ? true : false), // True if rules have been defined, otherwise false
32 | keyDropdownOpen: false,
33 | valueDropdownOpen: false,
34 | prevIndex: this.props.index
35 | };
36 | }
37 |
38 | componentDidUpdate() {
39 | if (this.state.prevIndex !== this.props.index) {
40 | this.setState({
41 | key: this.props.attributes[this.props.index].name,
42 | value: this.props.attributes[this.props.index].value,
43 | conditionalFlag: (typeof this.props.attributes[this.props.index].value === 'object' && this.props.attributes[this.props.index].value.length ? true : false), // True if rules have been defined, otherwise false
44 | keyDropdownOpen: false,
45 | valueDropdownOpen: false,
46 | prevIndex: this.props.index
47 | }, () => { this.props.buildJSON() });
48 | }
49 | }
50 |
51 | // called inside Condition component
52 | moveRule(ele, index) {
53 | let arr = this.state.value.slice();
54 | let temp = arr[index];
55 |
56 | console.log(arr);
57 | switch(ele.target.attributes.value.value) {
58 | case 'up':
59 | arr.splice(index, 1);
60 | arr.splice((index === 0 ? 0 : index-1), 0, temp);
61 | break;
62 | case 'down':
63 | arr.splice(index, 1);
64 | arr.splice(index+1, 0, temp);
65 | break;
66 | }
67 |
68 |
69 | this.setState({
70 | value: arr
71 | }, () => { this.props.updateAttribute(this.props.index, this.state.key, this.state.value) });
72 | }
73 |
74 |
75 | setValue(obj) {
76 | this.setState({
77 | value: obj
78 | })
79 | }
80 |
81 |
82 | toggleConditionals() {
83 | this.setState({
84 | conditionalFlag: !this.state.conditionalFlag,
85 | value: []
86 | }, () => { this.props.updateAttribute(this.props.index, this.state.key, this.state.value) });
87 | }
88 |
89 | handleInputChange(event) {
90 | const target = event.target;
91 | const name = target.name;
92 |
93 | let resetValue = '';
94 |
95 | if (name === 'key') {
96 | let value = target.type === 'button' ? target.innerHTML : target.value;
97 | resetValue = [];
98 | this.setState({
99 | [name]: value,
100 | value: resetValue
101 | }, () => { this.props.updateAttribute(this.props.index, value, '') })
102 |
103 | } else {
104 | let value = target.type === 'button' ? ( this.state.key === 'background-color' ? this.props.colors[target.innerHTML] : target.innerHTML) : target.value;
105 | resetValue = value;
106 | this.setState({
107 | [name]: value,
108 | value: resetValue
109 | }, () => { this.props.updateAttribute(this.props.index, this.state.key, this.state.value) })
110 | }
111 |
112 | }
113 |
114 | setConditionalValue(eleValue) {
115 | this.setState({
116 | value: eleValue
117 | });
118 | }
119 |
120 | toggleAttributeDropdownOpen() {
121 | this.setState({
122 | value: '',
123 | keyDropdownOpen: !this.state.keyDropdownOpen
124 | });
125 | }
126 |
127 | toggleValueDropdownOpen() {
128 | this.setState({
129 | valueDropdownOpen: !this.state.valueDropdownOpen
130 | });
131 | }
132 |
133 | render() {
134 | return (
135 |
136 |
137 |
138 |
139 |
140 | this.props.deleteKey('attributes', this.props.index)}>X
141 |
142 |
143 |
144 | Attribute (help)
145 |
146 |
147 |
148 |
149 |
150 | {Object.keys(this.props.attributeChoices).map((key, i) => {
151 | return ({key} );
152 | })}
153 |
154 |
155 |
156 |
157 |
158 |
159 | {!this.state.conditionalFlag ?
160 |
161 | Value (help)
162 |
163 |
164 |
166 |
167 |
168 | { (this.props.attributeChoices[this.state.key] !== undefined && this.props.attributeChoices[this.state.key].options !== undefined
169 | ? this.props.attributeChoices[this.state.key].options.split(',').map((key, i) => {
170 | return ({key} );
171 | }) : (this.state.key === 'background-color' ? Object.keys(this.props.colors).map((key, i) => {
172 | return ({key} );
173 | })
174 | : '') )}
175 |
176 |
177 |
178 |
179 | : ''}
180 |
181 |
182 |
183 |
184 | {!this.state.conditionalFlag ? 'Add Condition' : 'Remove Conditions'}
185 |
186 |
187 |
188 | {this.state.conditionalFlag ? : ''}
189 |
190 |
191 | )
192 | }
193 | }
194 |
195 | export default Attribute;
--------------------------------------------------------------------------------
/src/Components/BottomNavigation.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | Navbar,
4 | NavbarBrand,
5 | Nav,
6 | NavItem,
7 | NavLink,
8 | DropdownToggle,
9 | DropdownMenu,
10 | DropdownItem,
11 | UncontrolledDropdown,
12 | Button
13 | } from 'reactstrap';
14 | import '../App.css';
15 |
16 | class BotttomNavigation extends Component {
17 | render() {
18 | return (
19 |
20 |
21 |
22 |
23 | Copy to Clipboard
24 |
25 |
26 |
27 | Reset
28 |
29 |
30 |
31 |
32 | )
33 | }
34 |
35 | }
36 |
37 | export default BotttomNavigation;
--------------------------------------------------------------------------------
/src/Components/Children.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | import '../App.css';
4 | import TopNavigation from './TopNavigation.jsx';
5 | import BottomNavigation from './BottomNavigation.jsx';
6 | import CurrentRules from './CurrentRules.jsx';
7 | import Property from './Property.jsx';
8 | import Attribute from './Attribute.jsx';
9 | import CustomRowAction from './CustomRowAction.jsx';
10 | import Condition from './Condition.jsx';
11 | import MyModal from './MyModal.jsx';
12 |
13 |
14 | // polyfill for .repeat function in IE11
15 | import '../pollyfills.js';
16 |
17 | // data
18 | import data from '../data.js';
19 |
20 | import {
21 | Container,
22 | Jumbotron,
23 | Navbar,
24 | NavbarBrand,
25 | Nav,
26 | NavItem,
27 | Row,
28 | Col,
29 | Button,
30 | Input,
31 | Label,
32 | Modal,
33 | ModalHeader,
34 | ModalBody,
35 | ModalFooter
36 | } from 'reactstrap';
37 | import { SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER } from 'constants';
38 |
39 | class Children extends Component {
40 |
41 | constructor(props) {
42 | super(props);
43 | this.newKey = this.newKey.bind(this);
44 | this.updateKey = this.updateKey.bind(this);
45 | this.buildKey = this.buildKey.bind(this);
46 | this.deleteKey = this.deleteKey.bind(this);
47 | this.clearAllKeys = this.clearAllKeys.bind(this);
48 | this.handleInputChange = this.handleInputChange.bind(this);
49 | this.resetForm = this.resetForm.bind(this);
50 |
51 | this.buildJSON = this.buildJSON.bind(this);
52 | // helper functions for buildJSON()
53 | this.buildValue = this.buildValue.bind(this);
54 | this.parseString = this.parseString.bind(this);
55 | this.parseFunctions = this.parseFunctions.bind(this);
56 | this.parseMathOperations = this.parseMathOperations.bind(this);
57 | this.validParen = this.validParen.bind(this);
58 |
59 | //temp
60 | this.updateAttribute = this.updateAttribute.bind(this);
61 | this.updateProperty = this.updateProperty.bind(this);
62 | this.updateCRA = this.updateCRA.bind(this);
63 | // end temp
64 |
65 | this.state = {
66 | attributes: this.props.children[this.props.index].name.attributes || [],
67 | properties: this.props.children[this.props.index].name.properties || [],
68 | customRowActions: this.props.children[this.props.index].name.customRowActions || [],
69 | JSON: this.props.children[this.props.index].name.JSON || '{}',
70 | elmType: this.props.children[this.props.index].name.elmType || 'span',
71 | fieldType: this.props.children[this.props.index].name.fieldType || 'Choice',
72 | textContent: this.props.children[this.props.index].name.textContent,
73 | resetChildren: this.props.resetChildren,
74 | modal: false,
75 | modalHeader: '',
76 | modalBody: '',
77 | modalTab: '1',
78 | attributeChoices: data.Attributes,
79 | propertyChoices: data.CSSProperties,
80 | customRowActionChoices: data.customRowActions,
81 | colors: data.customColors,
82 | prevIndex: this.props.index
83 | };
84 | }
85 |
86 | // FIX - Children not moving up and down - textContent not resetting properly
87 |
88 | componentDidMount() {
89 | this.buildJSON();
90 | }
91 |
92 |
93 | componentDidUpdate() {
94 | // build JSON for updated children
95 | if (this.props.children[this.props.index].name.length === 0)
96 | this.buildJSON();
97 |
98 |
99 | if (this.state.JSON !== this.props.children[this.props.index].value) {
100 | this.setState({
101 | attributes: this.props.children[this.props.index].name.attributes || [],
102 | properties: this.props.children[this.props.index].name.properties || [],
103 | customRowActions: this.props.children[this.props.index].name.customRowActions || [],
104 | JSON: this.props.children[this.props.index].name.JSON || '{}',
105 | elmType: this.props.children[this.props.index].name.elmType || 'span',
106 | fieldType: this.props.children[this.props.index].name.fieldType || 'Choice',
107 | textContent: this.props.children[this.props.index].name.textContent || (this.props.children[this.props.index].name.textContent === undefined ? '@currentField' : ''),
108 |
109 | prevIndex: this.props.index
110 | }, () => { this.props.buildJSON();});
111 | }
112 |
113 |
114 | }
115 |
116 |
117 |
118 | newKey(key) {
119 | var arr = this.state[key].slice();
120 |
121 | arr.splice(0,0, {'name': '', 'value':[]});
122 | this.buildKey(key, arr);
123 | }
124 |
125 | updateKey(key, index, name, value) {
126 | var arr = this.state[key].slice();
127 | arr[index] = ({'name': name, 'value': value});
128 | this.setState({
129 | [key]: arr
130 | }, () => { this.buildJSON() });
131 | }
132 |
133 | clearAllKeys(key) {
134 | this.setState({
135 | [key]: []
136 | }, () => { this.buildJSON() } );
137 | }
138 |
139 | // This function builds properties from arr input.
140 | // Needed to build this way for state to reset properly.
141 | updateAttribute(index, key, value) {
142 | this.updateKey('attributes', index, key, value);
143 | }
144 |
145 | updateProperty(index, key, value) {
146 | this.updateKey('properties', index, key, value);
147 | }
148 |
149 | updateCRA(index, prop, value) {
150 | this.updateKey('customRowActions', index, prop, value);
151 | }
152 |
153 |
154 | buildKey(key, arr) {
155 | this.setState({
156 | [key]: []
157 | }, () => {
158 | this.setState({
159 | [key]: arr
160 | }, () => { this.buildJSON() })
161 | });
162 | }
163 |
164 | deleteKey(key, index) {
165 | var arr = this.state[key].slice();
166 |
167 | var deleteAtt = arr[index];
168 | arr.splice(index, 1);
169 | this.setState({
170 | [key]: []
171 | }, () => {
172 | this.setState({
173 | [key]: arr
174 | }, () => { this.buildJSON() });
175 | });
176 | }
177 |
178 | resetForm() {
179 | this.setState({
180 | elmType: 'div',
181 | fieldType: 'Choice',
182 | textContent: '@currentField'
183 | });
184 | this.clearAllKeys('attributes');
185 | this.clearAllKeys('properties');
186 | this.clearAllKeys('customRowActions');
187 | window.scrollTo(0, 0); // scroll to top of screen
188 | }
189 |
190 |
191 | parseMathOperations(operator, str, indent) {
192 | let value = str;
193 | switch (operator) {
194 | case '//':
195 | if (str.includes('https://') || str.includes('http://'))
196 | break;
197 | default:
198 | if (str.toString().includes(operator)) {
199 | let temp_value = '\t'.repeat(++indent) + '"operator": "' + operator.charAt(1) + '",\n' + '\t'.repeat(++indent) + ' "operands": [\n';
200 | indent++;
201 | str.split(operator).forEach((val, i) => { temp_value = temp_value + (this.state.fieldType !== 'Number' ? '\t'.repeat(indent) + this.parseString(val, indent) : this.parseString(val, indent).slice(1, -1)) + ',\n' });
202 | indent--;
203 | // remove , from last item in list and add closing brackets
204 | value = '{\n' + temp_value.slice(0,-2) + (str.toString().split(operator.charAt(1)).length === 1 ? ',\n' + '\t'.repeat(indent + 1) + '""' : '') + '\n' + '\t'.repeat(indent) + ']\n}';
205 | }
206 | }
207 |
208 | return value;
209 | }
210 |
211 | parseFunctions(str, indent) {
212 | let f = '';
213 | let value = str;
214 | let acceptedFunctions = ['toString', 'Number', 'Date', 'cos', 'sin', 'toLocaleString', 'toLocaleDateString', 'toLocaleTimeString'];
215 |
216 | // set index to innermost '('
217 | let index = str.lastIndexOf('(');
218 |
219 | // before(inner)after
220 | let before = str.substring(0, index - f.length + 1);
221 | let inner = str.substring(index + 1, str.indexOf(')', index));
222 | let after = str.substring(str.indexOf(')', index) + 1, str.length);
223 |
224 | // check what function is being called, remove function name from 'before' variable
225 | for (var i = 0; i < acceptedFunctions.length; i++) {
226 | if (before.slice(-acceptedFunctions[i].length - 1, -1) === acceptedFunctions[i]) {
227 | f = acceptedFunctions[i];
228 | before = before.slice(0, -acceptedFunctions[i].length - 1);
229 | break;
230 | }
231 | }
232 |
233 | // check what function is being called - f === '' is no funciton - () order of operations
234 | if (f === '') {
235 | before = before.slice(0, -1);
236 | str = this.parseString(inner, indent);//this.parseString(inner, index) + after;
237 |
238 | value = this.parseString(before + '~' + after, indent).replace('~', str);
239 | //value = value.slice(1, -1);
240 | } else { // order of operations w/ () - not a function call
241 | let prefix = '\t'.repeat(++indent) + '"operator": "' + f + '()",\n' + '\t'.repeat(++indent) + ' "operands": [\n';
242 |
243 | str = this.parseString(inner, indent);//this.parseString(inner, index) + after;
244 | str = prefix + str;
245 |
246 | value = '{\n' + '\t'.repeat(++indent) + str + '\n' + '\t'.repeat(indent) + ']\n}';
247 | value = this.parseString(before + '~' + after, indent).replace('~', value);
248 | }
249 |
250 | return value;
251 | }
252 |
253 | // validate proper function (prevents stack overflow)
254 | validParen(str) {
255 | let arr = [];
256 | for (let i = 0; i < str.length; i++) {
257 | if (str.charAt(i) === '(')
258 | arr.push('(');
259 | else if (str.charAt(i) === ')')
260 | arr.pop();
261 | }
262 | return (arr.length === 0 ? true : false);
263 | }
264 |
265 | parseString(str, indent) {
266 | if (str === undefined)
267 | str = '';
268 | // replace '%5F' with '_' inside variable names
269 | str = str.replace(/\[\$(.*?)%5F(.*?)\]/g, (match) =>
270 | {
271 | return match.replace(/%5F/g, '_')
272 | });
273 |
274 |
275 |
276 | let value = '"' + str + '"';
277 |
278 | // parses functions and () order of operations
279 | if (this.validParen(str) && str.indexOf('(') >= 0) {
280 | value = value.replace(str, this.parseFunctions(str, indent));
281 | }
282 | value = value.replace(str, this.parseMathOperations('--', str, indent));
283 | value = value.replace(str, this.parseMathOperations('++', str, indent));
284 | value = value.replace(str, this.parseMathOperations('//', str, indent));
285 | value = value.replace(str, this.parseMathOperations('**', str, indent));
286 |
287 |
288 | // backslashes for Flow actionParameters command
289 | if ( value.charAt(1) === '{' || value === '"~"') {
290 | value = value.slice(1, -1);
291 | }
292 |
293 |
294 | // add " back to Flow Parameters
295 | if (value.includes("{\\\"id\\\": \\\""))
296 | value = '"' + value + '"';
297 |
298 | return value;
299 | }
300 |
301 | buildValue(type, obj, indent) {
302 | let output = '';
303 | let key = '';
304 | switch (type) {
305 | case 'attribute':
306 | output = '\t'.repeat(indent) + '"attributes": {';
307 | break;
308 | case 'property':
309 | output = '\t'.repeat(indent) + '"style": {';
310 | break;
311 | case 'customRowActions':
312 | output = output = '\t'.repeat(indent) + '"customRowAction": {';
313 | break;
314 | }
315 |
316 | obj.forEach((ele, i) => {
317 | // NEED TO CHECK IF RULES/CONDITIONS ARE APPLIED OR NOT (FIX HERE)
318 | // craft value
319 | let value = '';
320 |
321 | // FIX: CHANGE ele.attribute => ele.name when changing
322 | if (typeof ele.value === 'string') {
323 | if (!(ele.name === 'class' || ele.name === 'iconName')) {
324 | value = this.parseString(ele.value, indent);
325 | } else {
326 | value = '"' + ele.value + '"';
327 | }
328 | }
329 | // craft value
330 | output = output + `
331 | "` + ele.name + '": ' + value;
332 |
333 | if (typeof ele.value === 'object') {
334 | // false
335 | ele.value.forEach( (condition) => {
336 | output = output + `\n
337 | {
338 | "operator": "?",
339 | "operands": [
340 | {
341 | "operator": "` + condition.operator + `",
342 | "operands": [
343 | ` + (this.state.fieldType !== 'Number' ? this.parseString(condition.operand, indent) : this.parseString(condition.operand, indent).slice(1, -1)) + `,
344 | ` + (this.state.fieldType !== 'Number' ? this.parseString(condition.operand2, indent) : this.parseString(condition.operand2, indent).slice(1, -1)) + `
345 | ]
346 | },
347 | ` + this.parseString(condition.value, indent) + `, `
348 | })
349 | // end false
350 | }
351 |
352 | //
353 | output = output + '""'.repeat( (typeof ele.value === 'object' ? 1 : 0) );
354 |
355 | // add closing brackets based on number of properties being evaluated
356 | output = output + `\n\t]
357 | }`.repeat( (typeof ele.value === 'string' ? 0 : ele.value.length) );
358 |
359 | // add commas for all properties until the last one
360 | output = output + ','.repeat( (i !== obj.length - 1 ? 1 : 0) );
361 | });
362 |
363 | output = output + '\n' + '\t'.repeat(indent) + '}';
364 |
365 | return output;
366 | }
367 |
368 |
369 | buildJSON() {
370 | let indent = 0;
371 | var JSON_Body = ``;
372 | var JSON_Header =
373 | `{
374 | "elmType": "` + this.state.elmType + `",
375 | "txtContent": ` + this.parseString(this.state.textContent, indent) + `,
376 | `;
377 | var JSON_Footer = ``;
378 | var JSON_Properties = '';
379 | var JSON_Attributes = '';
380 | var JSON_CustomRowActions = '';
381 |
382 |
383 | // BUILD JSON HERE in forEach loop for ATTRIBUTES
384 | indent++;
385 |
386 | JSON_Properties = JSON_Properties + this.buildValue('property', this.state.properties, indent);
387 | JSON_Attributes = JSON_Attributes + this.buildValue('attribute', this.state.attributes, indent);
388 | JSON_CustomRowActions = JSON_CustomRowActions + this.buildValue('customRowActions', this.state.customRowActions, indent);
389 |
390 | // JSON Footer
391 | JSON_Footer = `\n}`;
392 |
393 | // build body of properties and attributes
394 | JSON_Body = JSON_Attributes + ",\n" + JSON_Properties + ",\n" + JSON_CustomRowActions ;
395 |
396 | // Set Output
397 | this.setState({
398 | JSON: JSON_Header + JSON_Body + JSON_Footer
399 | }, () => { this.props.updateChildren(this.props.index, this.state, this.state.JSON); this.props.buildJSON() });
400 |
401 | }
402 |
403 | handleInputChange(event) {
404 | const target = event.target;
405 | const value = target.type === 'checkbox' ? target.checked : target.value;
406 | const name = target.name;
407 | this.setState({
408 | [name]: value
409 | }, () => { this.buildJSON() });
410 | }
411 |
412 |
413 |
414 |
415 | render() {
416 | return (
417 |
418 |
419 |
420 |
421 |
422 | Child {this.props.index + 1}
423 |
424 |
425 |
426 |
427 |
428 | this.props.deleteKey('children', this.props.index)}>X
429 |
430 |
431 |
432 |
433 | this.props.moveChild(ele, this.props.index, this) }>To Top
434 |
435 |
436 |
437 |
438 | this.props.moveChild(ele, this.props.index, this)}>Move Up
439 |
440 |
441 |
442 | Child {this.props.index + 1}
443 |
444 |
445 |
446 | this.props.moveChild(ele, this.props.index, this)}>Move Down
447 |
448 |
449 |
450 |
451 | this.props.moveChild(ele, this.props.index, this)}>To Bottom
452 |
453 |
454 |
455 |
456 |
457 |
458 |
459 | {/* */}
460 |
461 | Element Type
462 |
463 | div
464 | span
465 | a
466 | img
467 | svg
468 | path
469 | button
470 |
471 |
472 |
473 | {/* */}
474 |
475 | Text Content (help)
476 |
477 |
478 |
479 |
480 |
481 | {/* Attributes */}
482 |
483 |
484 | Attributes
485 |
486 |
487 |
488 |
489 | this.newKey('attributes')}>New Attribute
490 |
491 |
492 | 0 ? 'Visible' : 'hidden'}} onClick={() => this.clearAllKeys('attributes')}>Clear All Attributes
493 |
494 |
495 |
496 |
497 | {Object.keys(this.state.attributes).map((key, i) => {
498 | return ( )
499 | })}
500 |
501 |
502 |
503 |
504 |
505 | {/* Properties */}
506 |
507 |
508 | Properties
509 |
510 |
511 |
512 |
513 |
514 | this.newKey('properties')}>New CSS Property
515 |
516 |
517 | 0 ? 'Visible' : 'hidden'}} onClick={() => this.clearAllKeys('properties')}>Clear All CSS Properties
518 |
519 |
520 |
521 |
522 | {Object.keys(this.state.properties).map((key, i) => {
523 | return ( )
524 | })}
525 |
526 |
527 |
528 |
529 |
530 | {/* Custom Row Action */}
531 |
532 |
533 | Row Actions
534 |
535 |
536 |
537 |
538 | this.newKey('customRowActions')}>New Custom Row Action
539 |
540 |
541 | 0 ? 'Visible' : 'hidden'}} onClick={() => this.clearAllKeys('customRowActions')}>Clear All Custom Row Actions
542 |
543 |
544 |
545 |
546 | {Object.keys(this.state.customRowActions).map((key, i) => {
547 | return ( )
548 | })}
549 |
550 |
551 |
552 |
553 |
554 |
555 |
556 | );
557 | }
558 | }
559 |
560 | export default Children;
561 |
--------------------------------------------------------------------------------
/src/Components/Condition.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | Container,
4 | Col,
5 | Row,
6 | Button,
7 | Input,
8 | Label,
9 | InputGroup,
10 | DropdownMenu,
11 | InputGroupButtonDropdown,
12 | DropdownToggle,
13 | DropdownItem
14 | } from 'reactstrap';
15 | import CurrentRules from './CurrentRules.jsx';
16 |
17 |
18 | class Condition extends Component {
19 | constructor(props) {
20 | super(props);
21 | this.handleInputChange = this.handleInputChange.bind(this);
22 | this.newRule = this.newRule.bind(this);
23 | this.editRule = this.editRule.bind(this);
24 | this.selectRule = this.selectRule.bind(this);
25 | this.deleteRule = this.deleteRule.bind(this);
26 | this.clearRules = this.clearRules.bind(this);
27 | this.toggleValueDropdownOpen = this.toggleValueDropdownOpen.bind(this);
28 | this.state = {
29 | operator: '==',
30 | operand: '@currentField',
31 | operand2: '',
32 | value: '',
33 | selectedRule: '',
34 | rules: this.props.rules,
35 | valueDropdownOpen: false,
36 | reset: this.props.reset
37 | };
38 | }
39 |
40 | componentDidUpdate() {
41 | if (this.props.resetRules)
42 | this.setState({
43 | rules: this.props.rules
44 | })
45 |
46 | if (this.state.reset)
47 | this.clearRules;
48 |
49 |
50 | }
51 |
52 |
53 | newRule() {
54 | var arr = this.state.rules.slice();
55 | arr.length > 0 ? '' : arr = [];
56 | arr.push({operator: this.state.operator, operand: this.state.operand, operand2: this.state.operand2, value: this.state.value});
57 | this.setState({
58 | rules: arr,
59 | value: ''
60 | }, () => { this.props.updateKey(this.props.index, this.props.name, this.state.rules) });
61 | this.props.setValue(arr);
62 | }
63 |
64 | selectRule(index) {
65 | switch (index) {
66 | case this.state.selectedRule:
67 | this.setState({
68 | selectedRule: '',
69 | operand: ''
70 | });
71 | break;
72 |
73 | default:
74 | var rule = this.state.rules[index];
75 | this.setState({
76 | selectedRule: index,
77 | operator: rule.operator,
78 | operand: rule.operand,
79 | operand2: rule.operand2,
80 | value: rule.value
81 | });
82 |
83 | }
84 |
85 | }
86 |
87 | editRule() {
88 | var arr = this.state.rules.slice();
89 | arr[this.state.selectedRule].operator = this.state.operator;
90 | arr[this.state.selectedRule].operand = this.state.operand;
91 | arr[this.state.selectedRule].operand2 = this.state.operand2;
92 | arr[this.state.selectedRule].value = this.state.value;
93 | this.props.setValue(arr);
94 | this.setState({
95 | rules: arr
96 | }, () => { this.props.updateKey(this.props.index, this.props.name, this.state.rules) });
97 |
98 | }
99 |
100 | deleteRule() {
101 | if (this.state.selectedRule !== '') {
102 | var arr = this.state.rules;
103 | var deleted_Value = arr[this.state.selectedRule].value;
104 | arr.splice(this.state.selectedRule, 1);
105 | this.props.setValue(arr);
106 | this.setState({
107 | rules: arr,
108 | operand: '@currentField',
109 | operand2: '',
110 | value: deleted_Value,
111 | selectedRule: ''
112 | }, () => { this.props.updateKey(this.props.index, this.props.name, this.state.rules) });
113 | }
114 | }
115 |
116 | clearRules() {
117 | this.props.setValue([]);
118 | this.setState({
119 | rules: [],
120 | operand: '@currentField',
121 | operand2: '',
122 | selectedRule: ''
123 | }, () => { this.props.updateKey(this.props.index, this.props.name, this.state.rules) });
124 | }
125 |
126 |
127 |
128 | handleInputChange(event) {
129 | const target = event.target;
130 | let value = target.type === 'button' ? ( this.props.name === 'background-color' ? this.props.colors[target.innerHTML] : target.innerHTML) : target.value;
131 | const name = target.name;
132 | this.setState({
133 | [name]: value,
134 | });
135 |
136 |
137 | }
138 |
139 | toggleValueDropdownOpen() {
140 | this.setState({
141 | valueDropdownOpen: !this.state.valueDropdownOpen
142 | });
143 | }
144 |
145 | render() {
146 | return (
147 |
148 |
149 |
150 | Operand (help)
151 |
152 |
153 |
154 | Operator
155 |
156 | ==
157 | !=
158 | <
159 | >
160 | <=
161 | >=
162 |
163 |
164 |
165 | Operand2 (help)
166 |
167 |
168 |
169 | Value (help)
170 |
171 |
173 |
175 |
176 |
177 |
178 | { (this.props.nameChoices[this.props.name] !== undefined && this.props.nameChoices[this.props.name].options !== undefined
179 | ? this.props.nameChoices[this.props.name].options.split(',').map((key, i) => {
180 | return ({key} );
181 | }) : (this.props.name === 'background-color' ? Object.keys(this.props.colors).map((key, i) => {
182 | return ({key} );
183 | })
184 | : '') )}
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 | {this.state.selectedRule === '' ? 'New Condition' : 'Edit Condition'}
195 |
196 |
197 | Clear All Conditions
198 |
199 |
200 |
201 |
202 | Condtions ({this.props.name}) {this.state.selectedRule === '' ? '*Click to Select*' : '*Click to Deselect*'}
203 |
204 |
205 |
206 |
207 |
208 | { this.props.moveRule(ele, this.state.selectedRule); this.setState({ selectedRule: (this.state.selectedRule === 0 ? 0 : this.state.selectedRule-1)}); } }>Move Rule Up
209 |
210 |
211 | Delete Condition
212 |
213 |
214 | { this.props.moveRule(ele, this.state.selectedRule); this.setState({ selectedRule: (this.state.selectedRule === this.state.rules.length-1 ? this.state.selectedRule : this.state.selectedRule+1)}); }}>Move Rule Down
215 |
216 |
217 |
218 |
219 |
220 | );
221 | }
222 | }
223 |
224 | export default Condition;
--------------------------------------------------------------------------------
/src/Components/CurrentRules.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import Rule from './Rule.jsx';
3 |
4 | class CurrentRules extends Component {
5 |
6 | render() {
7 | return (
8 |
9 | {/* Only check if rules exist, print if they do */}
10 | {typeof this.props.rules === 'object' && this.props.rules.length > 0 ? this.props.rules.map((ele, i) => {
11 | var text = ele.operand + ' ' + ele.operator + ' ' + ele.operand2 + ' (' + ele.value + ')';
12 | return (
13 | )
14 | }) : ''}
15 |
16 | )
17 | }
18 | }
19 |
20 |
21 | export default CurrentRules;
--------------------------------------------------------------------------------
/src/Components/CustomRowAction.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | Row,
4 | Col,
5 | Label,
6 | Input,
7 | InputGroup,
8 | InputGroupButtonDropdown,
9 | DropdownMenu,
10 | DropdownToggle,
11 | DropdownItem,
12 | Container,
13 | Button
14 | } from 'reactstrap';
15 | import Condition from './Condition.jsx';
16 |
17 |
18 |
19 | class customRowAction extends Component {
20 | constructor(props) {
21 | super(props);
22 | this.setValue = this.setValue.bind(this);
23 | this.handleInputChange = this.handleInputChange.bind(this);
24 | this.toggleAttributeDropdownOpen = this.toggleAttributeDropdownOpen.bind(this);
25 | this.toggleValueDropdownOpen = this.toggleValueDropdownOpen.bind(this);
26 | this.toggleConditionals = this.toggleConditionals.bind(this);
27 | this.moveRule = this.moveRule.bind(this);
28 | this.state = {
29 | key: this.props.customRowActions[this.props.index].name,
30 | value: this.props.customRowActions[this.props.index].value,
31 | conditionalFlag: (typeof this.props.customRowActions[this.props.index].value === 'object' && this.props.customRowActions[this.props.index].value.length ? true : false), // True if rules have been defined, otherwise false
32 | keyDropdownOpen: false,
33 | valueDropdownOpen: false,
34 | prevIndex: this.props.index,
35 | reset: this.props.reset
36 | };
37 | }
38 |
39 | componentDidUpdate() {
40 | if (this.state.prevIndex !== this.props.index) {
41 | this.setState({
42 | key: this.props.customRowActions[this.props.index].name,
43 | value: this.props.customRowActions[this.props.index].value,
44 | conditionalFlag: (typeof this.props.customRowActions[this.props.index].value === 'object' && this.props.customRowActions[this.props.index].value.length ? true : false), // True if rules have been defined, otherwise false
45 | keyDropdownOpen: false,
46 | valueDropdownOpen: false,
47 | prevIndex: this.props.index
48 | }, () => { this.props.buildJSON() });
49 | }
50 |
51 |
52 | }
53 |
54 |
55 | setValue(obj) {
56 | this.setState({
57 | value: obj
58 | })
59 | }
60 |
61 | // called inside Condition component
62 | moveRule(ele, index) {
63 | let arr = this.state.value.slice();
64 | let temp = arr[index];
65 |
66 | console.log(arr);
67 | switch(ele.target.attributes.value.value) {
68 | case 'up':
69 | arr.splice(index, 1);
70 | arr.splice((index === 0 ? 0 : index-1), 0, temp);
71 | break;
72 | case 'down':
73 | arr.splice(index, 1);
74 | arr.splice(index+1, 0, temp);
75 | break;
76 | }
77 |
78 |
79 | this.setState({
80 | value: arr
81 | }, () => { this.props.updateCRA(this.props.index, this.state.key, this.state.value) });
82 | }
83 |
84 |
85 |
86 | toggleConditionals() {
87 | this.setState({
88 | conditionalFlag: !this.state.conditionalFlag,
89 | value: []
90 | }, () => { this.props.updateCRA(this.props.index, this.state.key, this.state.value) });
91 | }
92 |
93 | handleInputChange(event) {
94 | const target = event.target;
95 | const name = target.name;
96 |
97 | let resetValue = '';
98 | if (name === 'key') {
99 | let value = target.type === 'button' ? target.innerHTML : target.value;
100 | resetValue = [];
101 | this.setState({
102 | [name]: value,
103 | value: resetValue
104 | }, () => { this.props.updateCRA(this.props.index, value, '') })
105 |
106 | } else {
107 | let value = target.type === 'button' ? ( this.state.key === 'background-color' ? this.props.colors[target.innerHTML] : target.innerHTML) : target.value;
108 | resetValue = value;
109 | this.setState({
110 | [name]: value,
111 | value: resetValue
112 | }, () => { this.props.updateCRA(this.props.index, this.state.key, this.state.value) })
113 | }
114 |
115 | }
116 |
117 | setConditionalValue(eleValue) {
118 | this.setState({
119 | value: eleValue
120 | });
121 | }
122 |
123 | toggleAttributeDropdownOpen() {
124 | this.setState({
125 | value: '',
126 | keyDropdownOpen: !this.state.keyDropdownOpen
127 | });
128 | }
129 |
130 | toggleValueDropdownOpen() {
131 | this.setState({
132 | valueDropdownOpen: !this.state.valueDropdownOpen
133 | });
134 | }
135 |
136 | render() {
137 | return (
138 |
139 |
140 |
141 |
142 |
143 | this.props.deleteKey('customRowActions', this.props.index)}>X
144 |
145 |
146 |
147 | Custom Row Action
148 |
149 |
150 |
151 |
152 |
153 | {Object.keys(this.props.customRowActionChoices).map((key, i) => {
154 | return ({key} );
155 | })}
156 |
157 |
158 |
159 |
160 |
161 |
162 | {!this.state.conditionalFlag ?
163 |
164 |
Value (help )
165 |
166 |
167 |
169 |
170 |
171 | { (this.props.customRowActionChoices[this.state.key] !== undefined && this.props.customRowActionChoices[this.state.key].options !== undefined
172 | ? this.props.customRowActionChoices[this.state.key].options.split(',').map((key, i) => {
173 | return ({key} );
174 | }) : (this.state.key === 'background-color' ? Object.keys(this.props.colors).map((key, i) => {
175 | return ({key} );
176 | })
177 | : '') )}
178 |
179 |
180 |
181 |
182 | : ''}
183 |
184 |
185 |
186 |
187 | {!this.state.conditionalFlag ? 'Add Condition' : 'Remove Conditions'}
188 |
189 |
190 |
191 | {this.state.conditionalFlag ? : ''}
192 |
193 |
194 | )
195 | }
196 | }
197 |
198 | export default customRowAction;
199 |
--------------------------------------------------------------------------------
/src/Components/MyModal.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | Modal,
4 | ModalHeader,
5 | ModalBody,
6 | ModalFooter,
7 | Button,
8 | TabContent,
9 | TabPane,
10 | Nav,
11 | NavItem,
12 | NavLink
13 |
14 | } from 'reactstrap';
15 |
16 | import classnames from 'classnames';
17 |
18 | class MyModal extends Component {
19 | constructor(props) {
20 | super(props);
21 | this.handleKeyDown = this.handleKeyDown.bind(this);
22 | this.toggle = this.toggle.bind(this);
23 | this.state = {
24 | activeTab: this.props.modalTab
25 | }
26 | }
27 |
28 | componentWillReceiveProps() {
29 | this.setState({
30 | activeTab: this.props.modalTab
31 | });
32 | }
33 |
34 | handleKeyDown(key) {
35 | if (key.keyCode === 13 || key.keyCode === 27) {
36 | this.props.toggleModal();
37 | }
38 | }
39 |
40 | toggle(tab) {
41 | if (this.state.activeTab !== tab) {
42 | this.setState({
43 | activeTab: tab
44 | });
45 | }
46 | }
47 |
48 |
49 | render() {
50 | return (
51 |
52 |
53 |
54 | {this.props.modalHeader}
55 |
56 |
57 |
58 |
59 | this.toggle('1') }>
60 | Help
61 |
62 |
63 |
64 | this.toggle('2') }>
65 | Variables
66 |
67 |
68 |
69 | this.toggle('3') }>
70 | Functions
71 |
72 |
73 |
74 | this.toggle('4') }>
75 | Math
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 | @currentField - refers to text in current field.
87 | @currentField.title - Person fields are represented in the system as objects, and a person’s display name is contained within that object’s title property
88 | @currentField.lookupValue - Lookup fields are also represented as objects; the display text is stored in the lookupValue property
89 | @now - current date/time
90 | @me - current user's email
91 | [$FieldName] - refers to value in field on same row
92 | [$PeoplePicker.email] - refers to email of the person in a people picker field
93 |
94 | People picker field properties: id , title , email , sip , picture
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 | toString() - Convert value to string
103 | Number() - Convert value to number
104 | Date() - Convert value to date
105 | cos() - Trig cos function
106 | sin() - Trig sin function
107 | toLocaleString() - Displays a date type fully expanded with date and time
108 | toLocaleDateString() - Displays a date type with just the date
109 | toLocaleTimeString() - Displays a date type with just the time
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 | ** - multiply
118 | // - divide
119 | ++ - add / concatenate strings
120 | -- - subtract
121 |
122 | () - parentheses are also supported to specify order of operations
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | OK
135 |
136 |
137 |
138 | )
139 | }
140 | }
141 |
142 | export default MyModal;
143 |
144 |
145 |
--------------------------------------------------------------------------------
/src/Components/Property.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | Row,
4 | Col,
5 | Label,
6 | Input,
7 | InputGroup,
8 | InputGroupButtonDropdown,
9 | InputGroupAddon,
10 | InputGroupText,
11 | DropdownMenu,
12 | DropdownToggle,
13 | DropdownItem,
14 | Container,
15 | Button
16 | } from 'reactstrap';
17 | import Condition from './Condition.jsx';
18 |
19 |
20 |
21 | class Property extends Component {
22 | constructor(props) {
23 | super(props);
24 | this.setValue = this.setValue.bind(this);
25 | this.handleInputChange = this.handleInputChange.bind(this);
26 | this.togglekeyDropdownOpen = this.togglekeyDropdownOpen.bind(this);
27 | this.toggleValueDropdownOpen = this.toggleValueDropdownOpen.bind(this);
28 | this.toggleConditionals = this.toggleConditionals.bind(this);
29 | this.moveRule = this.moveRule.bind(this);
30 | this.state = {
31 | key: this.props.properties[this.props.index].name,
32 | value: this.props.properties[this.props.index].value,
33 | conditionalFlag: (typeof this.props.properties[this.props.index].value === 'object' && this.props.properties[this.props.index].value.length ? true : false), // True if rules have been defined, otherwise false
34 | keyDropdownOpen: false,
35 | valueDropdownOpen: false,
36 | prevIndex: this.props.index,
37 | reset: false
38 | };
39 | }
40 |
41 | componentDidUpdate() {
42 | if (this.state.prevIndex !== this.props.index) {
43 | this.setState({
44 | key: this.props.properties[this.props.index].name,
45 | value: this.props.properties[this.props.index].value,
46 | conditionalFlag: (typeof this.props.properties[this.props.index].value === 'object' && this.props.properties[this.props.index].value.length ? true : false), // True if rules have been defined, otherwise false
47 | keyDropdownOpen: false,
48 | valueDropdownOpen: false,
49 | prevIndex: this.props.index
50 | }, () => { this.props.buildJSON() });
51 | }
52 | }
53 |
54 |
55 |
56 | setValue(obj) {
57 | this.setState({
58 | value: obj
59 | })
60 | }
61 |
62 | // called inside Condition component
63 | moveRule(ele, index) {
64 | let arr = this.state.value.slice();
65 | let temp = arr[index];
66 |
67 | console.log(arr);
68 | switch(ele.target.attributes.value.value) {
69 | case 'up':
70 | arr.splice(index, 1);
71 | arr.splice((index === 0 ? 0 : index-1), 0, temp);
72 | break;
73 | case 'down':
74 | arr.splice(index, 1);
75 | arr.splice(index+1, 0, temp);
76 | break;
77 | }
78 |
79 |
80 | this.setState({
81 | value: arr
82 | }, () => { this.props.updateProperty(this.props.index, this.state.key, this.state.value); });
83 | }
84 |
85 |
86 |
87 | toggleConditionals() {
88 | this.setState({
89 | conditionalFlag: !this.state.conditionalFlag,
90 | value: []
91 | }, () => { this.props.updateProperty( this.props.index, this.state.key, this.state.value) });
92 | }
93 |
94 | handleInputChange(event) {
95 | const target = event.target;
96 | const name = target.name;
97 |
98 | let resetValue = '';
99 |
100 | if (name === 'key') {
101 | let value = target.type === 'button' ? target.innerHTML : target.value;
102 | resetValue = [];
103 | this.setState({
104 | [name]: value,
105 | value: resetValue
106 | }, () => { this.props.updateProperty( this.props.index, value, '') })
107 |
108 | } else {
109 | let value = target.type === 'button' ? ( this.state.key === 'background-color' ? this.props.colors[target.innerHTML] : target.innerHTML) : target.value;
110 | resetValue = value;
111 | this.setState({
112 | [name]: value,
113 | value: resetValue
114 | }, () => { this.props.updateProperty( this.props.index, this.state.key, this.state.value) })
115 | }
116 |
117 |
118 | console.log(this.props.properties[this.props.index].value);
119 |
120 | }
121 |
122 | setConditionalValue(eleValue) {
123 | this.setState({
124 | value: eleValue
125 | });
126 | }
127 |
128 | togglekeyDropdownOpen() {
129 | this.setState({
130 | value: '',
131 | keyDropdownOpen: !this.state.keyDropdownOpen
132 | });
133 | }
134 |
135 | toggleValueDropdownOpen() {
136 | this.setState({
137 | valueDropdownOpen: !this.state.valueDropdownOpen
138 | });
139 | }
140 |
141 | render() {
142 | return (
143 |
144 |
145 |
146 |
147 |
148 | this.props.deleteKey('properties', this.props.index)}>X
149 |
150 |
151 |
152 | Property (help)
153 |
154 |
155 |
156 |
157 |
158 | {Object.keys(this.props.nameChoices).map((key, i) => {
159 | return ({key} );
160 | })}
161 |
162 |
163 |
164 |
165 |
166 |
167 | {!this.state.conditionalFlag ?
168 |
169 |
Value (help )
170 |
171 |
172 |
174 |
175 |
176 | { (this.props.nameChoices[this.state.key] !== undefined && this.props.nameChoices[this.state.key].options !== undefined
177 | ? this.props.nameChoices[this.state.key].options.split(',').map((key, i) => {
178 | return ({key} );
179 | }) : (this.state.key === 'background-color' ? Object.keys(this.props.colors).map((key, i) => {
180 | return ({key} );
181 | })
182 | : '') )}
183 |
184 |
185 |
186 |
187 | : ''}
188 |
189 |
190 |
191 |
192 | {!this.state.conditionalFlag ? 'Add Condition' : 'Remove Conditions'}
193 |
194 |
195 |
196 | {this.state.conditionalFlag ? : ''}
197 |
198 |
199 | )
200 | }
201 | }
202 |
203 | export default Property;
--------------------------------------------------------------------------------
/src/Components/Rule.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 |
3 | class Rule extends Component {
4 |
5 | render() {
6 | return (
7 | {this.props.selectRule(this.props.index)}}
8 | style={ { [this.props.name] : this.props.value, 'borderWidth': (this.props.index === this.props.selectedRule ? '3px' : '0px') }}>
9 | {this.props.text}
10 |
11 | );
12 | }
13 | }
14 |
15 |
16 | export default Rule;
--------------------------------------------------------------------------------
/src/Components/TopNavigation.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import {
3 | Navbar,
4 | NavbarBrand,
5 | Nav,
6 | Collapse,
7 | NavbarToggler,
8 | NavItem,
9 | NavLink,
10 | DropdownToggle,
11 | DropdownMenu,
12 | DropdownItem,
13 | UncontrolledDropdown
14 | } from 'reactstrap';
15 | import '../App.css';
16 |
17 | class TopNavigation extends Component {
18 | constructor(props) {
19 | super(props);
20 | this.toggleNavbar = this.toggleNavbar.bind(this);
21 | this.state = {
22 | isOpen: false
23 | };
24 | }
25 |
26 | toggleNavbar() {
27 | this.setState({
28 | isOpen: !this.state.isOpen
29 | });
30 | }
31 |
32 | render() {
33 | return (
34 |
35 |
36 | {/* Left Nav Buttons */}
37 |
38 |
39 |
40 | Quick Add
41 |
42 |
43 |
44 | Attributes
45 |
46 |
47 | Hyperlink
48 |
49 |
50 |
51 | CSS Properties
52 |
53 |
54 | Background Color
55 |
56 |
57 | Bold Font
58 |
59 |
60 | Font Size
61 |
62 |
63 | Text Overflow
64 |
65 |
66 |
67 | Custom Row Actions
68 |
69 |
70 | Microsoft Flow
71 |
72 |
73 |
74 |
75 |
76 | Templates
77 |
78 |
79 |
80 | Completed/In Progress/Late
81 |
82 |
83 | Data Bars 0-1
84 |
85 |
86 | Data Bars 0-100
87 |
88 |
89 | Button with link + icon
90 |
91 |
92 |
93 |
94 |
95 | {/* Right Nav Buttons */}
96 |
97 |
98 |
99 | Help
100 |
101 |
102 |
103 | Microsoft Docs
104 |
105 |
106 | JSON Formatting
107 |
108 |
109 | SharePoint Fabric
110 |
111 |
112 |
113 | Useful Tools
114 |
115 |
116 | JSON Lint
117 |
118 |
119 |
120 | App Specific
121 |
122 |
123 | YouTube Tutorial
124 |
125 |
126 | Source Code
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 | )
137 | }
138 | }
139 |
140 |
141 | // Verbose Schema
142 | // https://gist.github.com/thechriskent/2e09be14a4b491cfae256220cfca6310
143 |
144 | export default TopNavigation;
145 |
--------------------------------------------------------------------------------
/src/data.js:
--------------------------------------------------------------------------------
1 |
2 | export default {
3 | Attributes: {
4 | 'href': {
5 | 'placeholder': 'URL'
6 | },
7 | 'target': {
8 | 'options': '_blank,_self,_parent,_top'
9 | }
10 | ,
11 | 'class': {
12 | 'options': 'sp-field-customFormatBackground,sp-field-severity--good,sp-field-severity--low,sp-field-severity--warning,sp-field-severity--blocked,sp-field-dataBars,sp-field-trending--up,sp-field-trending--down,sp-field-quickAction'
13 | },
14 | 'iconName': {}
15 | },
16 | CSSProperties: {
17 | 'background-color': {
18 | 'placeholder': '#hex -- color'
19 | },
20 | 'font-size': {
21 | 'placeholder': '18px -- 150% '
22 | },
23 | 'text-align': {
24 | 'options': 'left,right,center,justify'
25 | },
26 | 'border': {
27 | 'placeholder': '4px solid black'
28 | },
29 | 'border-radius': {},
30 | 'font-weight': {
31 | 'options': 'bold,semibold'
32 | },
33 |
34 | 'color': {
35 | 'placeholder': '#hex -- color'
36 | },
37 | 'width': {},
38 | 'max-height': {},
39 | 'overflow': {
40 | 'options': 'scroll,hidden,auto,visible'
41 | }
42 | },
43 | customRowActions: {
44 | 'action': {
45 | 'options': 'executeFlow,share,defaultClick'
46 | },
47 | 'actionParams': {
48 | 'options': '{\\"id\\": \\"FLOW_ID\\"}'
49 | }
50 | },
51 | customColors: {
52 | 'Transparent': 'transparent',
53 | 'Green': '#98FB98',
54 | 'Yellow': '#FFFF66',
55 | 'Orange': '#FFA450',
56 | 'Red': '#FF6A6A',
57 | 'Blue': '#5078FF',
58 | 'Purple': '#B350FF',
59 | 'Custom': '#'
60 | },
61 | template_completedInProgressLate: {
62 | properties: [
63 | {
64 | name: 'background-color',
65 | value: [
66 | {
67 | operator: '==',
68 | operand: '@currentField',
69 | operand2: 'Completed',
70 | value: '#98fb98'
71 | },
72 | {
73 | operator: '==',
74 | operand: '@currentField',
75 | operand2: 'In Progress',
76 | value: '#FFFF66'
77 | },
78 | {
79 | operator: '==',
80 | operand: '@currentField',
81 | operand2: 'Late',
82 | value: '#ff6a6a'
83 | }
84 | ]
85 | }
86 | ]
87 | },
88 | template_dataBars_one: {
89 | attributes: [
90 | {
91 | name: 'class',
92 | value: 'sp-field-dataBars'
93 | }
94 | ],
95 | properties: [
96 | {
97 | name: 'width',
98 | value: 'Number(@currentField)**Number(100)++%'
99 | }
100 | ]
101 | },
102 | template_dataBars_hundred: {
103 | attributes: [
104 | {
105 | name: 'class',
106 | value: 'sp-field-dataBars'
107 | }
108 | ],
109 | properties: [
110 | {
111 | name: 'width',
112 | value: '@currentField++%'
113 | }
114 | ]
115 | },
116 | template_buttonWithLinkandIcon: {
117 | // button
118 | // currentText = " ++@currentField
119 | properties: [
120 | {
121 | name: 'display',
122 | value: 'block'
123 | },
124 | {
125 | name: 'width',
126 | value: '9em'
127 | },
128 | {
129 | name: 'background-color',
130 | value: '#FFA450'
131 | },
132 | {
133 | name: 'border',
134 | value: '2px solid black'
135 | },
136 | {
137 | name: "border-radius",
138 | value: "15px"
139 | },
140 | ],
141 | children: [
142 | {
143 | name: {
144 | elmType: "a",
145 | textContent: " ++@currentField",
146 | properties: [
147 | {
148 | name: "text-decoration",
149 | value: "none"
150 | }
151 | ],
152 | attributes: [
153 | {
154 | name: "target",
155 | value: "_blank"
156 | },
157 | {
158 | name: "href",
159 | value: "https://www.google.com?ID=++[$ID]"
160 | },
161 | {
162 | name: "iconName",
163 | value: "Link"
164 | }
165 | ]
166 | },
167 | value: ''
168 | },
169 |
170 | ]
171 | // children: [
172 | // {
173 |
174 | // ]
175 |
176 | // child: a
177 | // target = _blank
178 | // href =
179 |
180 | },
181 |
182 |
183 |
184 | modalBody_TextContentHelp: `
185 | @currentField - refers to text in current field.
186 | @currentField.title - Person fields are represented in the system as objects, and a person’s display name is contained within that object’s title property
187 | @currentField.lookupValue - Lookup fields are also represented as objects; the display text is stored in the lookupValue property
188 | @now - current date/time
189 | @me - current user's email
190 | [$FieldName] - refers to value in field on same row
191 | [$PeoplePicker.email] - refers to email of the person in a people picker field
192 | People picker field properties: id , title , email , sip , picture
193 |
194 |
195 | Basic math functions (parenthases () are also supported):
196 | multiply (** ), divide (// ), add (++ ), subtract (-- )
197 |
198 | Functions:
199 | toString() , Number() , Date() , cos() , sin() ,
200 | toLocaleString() [Displays a date type fully expanded with date and time],
201 | toLocaleDateString() [Displays a date type with just the date],
202 | toLocaleTimeString() [Displays a date type with just the time]
203 |
204 |
205 | Note: If you have spaces in the field name, those are defined as _x0020_. For example, a field named "Due Date" should be referenced as $Due_x0020_Date.
206 | Note2: Use ++ to concatenate
207 | `
208 |
209 | }
210 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import 'bootstrap/dist/css/bootstrap.min.css';
4 | import './index.css';
5 | import App from './App';
6 | import registerServiceWorker from './registerServiceWorker';
7 |
8 | ReactDOM.render( , document.getElementById('root'));
9 | registerServiceWorker();
10 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/pollyfills.js:
--------------------------------------------------------------------------------
1 | if (!String.prototype.repeat) {
2 | String.prototype.repeat = function(count) {
3 | 'use strict';
4 | if (this == null) {
5 | throw new TypeError('can\'t convert ' + this + ' to object');
6 | }
7 | var str = '' + this;
8 | count = +count;
9 | if (count != count) {
10 | count = 0;
11 | }
12 | if (count < 0) {
13 | throw new RangeError('repeat count must be non-negative');
14 | }
15 | if (count == Infinity) {
16 | throw new RangeError('repeat count must be less than infinity');
17 | }
18 | count = Math.floor(count);
19 | if (str.length == 0 || count == 0) {
20 | return '';
21 | }
22 | // Ensuring count is a 31-bit integer allows us to heavily optimize the
23 | // main part. But anyway, most current (August 2014) browsers can't handle
24 | // strings 1 << 28 chars or longer, so:
25 | if (str.length * count >= 1 << 28) {
26 | throw new RangeError('repeat count must not overflow maximum string size');
27 | }
28 | var rpt = '';
29 | for (var i = 0; i < count; i++) {
30 | rpt += str;
31 | }
32 | return rpt;
33 | }
34 | }
35 |
36 | if (!String.prototype.includes) {
37 | String.prototype.includes = function(search, start) {
38 | 'use strict';
39 | if (typeof start !== 'number') {
40 | start = 0;
41 | }
42 |
43 | if (start + search.length > this.length) {
44 | return false;
45 | } else {
46 | return this.indexOf(search, start) !== -1;
47 | }
48 | };
49 | }
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------