-
Notifications
You must be signed in to change notification settings - Fork 45
feat: mask editor [STU-313] #33
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
727e878
feat: add mask editor (STU-313)
lag-of-death 7d119fa
chore: rm yarn.lock and reinstall dependencies
lag-of-death f47a1c2
style: address linting issue
lag-of-death 376be7e
feat: add "with mask controls" example to the stories
lag-of-death 56aa1dc
fix: adjust padding
lag-of-death d5f6eb7
feat: handle array and allOf [wip]
lag-of-death 2c8b6ba
fix: toggle node
lag-of-death da60a1f
feat: do not merge allOfs when in MaskEditor
lag-of-death b717550
refactor: use a class instead of applying styles with style attr
lag-of-death f5976d1
chore: revert yarn.lock changes
lag-of-death 8cf5895
refactor: define SelectedPaths type
lag-of-death 86e0812
refactor: use React.ReactElement
lag-of-death 82fe590
refactor: use React.FunctionComponent for typing
lag-of-death 45766e7
refactor: rm TODO
lag-of-death ecb7b3b
refactor: use tailwind class names
lag-of-death 74b12e8
refactor: use MaskingProps type
lag-of-death 02d6a9b
refactor: give "Apply/Update Mask" btn a better name (as a prop)
lag-of-death File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,176 @@ | ||
| import { Button, Checkbox as UIKitCheckbox, Colors, Icon, Tooltip } from '@stoplight/ui-kit'; | ||
| import * as React from 'react'; | ||
| import { Dispatch, SetStateAction, useState } from 'react'; | ||
|
|
||
| const ICON_SIZE = 12; | ||
|
|
||
| export type SelectedPaths = Array<{ path: string; required: boolean }>; | ||
|
|
||
| export type MaskingProps = Array<{ path: string; required: any }>; | ||
|
|
||
| interface IMaskGenericControls { | ||
| node: { level: number; metadata: { path: string[] } }; | ||
| maskControlsHandler?: (attrs: SelectedPaths) => string[]; | ||
| setSelectedProps: Dispatch<SetStateAction<Array<{ path: string; required: number }>>>; | ||
| } | ||
|
|
||
| interface IMaskControls extends IMaskGenericControls { | ||
| selectedProps: SelectedPaths; | ||
| } | ||
| interface ICheckbox extends IMaskGenericControls { | ||
| isChecked: boolean; | ||
| } | ||
|
|
||
| interface IRequired extends IMaskGenericControls { | ||
| idx: number; | ||
| setIdx: Dispatch<SetStateAction<number>>; | ||
| } | ||
|
|
||
| function addAttr(old: Array<{ path: string; required: number }>, path: string, required: number) { | ||
| const attr = old.find(oldAttr => oldAttr.path === path); | ||
|
|
||
| return attr | ||
| ? old.map(oldAttr => { | ||
| return oldAttr.path === path ? Object.assign({}, oldAttr, { required }) : oldAttr; | ||
| }) | ||
| : old.concat({ path, required }); | ||
| } | ||
|
|
||
| function updateSelectedAttrs( | ||
| isChecked: boolean, | ||
| oldAttrs: Array<{ path: string; required: number }>, | ||
| path: string, | ||
| idx: number, | ||
| ): Array<{ path: string; required: number }> { | ||
| return isChecked ? addAttr(oldAttrs, path, idx) : oldAttrs.filter(oldAttr => oldAttr.path !== path); | ||
| } | ||
|
|
||
| const toRequiredNumForm = (node: { required?: boolean } = {}) => { | ||
| if (node.required === true) { | ||
| return 1; | ||
| } else if (node.required === false) { | ||
| return 2; | ||
| } else { | ||
| return 0; | ||
| } | ||
| }; | ||
|
|
||
| function toMaskAttrsWitReqAsBool(maskAttrs: Array<{ path: string; required: number }>, nodePath: string) { | ||
| return maskAttrs.map(maskAttr => { | ||
| const { path, required } = maskAttr; | ||
|
|
||
| const requiredState = { | ||
| 0: {}, | ||
| 1: { required: true }, | ||
| 2: { required: false }, | ||
| }[required || 0]; | ||
|
|
||
| return nodePath === path ? Object.assign({}, { path }, requiredState) : maskAttr; | ||
| }); | ||
| } | ||
|
|
||
| const toNodePath = (node: { metadata: { path: string[] } }) => node.metadata.path.join('/'); | ||
|
|
||
| function updateMaskAttrs( | ||
| setSelectedProps: Dispatch<SetStateAction<Array<{ path: string; required: number }>>>, | ||
| isChecked: boolean, | ||
| node: { metadata: { path: string[] } }, | ||
| idx: number, | ||
| maskControlsHandler?: (attrs: SelectedPaths) => string[], | ||
| ) { | ||
| setSelectedProps(oldProps => { | ||
| const nodePath = toNodePath(node); | ||
|
|
||
| const maskAttrs = updateSelectedAttrs(isChecked, oldProps, nodePath, idx); | ||
| const maskAttrsWithBools = toMaskAttrsWitReqAsBool(maskAttrs, nodePath); | ||
|
|
||
| if (maskControlsHandler) { | ||
| maskControlsHandler(maskAttrsWithBools); | ||
| } | ||
|
|
||
| return maskAttrsWithBools; | ||
| }); | ||
| } | ||
|
|
||
| const Required: React.FunctionComponent<IRequired> = ({ | ||
| idx, | ||
| setIdx, | ||
| setSelectedProps, | ||
| maskControlsHandler, | ||
| node, | ||
| }: IRequired) => { | ||
| return ( | ||
| <Tooltip boundary="window" position="top"> | ||
| <Button | ||
| style={{ paddingRight: '1em' }} | ||
| small | ||
| minimal | ||
| title={['No Change', 'Required', 'Not Required'][idx]} | ||
| icon={<Icon color={[Colors.GRAY1, Colors.RED3, Colors.GREEN2][idx]} iconSize={ICON_SIZE} icon="issue" />} | ||
| onClick={(evt: { stopPropagation: () => void }) => { | ||
| evt.stopPropagation(); | ||
|
|
||
| setIdx((prev: number) => { | ||
| return prev >= 2 ? 0 : prev + 1; | ||
| }); | ||
|
|
||
| updateMaskAttrs(setSelectedProps, true, node, idx + 1, maskControlsHandler); | ||
| }} | ||
| /> | ||
| </Tooltip> | ||
| ); | ||
| }; | ||
|
|
||
| const Checkbox: React.FunctionComponent<ICheckbox> = ({ | ||
| isChecked, | ||
| setSelectedProps, | ||
| maskControlsHandler, | ||
| node, | ||
| }: ICheckbox) => { | ||
| return ( | ||
| <UIKitCheckbox | ||
| className="m-0 self-center" | ||
| checked={isChecked} | ||
| onChange={(evt: any) => { | ||
| evt.persist(); | ||
|
|
||
| updateMaskAttrs(setSelectedProps, evt.target.checked, node, 0, maskControlsHandler); | ||
| }} | ||
| /> | ||
| ); | ||
| }; | ||
|
|
||
| const MaskControls: React.FunctionComponent<IMaskControls> = ({ | ||
| node, | ||
| maskControlsHandler, | ||
| setSelectedProps, | ||
| selectedProps, | ||
| }: IMaskControls) => { | ||
| const theNode = selectedProps.find(({ path }: { path: string }) => path === toNodePath(node)); | ||
| const isChecked = !!theNode; | ||
| const [idx, setIdx] = useState(toRequiredNumForm(theNode)); | ||
|
|
||
| return ( | ||
| <div className="flex"> | ||
| {node.level && maskControlsHandler ? ( | ||
| <> | ||
| <Checkbox | ||
| isChecked={isChecked} | ||
| setSelectedProps={setSelectedProps} | ||
| maskControlsHandler={maskControlsHandler} | ||
| node={node} | ||
| /> | ||
| <Required | ||
| idx={idx} | ||
| setIdx={setIdx} | ||
| setSelectedProps={setSelectedProps} | ||
| maskControlsHandler={maskControlsHandler} | ||
| node={node} | ||
| /> | ||
| </> | ||
| ) : null} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default MaskControls; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,11 +1,12 @@ | ||
| import { TreeList, TreeListEvents, TreeStore } from '@stoplight/tree-list'; | ||
| import { TreeList, TreeStore } from '@stoplight/tree-list'; | ||
| import { Button } from '@stoplight/ui-kit'; | ||
| import * as cn from 'classnames'; | ||
| import { JSONSchema4 } from 'json-schema'; | ||
| import { observer } from 'mobx-react-lite'; | ||
| import * as React from 'react'; | ||
|
|
||
| import { GoToRefHandler } from '../types'; | ||
| import { SchemaRow } from './'; | ||
| import MaskControls, { MaskingProps, SelectedPaths } from './MaskControls'; | ||
|
|
||
| export interface ISchemaTree { | ||
| treeStore: TreeStore; | ||
|
|
@@ -17,24 +18,52 @@ export interface ISchemaTree { | |
| expanded?: boolean; | ||
| maxRows?: number; | ||
| onGoToRef?: GoToRefHandler; | ||
| maskControlsHandler?: (attrs: SelectedPaths) => string[]; | ||
| maskUpdater?: () => React.ReactElement; | ||
| maskProps?: SelectedPaths; | ||
| } | ||
|
|
||
| const canDrag = () => false; | ||
|
|
||
| export const SchemaTree = observer<ISchemaTree>(props => { | ||
| const { hideTopBar, name, treeStore, maxRows, className, onGoToRef } = props; | ||
|
|
||
| treeStore.on(TreeListEvents.NodeClick, (e, node) => treeStore.toggleExpand(node)); | ||
|
|
||
| const itemData = { | ||
| treeStore, | ||
| count: treeStore.nodes.length, | ||
| onGoToRef, | ||
| }; | ||
|
|
||
| const [selectedProps, setSelectedProps] = React.useState<MaskingProps>((props.maskProps || []) as MaskingProps); | ||
|
|
||
| const rowRenderer = React.useCallback( | ||
| (node, rowOptions) => <SchemaRow node={node} rowOptions={rowOptions} {...itemData} />, | ||
| [itemData.count], | ||
| (node, rowOptions) => { | ||
| const possibleProps = props.maskControlsHandler | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Suggest passing custom |
||
| ? { | ||
| maskControls: () => ( | ||
| <MaskControls | ||
| node={node} | ||
| maskControlsHandler={props.maskControlsHandler} | ||
| setSelectedProps={setSelectedProps} | ||
| selectedProps={selectedProps} | ||
| /> | ||
| ), | ||
| } | ||
| : {}; | ||
|
|
||
| return ( | ||
| <SchemaRow | ||
| toggleExpand={() => { | ||
| treeStore.toggleExpand(node); | ||
| }} | ||
| {...possibleProps} | ||
| node={node} | ||
| rowOptions={rowOptions} | ||
| {...itemData} | ||
| /> | ||
| ); | ||
| }, | ||
| [itemData.count, selectedProps], | ||
| ); | ||
|
|
||
| return ( | ||
|
|
@@ -53,6 +82,26 @@ export const SchemaTree = observer<ISchemaTree>(props => { | |
| rowRenderer={rowRenderer} | ||
| canDrag={canDrag} | ||
| /> | ||
|
|
||
| {props.maskControlsHandler && ( | ||
| <div className="pt-4 flex self-end justify-between"> | ||
| {props.maskUpdater && props.maskUpdater()} | ||
|
|
||
| <Button | ||
| intent="none" | ||
| title="clear selection" | ||
| onClick={() => { | ||
| setSelectedProps([]); | ||
|
|
||
| if (props.maskControlsHandler) { | ||
| props.maskControlsHandler([]); | ||
| } | ||
| }} | ||
| > | ||
| Clear Selection | ||
| </Button> | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| }); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.