Skip to content
This repository has been archived by the owner on Jul 3, 2023. It is now read-only.

[WIP] Configuration View #19

Open
wants to merge 22 commits into
base: master
Choose a base branch
from
Open

Conversation

JosephusPaye
Copy link
Member

Adding the configuration view. Feedback welcome.

- Includes a basic working Tree component for browsing configuration files
Copy link
Member

@BrendanAnnable BrendanAnnable left a 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;
Copy link
Member

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.

Copy link
Member

@BrendanAnnable BrendanAnnable May 19, 2017

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

Copy link
Member Author

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[]
}
Copy link
Member

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)...

Copy link
Member Author

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
Copy link
Member

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 {
Copy link
Member

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 🙂

Copy link
Member Author

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;
Copy link
Member

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.

Copy link
Member Author

@JosephusPaye JosephusPaye May 19, 2017

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>
Copy link
Member

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
Copy link
Member

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 😄

@JosephusPaye
Copy link
Member Author

@BrendanAnnable Thanks for the suggestions, I'll make the requested changes and push updates soon.

Copy link
Member

@BrendanAnnable BrendanAnnable left a 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} />) }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

@JosephusPaye JosephusPaye May 24, 2017

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}>
Copy link
Member

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

Copy link
Member

@BrendanAnnable BrendanAnnable May 19, 2017

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>
}

Copy link
Member Author

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) => {
Copy link
Member

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

Copy link
Member Author

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({
Copy link
Member

@BrendanAnnable BrendanAnnable May 19, 2017

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.

Copy link
Member Author

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>
Copy link
Member

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 🙂

Copy link
Member Author

@JosephusPaye JosephusPaye May 24, 2017

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> {
Copy link
Member

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

Copy link
Member Author

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)
Copy link
Member

@BrendanAnnable BrendanAnnable May 19, 2017

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
}

Copy link
Member Author

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}>
Copy link
Member

@BrendanAnnable BrendanAnnable May 19, 2017

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 🙂

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had that initially (see below) but changed to paddingLeft because I wanted the hover and selected highlights to be full width. I can switch back if necessary.

Using ul/li:

Using div and paddingLeft:

Copy link
Member

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.

Copy link
Member

@BrendanAnnable BrendanAnnable May 24, 2017

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

Copy link
Member

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: 6e431ac

@JosephusPaye
Copy link
Member Author

JosephusPaye commented Jun 27, 2017

Hey @BrendanAnnable,

After pulling in the latest master (1992968), I'm having trouble compiling the code.

Errors
ERROR 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 observer from mobx-react.

I get no errors when I run yarn tslint. To test checkout my latest commit: 9135187.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants