Conversation
- Includes a basic working Tree component for browsing configuration files
BrendanAnnable
left a comment
There was a problem hiding this comment.
Quick review on my way to work, I didn't get a chance to run the code yet but just some high level feedback 🙂
| flex-shrink: 1; | ||
| display: flex; | ||
| flex-direction: column; | ||
| font-weight: normal; |
There was a problem hiding this comment.
Could you reformat all these CSS files to use 2 space indentation? 🙂 I think its my fault for checking in some non-2-space files.
There was a problem hiding this comment.
I should probably should look into adding something like https://github.com/stylelint/stylelint
| leaf: boolean, | ||
| selected: boolean, | ||
| children?: Node[] | ||
| } |
There was a problem hiding this comment.
You can remove all the commas from these interface definitions, e.g.
export interface Node {
label: string
expanded: boolean
leaf: boolean
selected: boolean
children?: Node[]
}
I'd put it in a tslint rule if there was one (palantir/tslint#960)...
| </div> | ||
| ) | ||
|
|
||
| export default Configuration |
There was a problem hiding this comment.
I'm going to be removing all default exports, so might as well remove this one 😄
| } | ||
|
|
||
| @action | ||
| public onNodeClick(node: Node): void { |
There was a problem hiding this comment.
One of my architectural goals is to keep models as dumb as possible, such that they are just data and no behaviour.
That means actions like these need to live somewhere else, preferably somewhere that we can test.
This is why I created a LocalisationPresenter for the localisation component, and any @action lives as a method on the presenter, such that the LocalisationModel itself does not have any actions on it. I'll soon be renaming LocalisationPresenter to LocalisationController as well.
Could we follow a similar pattern here? 😄 You'll probably want a separate ConfigurationController class where these actions can live 🙂
There was a problem hiding this comment.
Sounds good, will do.
| @import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,500'); | ||
|
|
||
| * { | ||
| box-sizing: border-box; |
There was a problem hiding this comment.
Do you think this will ever come back to bite us? Would libraries ever assume content-box is the default and break? Maybe its fine and we'll revisit if its a problem.
There was a problem hiding this comment.
Another option is this, which allows for easily resetting the box-sizing locally, when needed:
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}Reference: https://css-tricks.com/box-sizing/#article-header-id-6
| @@ -0,0 +1,3 @@ | |||
| <svg width="16" height="16" fill="#999" viewBox="0 0 24 24"> | |||
| <path d="M13,9H18.5L13,3.5V9M6,2H14L20,8V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V4C4,2.89 4.89,2 6,2M11,4H6V20H11L18,20V11H11V4Z" /> | |||
| </svg> No newline at end of file | |||
There was a problem hiding this comment.
Can you ensure all these SVG files have a trailing newline? 🙂
| } | ||
| } | ||
|
|
||
| export default Tree |
There was a problem hiding this comment.
I'm going to be removing all default exports, so might as well remove this one 😄
|
@BrendanAnnable Thanks for the suggestions, I'll make the requested changes and push updates soon. |
BrendanAnnable
left a comment
There was a problem hiding this comment.
Got a chance to run it, lookin' fancy! 😄
| </div> | ||
|
|
||
| <div className={style.treenode__children}> | ||
| { children.map((child, i) => <Tree key={i} data={child} level={level + 1} onClick={this.props.onClick} />) } |
There was a problem hiding this comment.
Are we safe to use i as a key here?
Source: https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
There was a problem hiding this comment.
Wasn't sure what to use as the key, and React was complaining without it, that's why I used the index.
We could use label + level, but there could be duplicates.
| <div className={style.treenode__label}>{ this.props.data.label }</div> | ||
| </div> | ||
|
|
||
| <div className={style.treenode__children}> |
There was a problem hiding this comment.
Can we optimise this by only rendering children if it is expanded? Then we can delete the higher specificity CSS selectors like .treenode--expanded > .treenode__children
There was a problem hiding this comment.
Maybe something like:
{this.props.data.expanded &&
<div className={style.treenode__children}>
...
</div>
}
| ) | ||
| } | ||
|
|
||
| private onClick = (e: any) => { |
There was a problem hiding this comment.
You can use MouseEvent instead of any here
There was a problem hiding this comment.
Using MouseEvent gives this error:
ERROR in [at-loader] ./src/client/components/configuration/tree/tree.tsx:44:14
TS2322: Type '{ className: any; onClick: (e: MouseEvent) => void; style: { paddingLeft: string; }; }' is not assignable to type 'HTMLProps<HTMLDivElement>'.
Types of property 'onClick' are incompatible.
Type '(e: MouseEvent) => void' is not assignable to type 'EventHandler<MouseEvent<HTMLDivElement>> | undefined'.
Type '(e: MouseEvent) => void' is not assignable to type 'EventHandler<MouseEvent<HTMLDivElement>>'.
Types of parameters 'e' and 'event' are incompatible.
Type 'MouseEvent<HTMLDivElement>' is not assignable to type 'MouseEvent'.
Property 'fromElement' is missing in type 'MouseEvent<HTMLDivElement>'.| import { Node, Tree } from './tree/tree' | ||
| import { TreeStore } from './tree_store' | ||
|
|
||
| const store = new TreeStore({ |
There was a problem hiding this comment.
I think its best that we stop using the word store and model and just use one throughout the code base (or at least come to a definition of the difference). Then we can maybe think of it as a more traditional "model view controller" architecture. So I suggest we replace every occurrence of store with model.
| export const Configuration = () => ( | ||
| <div className={style.configuration}> | ||
| <div className={style.configuration__header}> | ||
| <h1 className={style.configuration__headerTitle}>Configuration</h1> |
There was a problem hiding this comment.
We'll have to make a component for this header stuff 🙂
There was a problem hiding this comment.
I've made two wrapper components: view (for top-level views like dashboard or localization) and panel (for stuff like sidebars) which could be useful.
I placed them under components/configuration for now, but I think they should go higher (components/view|panel/ or components/common/view|panel/?) since they could be used by other views.
I can extract them into a separate pull request if needed.
| } | ||
|
|
||
| @observer | ||
| export class Tree extends React.Component<TreeProps, any> { |
There was a problem hiding this comment.
The second param type any should be void I believe
| export class Tree extends React.Component<TreeProps, any> { | ||
| constructor(props: any, context: any) { | ||
| super(props, context) | ||
| this.onClick = this.onClick.bind(this) |
There was a problem hiding this comment.
This line is not needed when you use class properties (which you're already doing!), so you can delete this line 🙂
Normal class method, needs the .bind in constructor if you want to pass it directly to something which won't keep the context:
onClick(e: MouseEvent) {
// "this" might change
}
Arrow function class property, automatically binds for you:
onClick = (e: MouseEvent) => {
// "this" will always be the class instance
}
There was a problem hiding this comment.
Nice, didn't know that!
|
|
||
| return ( | ||
| <div className={classes}> | ||
| <div className={style.treenode__header} onClick={this.onClick} style={headerInlineStyle}> |
There was a problem hiding this comment.
Could we use ul and li for this HTML structure so we don't need to use a calculated paddingLeft?
e.g. http://jsbin.com/zoyabovami/edit?html,css,output
Credit to @MonicaOlejniczak for the idea 🙂
There was a problem hiding this comment.
Hmmm, good point! You can sorta do it with pure CSS but it's pretty hacky.
There was a problem hiding this comment.
Ah! Makes sense. Maybe add a comment explaining why then? 🙂 Otherwise the next person will think the same thing haha
There was a problem hiding this comment.
We can still use ul and li though at least for the semantics
- Also use the sidebar color for hover and selected states
|
Hey @BrendanAnnable, After pulling in the latest master (1992968), I'm having trouble compiling the code. ErrorsERROR in [at-loader] ./src/client/components/configuration/editor/editor.tsx:33:2
TS2345: Argument of type 'typeof Editor' is not assignable to parameter of type 'string[]'.
Property 'includes' is missing in type 'typeof Editor'.
ERROR in [at-loader] ./src/client/components/configuration/editor/editor.tsx:39:41
TS2605: JSX element type 'MapField' is not a constructor function for JSX elements.
ERROR in [at-loader] ./src/client/components/configuration/editor/editor.tsx:40:42
TS2605: JSX element type 'ListField' is not a constructor function for JSX elements.
ERROR in [at-loader] ./src/client/components/configuration/editor/editor.tsx:45:11
TS2605: JSX element type 'ScalarField' is not a constructor function for JSX elements.
ERROR in [at-loader] ./src/client/components/configuration/editor/fields/scalar_field.tsx:22:13
TS2605: JSX element type 'NumberValue' is not a constructor function for JSX elements.
Types of property 'setState' are incompatible.
Type '{ <K extends never>(f: (prevState: void, props: FieldProps) => Pick<void, K>, callback?: (() => a...' is not assignable to type '{ <K extends never>(f: (prevState: {}, props: any) => Pick<{}, K>, callback?: (() => any) | undef...'.
Types of parameters 'f' and 'f' are incompatible.
Type '(prevState: {}, props: any) => Pick<{}, any>' is not assignable to type '(prevState: void, props: FieldProps) => Pick<void, any>'.
Types of parameters 'prevState' and 'prevState' are incompatible.
Type 'void' is not assignable to type '{}'.It seems to have something to do with I get no errors when I run |


Adding the configuration view. Feedback welcome.