Skip to content

Commit

Permalink
feat(layout): add dataset view with draggable sidebar, datasets picke…
Browse files Browse the repository at this point in the history
…r, etc
  • Loading branch information
chriswhong committed Jul 10, 2019
1 parent 1d4ccd6 commit 827471b
Show file tree
Hide file tree
Showing 29 changed files with 1,677 additions and 336 deletions.
23 changes: 23 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"extends": [
"standard-with-typescript",
"plugin:react/recommended"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
},
"project": "./tsconfig.json"
},
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-member-accessibility": "off",
<<<<<<< HEAD
"react/prop-types": "off",
=======
"react/prop-types": [2, { ignore: ['children'] }]
>>>>>>> 703791a... chore(*): add eslint with typescript, react, and standard rules (#8)
}
}
22 changes: 22 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
The MIT License (MIT)

Copyright (c) 2015-present C. T. Lin

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

Welcome to the qri desktop repository

The Qri Desktop app helps you manage your datasets.
The Qri Desktop app helps you manage your datasets.
24 changes: 24 additions & 0 deletions app/actions/counter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { actionCreatorVoid } from './helpers'

export const increment = actionCreatorVoid('INCREMENT_COUNTER')
export const decrement = actionCreatorVoid('DECREMENT_COUNTER')

export function incrementIfOdd () {
return (dispatch: Function, getState: Function) => {
const { counter } = getState()

if (counter % 2 === 0) {
return
}

dispatch(increment())
}
}

export function incrementAsync (delay: number = 1000) {
return (dispatch: Function) => {
setTimeout(() => {
dispatch(increment())
}, delay)
}
}
36 changes: 36 additions & 0 deletions app/actions/helpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Action } from 'redux'

export interface IAction extends Action {} // eslint-disable-line
export interface IActionWithPayload<T> extends Action {
readonly payload: T
}

interface IActionCreator<T> {
readonly type: string
(payload: T): IActionWithPayload<T>

test(action: IAction): action is IActionWithPayload<T>
}

interface IActionCreatorVoid {
readonly type: string
(): IAction

test(action: IAction): action is IAction
}

export const actionCreator = <T>(type: string): IActionCreator<T> =>
Object.assign((payload: T): any => ({ type, payload }), { // eslint-disable-line
type,
test (action: IAction): action is IActionWithPayload<T> {
return action.type === type
}
})

export const actionCreatorVoid = (type: string): IActionCreatorVoid =>
Object.assign((): any => ({ type }), { // eslint-disable-line
type,
test (action: IAction): action is IAction {
return action.type === type
}
})
2 changes: 1 addition & 1 deletion app/app.global.scss
Original file line number Diff line number Diff line change
@@ -1 +1 @@
@import "scss/style"
@import "scss/style"
19 changes: 13 additions & 6 deletions app/components/App.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import * as React from 'react'
import DatasetContainer from '../containers/DatasetContainer'

// App is the main component and currently the only view
// Everything must flow through here
export default class App extends React.Component {
render() {
return (
<div>Hello World</div>
)
}
}
render () {
return (
<<<<<<< HEAD
<div id='app'>
<DatasetContainer />
</div>
=======
<div>Hello World</div>
>>>>>>> 703791a... chore(*): add eslint with typescript, react, and standard rules (#8)
)
}
}
55 changes: 55 additions & 0 deletions app/components/DatasetSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import * as React from 'react'

interface FileRowProps {
name: string
}

const FileRow: React.SFC<FileRowProps> = (props) => {
return (
<div className='file-row sidebar-row'>
<div className='label'>{props.name}</div>
</div>
)
}

export default class DatasetSidebar extends React.Component<{}, { activeTab: string }> {
constructor (p: {}) {
super(p)
this.state = {
activeTab: 'status'
}
}

handleTabClick (activeTab: string) {
this.setState({ activeTab })
}

render () {
const { activeTab } = this.state
return (
<div id='dataset-sidebar'>
<div id='tabs' className='sidebar-row'>
<div className={'tab ' + (activeTab === 'status' ? 'active' : '')} onClick={() => this.handleTabClick('status')}>Status</div>
<div className={'tab ' + (activeTab === 'status' ? '' : 'active')} onClick={() => this.handleTabClick('history')}>History</div>
</div>
<div id='content'>
<div id='status-content' className='sidebar-content' hidden = {activeTab !== 'status'}>
<div className='sidebar-row'>
<div className='changes'>
Changes
</div>
</div>
<FileRow name='Meta' />
<FileRow name='Body' />
<FileRow name='Schema' />
</div>
<div id='history-content' className='sidebar-content' hidden = {activeTab === 'status'}>
<FileRow name='Last Commit' />
<FileRow name='Some other Commit' />
<FileRow name='First Commit' />
</div>
</div>
</div>
)
}
}
148 changes: 148 additions & 0 deletions app/components/Resizable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import * as React from 'react'

/**
* Component abstracting a resizable panel.
* Borrowed from https://github.com/desktop/desktop/blob/development/app/src/ui/resizable/resizable.tsx
*
* Note: this component is pure, consumers must subscribe to the
* onResize and onReset event and update the width prop accordingly.
*/
export class Resizable extends React.Component<IResizableProps, {}> {
public static defaultProps: IResizableProps = {
width: 250,
maximumWidth: 350,
minimumWidth: 200
}

private startWidth: number | null = null
private startX: number | null = null

/**
* Returns the current width as determined by props.
*
* This value will be constrained by the maximum and minimum
* with props and might not be identical to that of props.width.
*/
private getCurrentWidth () {
return this.clampWidth(this.props.width)
}

/**
* Constrains the provided width to lie within the minimum and
* maximum widths as determined by props
*/
private clampWidth (width: number) {
return Math.max(
this.props.minimumWidth!, // eslint-disable-line
Math.min(this.props.maximumWidth!, width) // eslint-disable-line
)
}

/**
* Handler for when the user presses the mouse button over the resize
* handle.
*/
private handleDragStart = (e: React.MouseEvent<any>) => {
this.startX = e.clientX
this.startWidth = this.getCurrentWidth() || null

document.addEventListener('mousemove', this.handleDragMove)
document.addEventListener('mouseup', this.handleDragStop)

e.preventDefault()
}

/**
* Handler for when the user moves the mouse while dragging
*/
private handleDragMove = (e: MouseEvent) => {
if (this.startWidth == null || this.startX == null) {
return
}

const deltaX = e.clientX - this.startX
const newWidth = this.startWidth + deltaX
const newWidthClamped = this.clampWidth(newWidth)

if (this.props.onResize) {
this.props.onResize(newWidthClamped)
}

e.preventDefault()
}

/**
* Handler for when the user lets go of the mouse button during
* a resize operation.
*/
private handleDragStop = (e: MouseEvent) => {
document.removeEventListener('mousemove', this.handleDragMove)
document.removeEventListener('mouseup', this.handleDragStop)

e.preventDefault()
}

/**
* Handler for when the resize handle is double clicked.
*
* Resets the panel width to its default value and clears
* any persisted value.
*/
private handleDoubleClick = () => {
if (this.props.onReset) {
this.props.onReset()
}
}

public render () {
const style: React.CSSProperties = {
width: this.getCurrentWidth(),
maxWidth: this.props.maximumWidth,
minWidth: this.props.minimumWidth
}

return (
<div id={this.props.id} className="resizable-component" style={style}>
{this.props.children}
<div
onMouseDown={this.handleDragStart}
onDoubleClick={this.handleDoubleClick}
className="resize-handle"
/>
</div>
)
}
}

export interface IResizableProps {
readonly width: number

/** The maximum width the panel can be resized to.
*
* @default 350
*/
readonly maximumWidth?: number

/**
* The minimum width the panel can be resized to.
*
* @default 150
*/
readonly minimumWidth?: number

/** The optional ID for the root element. */
readonly id?: string

/**
* Handler called when the width of the component has changed
* through an explicit resize event (dragging the handle).
*/
readonly onResize?: (newWidth: number) => void

/**
* Handler called when the resizable component has been
* reset (ie restored to its original width by double clicking
* on the resize handle).
*/
readonly onReset?: () => void
}
6 changes: 3 additions & 3 deletions app/containers/AppContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import * as React from 'react'

export default class AppComponent extends React.Component {
render() {
render () {
return (
<div>{this.props.children}</div>
<div id="app-container">{this.props.children}</div>
)
}
}
}
Loading

0 comments on commit 827471b

Please sign in to comment.