diff --git a/.eslintrc b/.eslintrc index b19ef59..90d2cf1 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,13 +4,8 @@ "sourceType": "module", "ecmaFeatures": { "jsx": true } }, - "plugins": [ - "@typescript-eslint/eslint-plugin" - ], - "extends": [ - "standard", - "plugin:react/recommended" - ], + "plugins": ["@typescript-eslint/eslint-plugin"], + "extends": ["standard", "prettier", "plugin:react/recommended"], "settings": { "react": { "version": "detect" } }, @@ -27,13 +22,38 @@ "no-useless-constructor": "off", "space-before-function-paren": "off", "no-dupe-class-members": "off", - "comma-dangle": ["error", { - "arrays": "always-multiline", - "objects": "always-multiline" - }], - "@typescript-eslint/no-unused-vars": ["error", { - "argsIgnorePattern": "^_", - "varsIgnorePattern": "^_" - }] - } + "comma-dangle": [ + "error", + { + "arrays": "always-multiline", + "objects": "always-multiline" + } + ], + "@typescript-eslint/no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_", + "varsIgnorePattern": "^_" + } + ] + }, + "overrides": [ + { + "files": ["**/*.ts", "**/*.tsx"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint", "prettier"], + "rules": { + "quotes": "off", + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/no-var-requires": 0, + "react/prop-types": 0 + } + } + ] } diff --git a/package.json b/package.json index 4e15bf3..080e2c1 100644 --- a/package.json +++ b/package.json @@ -19,13 +19,13 @@ "scripts": { "analyze": "NODE_ENV=production webpack --profile --json | webpack-bundle-size-analyzer", "build": "npm run compile && npm run bundle", - "bundle": "rm -rf dist && webpack --progress && NODE_ENV=production webpack --progress", - "compile": "rm -rf lib && tsc && cp -r src/images lib && cp -r src/styles lib", + "bundle": "rimraf dist && webpack --progress && NODE_ENV=production webpack --progress", + "compile": "rimraf dist && tsc && copy -r src/images lib && copy -r src/styles lib", "docs": "build-storybook -o build", "start": "start-storybook -p 6006", "e2e": "cypress run", - "format": "prettier --write '{src,test}/**/*.ts*' && eslint --fix '{src,test}/**/*.ts*'", - "lint": "prettier --check '{src,test}/**/*.ts*' && eslint '{src,test}/**/*.ts*'", + "format": "prettier --write \"{src,test}/**/*.ts*\" && eslint --fix \"{src,test}/**/*.ts*\"", + "lint": "prettier --check \"{src,test}/**/*.ts*\" && eslint \"{src,test}/**/*.ts*\"", "prepare": "husky install", "pretest": "npm run lint", "readme": "doctoc --maxlevel 3 README.md", @@ -33,15 +33,24 @@ "update": "ncu -u" }, "dependencies": { + "@fortawesome/fontawesome-svg-core": "1.2.36", + "@fortawesome/free-solid-svg-icons": "5.15.4", + "@fortawesome/react-fontawesome": "0.1.17", "@types/lodash": "^4.14.168", "@types/marked": "^2.0.1", "@types/uuid": "^8.3.0", + "bootstrap": "5.1.3", "classnames": "^2.3.1", + "eslint-config-prettier": "8.3.0", + "eslint-plugin-prettier": "4.0.0", "jsonschema": "^1.4.0", "jszip": "^3.6.0", "lodash": "^4.17.21", "marked": "^2.0.1", + "react-json-editor-ajrm": "^2.5.13", "react-sortable-hoc": "^2.0.0", + "reactstrap": "9.0.1", + "rimraf": "3.0.2", "tableschema": "^1.12.4", "use-async-effect": "^2.2.3", "uuid": "^8.3.2" @@ -64,6 +73,7 @@ "@types/node-fetch": "^2.5.10", "@types/react": "^17.0.3", "@types/react-dom": "^17.0.3", + "@types/react-json-editor-ajrm": "^2.5.2", "@typescript-eslint/eslint-plugin": "^4.21.0", "@typescript-eslint/parser": "^4.21.0", "babel-loader": "^8.2.2", diff --git a/src/components/Constraints.tsx b/src/components/Constraints.tsx new file mode 100644 index 0000000..633aedf --- /dev/null +++ b/src/components/Constraints.tsx @@ -0,0 +1,160 @@ +import React, { useState } from 'react' +import { UNIQUE, REQUIRED } from '../constants' +import { Dropdown, DropdownItem, DropdownMenu, DropdownToggle, Table } from 'reactstrap' +import { ToggleSwitch } from './toggleSwitch' + +/** + * + */ +export type MultiSelectProps = { + toggleModal: any + saveConstraint: any + removeConstraint: any + column: any +} + +export function Constraints(props: MultiSelectProps) { + const [constraint, setConstraint] = useState('') + const [toggleConstraint, setToggleConstraint] = useState(false) + const [requiredChecked, setRequiredChecked] = useState(true) + const [uniqueChecked, setUniqueChecked] = useState(true) + + const toggleDropDown = () => { + setToggleConstraint(!toggleConstraint) + } + + const selectConstraint = (type: string) => { + if (type === REQUIRED) { + setConstraint(REQUIRED) + } + if (type === UNIQUE) { + setConstraint(UNIQUE) + } + } + + const setConstraintChecked = (value: boolean) => { + if (constraint === REQUIRED) { + setRequiredChecked(value) + } else if (constraint === UNIQUE) { + setUniqueChecked(value) + } + } + + const setDropdown = () => { + return constraint || props.column.field.constraintsAvailable[0] + } + + const currentDate = () => { + return new Date().toLocaleString().split(',')[0] + } + + return ( +
+
+
+
+ {props.column.field ? props.column.field.name : ''} +
+ + {setDropdown()} + + {props.column.field.constraintsAvailable && + props.column.field.constraintsAvailable.map((constraint: any) => ( + selectConstraint(constraint)} + > + {constraint} + + ))} + + + + {constraint === REQUIRED && ( +
+ Required Constraint:
+ setConstraintChecked(val)} + name="toggle" + small={false} + disabled={true} + > +
+ )} + + {constraint === UNIQUE && ( +
+ Unique Constraint:
+ setConstraintChecked(val)} + name="toggle" + small={false} + disabled={true} + > +
+ )} +
+ +
+ + +
+
+ +
+
+
Previously added
+
+
+ + + + + + + + + + + + {props.column.field.constraintList && + props.column.field.constraintList.map((value: any) => { + return ( + + + + + + + + ) + })} + +
ConstraintValueAdded ByEffective Date
{value}trueMark{currentDate()} + +
+
+
+
+
+ ) +} diff --git a/src/components/Schema.tsx b/src/components/Schema.tsx index 9f0f09b..953129e 100644 --- a/src/components/Schema.tsx +++ b/src/components/Schema.tsx @@ -1,4 +1,4 @@ -import { find } from 'lodash' +import { find, omit, entries, forOwn } from 'lodash' import classNames from 'classnames' import React, { useCallback, useEffect, useState } from 'react' import { arrayMove } from 'react-sortable-hoc' @@ -9,7 +9,7 @@ import { SchemaPreview } from './SchemaPreview' import { SchemaField } from './SchemaField' import * as helpers from '../helpers' import { IDict } from '../common' - +import { UNIQUE, REQUIRED } from '../constants' export interface ISchemaProps { source?: string | File schema: IDict | File @@ -26,7 +26,6 @@ export function Schema(props: ISchemaProps) { const [columns, setColumns] = useState([] as IDict[]) const [metadata, setMetadata] = useState({} as IDict) const [feedback] = useState({} as ISchemaFeedbackProps['feedback']) - // Mount useAsyncEffect(async () => { try { @@ -36,8 +35,7 @@ export function Schema(props: ISchemaProps) { setColumns(columns) setMetadata(metadata) } catch (error) { - // @ts-ignore - setError(error) + setError(null) setLoading(false) } }, []) @@ -62,8 +60,15 @@ export function Schema(props: ISchemaProps) { } } + const setSchemaColumns = (columns: IDict[], primaryKeys: string[] = []) => { + const data = helpers.createColumns(columns, primaryKeys) + setColumns([...data]) + } + // Feedback Reset - const resetFeedback = () => {} + const resetFeedback = () => { + console.log('Feedback') + } // Add Field const addField = () => { @@ -85,6 +90,34 @@ export function Schema(props: ISchemaProps) { } } + // Update Constraint + const updateConstraint = (id: number, setConstraint: string, value: any): void => { + const column = find(columns, (column) => column.id === id) + if (column) { + const constrain: any = {} + if (setConstraint === REQUIRED) { + if (!value) { + column.field.constraints = omit(column.field.constraints, REQUIRED) + } else { + constrain[REQUIRED] = value + } + } + if (setConstraint === UNIQUE) { + if (!value) { + column.field.constraints = omit(column.field.constraints, UNIQUE) + } else { + constrain[UNIQUE] = value + } + } + column.field.constraints = { ...column.field.constraints, ...constrain } + if (entries(column.field.constraints).length < 1) { + column.field = omit(column.field, 'constraints') + } + + setColumns([...columns]) + } + } + // Move Field const moveField = (props: { oldIndex: number; newIndex: number }) => { setColumns([...arrayMove(columns, props.oldIndex, props.newIndex)]) @@ -92,6 +125,55 @@ export function Schema(props: ISchemaProps) { const showTabNumber = !props.disablePreview && !props.disableSave + const saveConstraint = (type: string, column: any) => { + const constrain: any = [] + constrain.push(type) + column.field.constraintList = column.field.constraintList + ? [...column.field.constraintList, ...constrain] + : constrain + + forOwn(column.field.constraintsAvailable, function (value) { + if (value === type) { + column.field.constraintsAvailable = column.field.constraintsAvailable.filter( + (constr: any) => { + if (constr !== type) { + return constr + } + return null + } + ) + } + }) + + setColumns([...columns]) + updateConstraint(column.id, type, true) + } + + const removeItem = (type: string, column: any) => { + const constrain: any = [] + constrain.push(type) + column.field.constraintsAvailable = [ + ...column.field.constraintsAvailable, + ...constrain, + ] + + forOwn(column.field.constraintList, function (value) { + if (value === type) { + column.field.constraintList = column.field.constraintList.filter( + (constr: any) => { + if (constr !== type) { + return constr + } + return null + } + ) + } + }) + + setColumns([...columns]) + updateConstraint(column.id, type, false) + } + return (
@@ -179,6 +261,11 @@ export function Schema(props: ISchemaProps) { metadata={metadata} removeField={removeField} updateField={updateField} + updateConstraint={updateConstraint} + saveConstraint={(val: any, column: any) => + saveConstraint(val, column) + } + removeConstraint={(val: any, column: any) => removeItem(val, column)} helperClass="frictionless-components-schema" onSortEnd={moveField} lockAxis="y" @@ -207,7 +294,11 @@ export function Schema(props: ISchemaProps) { role="tabpanel" className={classNames('tab-pane', { active: tab === 'preview' })} > - +
)}
@@ -220,7 +311,15 @@ export function Schema(props: ISchemaProps) { // Internal const SortableFields = SortableContainer( - (props: { columns: IDict[]; metadata: IDict; removeField: any; updateField: any }) => ( + (props: { + columns: IDict[] + metadata: IDict + removeField: any + updateField: any + updateConstraint: any + saveConstraint: any + removeConstraint: any + }) => ( @@ -237,7 +341,15 @@ const SortableFields = SortableContainer( ) const SortableField = SortableElement( - (props: { column: IDict; metadata: IDict; removeField: any; updateField: any }) => ( + (props: { + column: IDict + metadata: IDict + removeField: any + updateField: any + updateConstraint: any + saveConstraint: any + removeConstraint: any + }) => (
  • props.saveConstraint(val, column)} + removeConstraint={(val: any, column: any) => props.removeConstraint(val, column)} />
  • ) diff --git a/src/components/SchemaField.tsx b/src/components/SchemaField.tsx index 4213a5a..fe7fc25 100644 --- a/src/components/SchemaField.tsx +++ b/src/components/SchemaField.tsx @@ -1,24 +1,40 @@ import React, { useState } from 'react' import { IDict } from '../common' import * as helpers from '../helpers' - +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faTimesCircle, faEllipsisV } from '@fortawesome/free-solid-svg-icons' +import { + Modal, + ModalBody, + ModalHeader, + Dropdown, + DropdownToggle, + DropdownMenu, + DropdownItem, +} from 'reactstrap' +import { Constraints } from './Constraints' export interface ISchemaFieldProps { column: IDict metadata: IDict removeField: any updateField: any + updateConstraint: any + saveConstraint: any + removeConstraint: any } export function SchemaField(props: ISchemaFieldProps) { const types = helpers.getFieldTypes() const formats = helpers.getFieldFormats(props.column.field.type) const [isDetails, setIsDetails] = useState(false) + const [modalOpen, setModalOpen] = useState(false) + return (
    {/* General */}
    {/* Name */} -
    +
    @@ -33,8 +49,42 @@ export function SchemaField(props: ISchemaFieldProps) {
    + {/* Title */} +
    +
    +
    +
    Title
    +
    + + props.updateField(props.column.id, 'title', ev.target.value) + } + /> +
    +
    + + {/* Description */} +
    +
    +
    +
    Description
    +
    + + props.updateField(props.column.id, 'description', ev.target.value) + } + /> +
    +
    + {/* Type */} -
    +
    Type
    @@ -55,7 +105,7 @@ export function SchemaField(props: ISchemaFieldProps) {
    {/* Format */} -
    +
    Format
    @@ -70,107 +120,90 @@ export function SchemaField(props: ISchemaFieldProps) {
    - {/* Controls */} -
    - {/* Details */} - - - {/* Remove */} - + {/* PrimaryKey */} +
    +
    +
    +
    Primary Key
    +
    + { + props.updateField(props.column.id, 'primaryKey', ev.currentTarget.checked) + }} + /> +
    -
    - {/* Details */} - {isDetails && ( -
    -
    -
    -
    - {/* Extra fields */} -
    - {/* Title */} -
    - - - props.updateField(props.column.id, 'title', ev.target.value) - } - /> -
    + {/* Action Menu */} +
    + + + setIsDetails(!isDetails)} + direction="start" + > + + + + + setModalOpen(true)}> + Add Schema Constraints + + + Another Action + + - {/* Description */} -
    - -