-
Notifications
You must be signed in to change notification settings - Fork 1
[WIP] Configuration View #19
base: master
Are you sure you want to change the base?
Conversation
- Includes a basic working Tree component for browsing configuration files
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I should probably should look into adding something like https://github.com/stylelint/stylelint
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do.
leaf: boolean, | ||
selected: boolean, | ||
children?: Node[] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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)...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gotcha.
</div> | ||
) | ||
|
||
export default Configuration |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, will do.
@@ -1,5 +1,9 @@ | |||
@import url('https://fonts.googleapis.com/css?family=Open+Sans:300,400,500'); | |||
|
|||
* { | |||
box-sizing: border-box; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you ensure all these SVG files have a trailing newline? 🙂
} | ||
} | ||
|
||
export default Tree |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe something like:
{this.props.data.expanded &&
<div className={style.treenode__children}>
...
</div>
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
) | ||
} | ||
|
||
private onClick = (e: any) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use MouseEvent
instead of any
here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done!
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll have to make a component for this header stuff 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The second param type any
should be void
I believe
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmmm, good point! You can sorta do it with pure CSS but it's pretty hacky.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can still use ul
and li
though at least for the semantics
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done: 6e431ac
- 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.