Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
N
nexpie-grafana-theme
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Registry
Registry
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Kornkitt Poolsup
nexpie-grafana-theme
Commits
47986908
Unverified
Commit
47986908
authored
Dec 11, 2018
by
Torkel Ödegaard
Committed by
GitHub
Dec 11, 2018
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #14433 from grafana/14274-develop-viz-keynav
14274 develop - VizPicker keyboard navigation
parents
74b3a509
20134c90
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
271 additions
and
103 deletions
+271
-103
public/app/features/dashboard/dashgrid/DataSourcePicker.tsx
+100
-73
public/app/features/dashboard/dashgrid/VizTypePicker.tsx
+62
-16
public/app/features/dashboard/dashgrid/VizTypePickerPlugin.tsx
+36
-0
public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx
+65
-0
public/sass/components/_panel_editor.scss
+8
-14
No files found.
public/app/features/dashboard/dashgrid/DataSourcePicker.tsx
View file @
47986908
import
React
,
{
PureComponent
}
from
'react'
;
import
classNames
from
'classnames'
;
import
_
from
'lodash'
;
import
withKeyboardNavigation
from
'./withKeyboardNavigation'
;
import
{
DataSourceSelectItem
}
from
'app/types'
;
interface
Props
{
export
interface
Props
{
onChangeDataSource
:
(
ds
:
any
)
=>
void
;
datasources
:
DataSourceSelectItem
[];
selected
?:
number
;
onKeyDown
?:
(
evt
:
any
,
maxSelectedIndex
:
number
,
onEnterAction
:
()
=>
void
)
=>
void
;
onMouseEnter
?:
(
select
:
number
)
=>
void
;
}
interface
State
{
searchQuery
:
string
;
}
export
class
DataSourcePicker
extends
PureComponent
<
Props
,
State
>
{
searchInput
:
HTMLElement
;
export
const
DataSourcePicker
=
withKeyboardNavigation
(
class
DataSourcePicker
extends
PureComponent
<
Props
,
State
>
{
searchInput
:
HTMLElement
;
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
searchQuery
:
''
,
};
}
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
searchQuery
:
''
,
};
}
getDataSources
()
{
const
{
searchQuery
}
=
this
.
state
;
const
regex
=
new
RegExp
(
searchQuery
,
'i'
);
const
{
datasources
}
=
this
.
props
;
getDataSources
()
{
const
{
searchQuery
}
=
this
.
state
;
const
regex
=
new
RegExp
(
searchQuery
,
'i'
);
const
{
datasources
}
=
this
.
props
;
const
filtered
=
datasources
.
filter
(
item
=>
{
return
regex
.
test
(
item
.
name
)
||
regex
.
test
(
item
.
meta
.
name
);
});
const
filtered
=
datasources
.
filter
(
item
=>
{
return
regex
.
test
(
item
.
name
)
||
regex
.
test
(
item
.
meta
.
name
);
});
return
filtered
;
}
return
filtered
;
}
get
maxSelectedIndex
()
{
const
filtered
=
this
.
getDataSources
();
return
filtered
.
length
-
1
;
}
renderDataSource
=
(
ds
:
DataSourceSelectItem
,
index
:
number
)
=>
{
const
{
onChangeDataSource
}
=
this
.
props
;
const
onClick
=
()
=>
onChangeDataSource
(
ds
);
const
cssClass
=
classNames
({
'ds-picker-list__item'
:
true
,
});
renderDataSource
=
(
ds
:
DataSourceSelectItem
,
index
:
number
)
=>
{
const
{
onChangeDataSource
,
selected
,
onMouseEnter
}
=
this
.
props
;
const
onClick
=
()
=>
onChangeDataSource
(
ds
);
const
isSelected
=
selected
===
index
;
const
cssClass
=
classNames
({
'ds-picker-list__item'
:
true
,
'ds-picker-list__item--selected'
:
isSelected
,
});
return
(
<
div
key=
{
index
}
className=
{
cssClass
}
title=
{
ds
.
name
}
onClick=
{
onClick
}
onMouseEnter=
{
()
=>
onMouseEnter
(
index
)
}
>
<
img
className=
"ds-picker-list__img"
src=
{
ds
.
meta
.
info
.
logos
.
small
}
/>
<
div
className=
"ds-picker-list__name"
>
{
ds
.
name
}
</
div
>
</
div
>
);
};
return
(
<
div
key=
{
index
}
className=
{
cssClass
}
title=
{
ds
.
name
}
onClick=
{
onClick
}
>
<
img
className=
"ds-picker-list__img"
src=
{
ds
.
meta
.
info
.
logos
.
small
}
/>
<
div
className=
"ds-picker-list__name"
>
{
ds
.
name
}
</
div
>
</
div
>
);
};
componentDidMount
()
{
setTimeout
(()
=>
{
this
.
searchInput
.
focus
();
},
300
);
}
componentDidMount
()
{
setTimeout
(()
=>
{
this
.
searchInput
.
focus
();
},
300
);
}
onSearchQueryChange
=
evt
=>
{
const
value
=
evt
.
target
.
value
;
this
.
setState
(
prevState
=>
({
...
prevState
,
searchQuery
:
value
,
}));
};
onSearchQueryChange
=
evt
=>
{
const
value
=
evt
.
target
.
value
;
this
.
setState
(
prevState
=>
({
...
prevState
,
searchQuery
:
value
,
}));
};
renderFilters
()
{
const
{
searchQuery
}
=
this
.
state
;
const
{
onKeyDown
}
=
this
.
props
;
return
(
<>
<
label
className=
"gf-form--has-input-icon"
>
<
input
type=
"text"
className=
"gf-form-input width-13"
placeholder=
""
ref=
{
elem
=>
(
this
.
searchInput
=
elem
)
}
onChange=
{
this
.
onSearchQueryChange
}
value=
{
searchQuery
}
onKeyDown=
{
evt
=>
{
onKeyDown
(
evt
,
this
.
maxSelectedIndex
,
()
=>
{
const
{
onChangeDataSource
,
selected
}
=
this
.
props
;
const
ds
=
this
.
getDataSources
()[
selected
];
onChangeDataSource
(
ds
);
});
}
}
/>
<
i
className=
"gf-form-input-icon fa fa-search"
/>
</
label
>
</>
);
}
renderFilters
()
{
const
{
searchQuery
}
=
this
.
state
;
return
(
<>
<
label
className=
"gf-form--has-input-icon"
>
<
input
type=
"text"
className=
"gf-form-input width-13"
placeholder=
""
ref=
{
elem
=>
(
this
.
searchInput
=
elem
)
}
onChange=
{
this
.
onSearchQueryChange
}
value=
{
searchQuery
}
/>
<
i
className=
"gf-form-input-icon fa fa-search"
/>
</
label
>
</>
);
render
()
{
return
(
<>
<
div
className=
"cta-form__bar"
>
{
this
.
renderFilters
()
}
<
div
className=
"gf-form--grow"
/>
</
div
>
<
div
className=
"ds-picker-list"
>
{
this
.
getDataSources
().
map
(
this
.
renderDataSource
)
}
</
div
>
</>
);
}
}
);
render
()
{
return
(
<>
<
div
className=
"cta-form__bar"
>
{
this
.
renderFilters
()
}
<
div
className=
"gf-form--grow"
/>
</
div
>
<
div
className=
"ds-picker-list"
>
{
this
.
getDataSources
().
map
(
this
.
renderDataSource
)
}
</
div
>
</>
);
}
}
export
default
DataSourcePicker
;
public/app/features/dashboard/dashgrid/VizTypePicker.tsx
View file @
47986908
import
React
,
{
PureComponent
}
from
'react'
;
import
classNames
from
'classnames'
;
import
_
from
'lodash'
;
import
config
from
'app/core/config'
;
import
{
PanelPlugin
}
from
'app/types/plugins'
;
import
VizTypePickerPlugin
from
'./VizTypePickerPlugin'
;
interface
Props
{
current
:
PanelPlugin
;
...
...
@@ -12,6 +12,7 @@ interface Props {
interface
State
{
searchQuery
:
string
;
selected
:
number
;
}
export
class
VizTypePicker
extends
PureComponent
<
Props
,
State
>
{
...
...
@@ -23,9 +24,50 @@ export class VizTypePicker extends PureComponent<Props, State> {
this
.
state
=
{
searchQuery
:
''
,
selected
:
0
,
};
}
get
maxSelectedIndex
()
{
const
filteredPluginList
=
this
.
getFilteredPluginList
();
return
filteredPluginList
.
length
-
1
;
}
goRight
=
()
=>
{
const
nextIndex
=
this
.
state
.
selected
>=
this
.
maxSelectedIndex
?
0
:
this
.
state
.
selected
+
1
;
this
.
setState
({
selected
:
nextIndex
,
});
};
goLeft
=
()
=>
{
const
nextIndex
=
this
.
state
.
selected
<=
0
?
this
.
maxSelectedIndex
:
this
.
state
.
selected
-
1
;
this
.
setState
({
selected
:
nextIndex
,
});
};
onKeyDown
=
evt
=>
{
if
(
evt
.
key
===
'ArrowDown'
)
{
evt
.
preventDefault
();
this
.
goRight
();
}
if
(
evt
.
key
===
'ArrowUp'
)
{
evt
.
preventDefault
();
this
.
goLeft
();
}
if
(
evt
.
key
===
'Enter'
)
{
const
filteredPluginList
=
this
.
getFilteredPluginList
();
this
.
props
.
onTypeChanged
(
filteredPluginList
[
this
.
state
.
selected
]);
}
};
componentDidMount
()
{
setTimeout
(()
=>
{
this
.
searchInput
.
focus
();
},
300
);
}
getPanelPlugins
(
filter
):
PanelPlugin
[]
{
const
panels
=
_
.
chain
(
config
.
panels
)
.
filter
({
hideFromList
:
false
})
...
...
@@ -36,26 +78,29 @@ export class VizTypePicker extends PureComponent<Props, State> {
return
_
.
sortBy
(
panels
,
'sort'
);
}
renderVizPlugin
=
(
plugin
:
PanelPlugin
,
index
:
number
)
=>
{
const
cssClass
=
classNames
({
'viz-picker__item'
:
true
,
'viz-picker__item--selected'
:
plugin
.
id
===
this
.
props
.
current
.
id
,
onMouseEnter
=
(
mouseEnterIndex
:
number
)
=>
{
this
.
setState
({
selected
:
mouseEnterIndex
,
});
};
renderVizPlugin
=
(
plugin
:
PanelPlugin
,
index
:
number
)
=>
{
const
isSelected
=
this
.
state
.
selected
===
index
;
const
isCurrent
=
plugin
.
id
===
this
.
props
.
current
.
id
;
return
(
<
div
key=
{
index
}
className=
{
cssClass
}
onClick=
{
()
=>
this
.
props
.
onTypeChanged
(
plugin
)
}
title=
{
plugin
.
name
}
>
<
div
className=
"viz-picker__item-name"
>
{
plugin
.
name
}
</
div
>
<
img
className=
"viz-picker__item-img"
src=
{
plugin
.
info
.
logos
.
small
}
/>
</
div
>
<
VizTypePickerPlugin
key=
{
plugin
.
id
}
isSelected=
{
isSelected
}
isCurrent=
{
isCurrent
}
plugin=
{
plugin
}
onMouseEnter=
{
()
=>
{
this
.
onMouseEnter
(
index
);
}
}
onClick=
{
()
=>
this
.
props
.
onTypeChanged
(
plugin
)
}
/>
);
};
componentDidMount
()
{
setTimeout
(()
=>
{
this
.
searchInput
.
focus
();
},
300
);
}
getFilteredPluginList
=
():
PanelPlugin
[]
=>
{
const
{
searchQuery
}
=
this
.
state
;
const
regex
=
new
RegExp
(
searchQuery
,
'i'
);
...
...
@@ -73,6 +118,7 @@ export class VizTypePicker extends PureComponent<Props, State> {
this
.
setState
(
prevState
=>
({
...
prevState
,
searchQuery
:
value
,
selected
:
0
,
}));
};
...
...
@@ -86,6 +132,7 @@ export class VizTypePicker extends PureComponent<Props, State> {
placeholder=
""
ref=
{
elem
=>
(
this
.
searchInput
=
elem
)
}
onChange=
{
this
.
onSearchQueryChange
}
onKeyDown=
{
this
.
onKeyDown
}
/>
<
i
className=
"gf-form-input-icon fa fa-search"
/>
</
label
>
...
...
@@ -102,7 +149,6 @@ export class VizTypePicker extends PureComponent<Props, State> {
{
this
.
renderFilters
()
}
<
div
className=
"gf-form--grow"
/>
</
div
>
<
div
className=
"viz-picker"
>
{
filteredPluginList
.
map
(
this
.
renderVizPlugin
)
}
</
div
>
</>
);
...
...
public/app/features/dashboard/dashgrid/VizTypePickerPlugin.tsx
0 → 100644
View file @
47986908
import
React
from
'react'
;
import
classNames
from
'classnames'
;
import
{
PanelPlugin
}
from
'app/types/plugins'
;
interface
Props
{
isSelected
:
boolean
;
isCurrent
:
boolean
;
plugin
:
PanelPlugin
;
onClick
:
()
=>
void
;
onMouseEnter
:
()
=>
void
;
}
const
VizTypePickerPlugin
=
React
.
memo
(
({
isSelected
,
isCurrent
,
plugin
,
onClick
,
onMouseEnter
}:
Props
)
=>
{
const
cssClass
=
classNames
({
'viz-picker__item'
:
true
,
'viz-picker__item--selected'
:
isSelected
,
'viz-picker__item--current'
:
isCurrent
,
});
return
(
<
div
className=
{
cssClass
}
onClick=
{
onClick
}
title=
{
plugin
.
name
}
onMouseEnter=
{
onMouseEnter
}
>
<
div
className=
"viz-picker__item-name"
>
{
plugin
.
name
}
</
div
>
<
img
className=
"viz-picker__item-img"
src=
{
plugin
.
info
.
logos
.
small
}
/>
</
div
>
);
},
(
prevProps
,
nextProps
)
=>
{
if
(
prevProps
.
isSelected
===
nextProps
.
isSelected
&&
prevProps
.
isCurrent
===
nextProps
.
isCurrent
)
{
return
true
;
}
return
false
;
}
);
export
default
VizTypePickerPlugin
;
public/app/features/dashboard/dashgrid/withKeyboardNavigation.tsx
0 → 100644
View file @
47986908
import
React
from
'react'
;
import
{
Props
}
from
'./DataSourcePicker'
;
interface
State
{
selected
:
number
;
}
const
withKeyboardNavigation
=
WrappedComponent
=>
{
return
class
extends
React
.
Component
<
Props
,
State
>
{
constructor
(
props
)
{
super
(
props
);
this
.
state
=
{
selected
:
0
,
};
}
goToNext
=
(
maxSelectedIndex
:
number
)
=>
{
const
nextIndex
=
this
.
state
.
selected
>=
maxSelectedIndex
?
0
:
this
.
state
.
selected
+
1
;
this
.
setState
({
selected
:
nextIndex
,
});
};
goToPrev
=
(
maxSelectedIndex
:
number
)
=>
{
const
nextIndex
=
this
.
state
.
selected
<=
0
?
maxSelectedIndex
:
this
.
state
.
selected
-
1
;
this
.
setState
({
selected
:
nextIndex
,
});
};
onKeyDown
=
(
evt
:
KeyboardEvent
,
maxSelectedIndex
:
number
,
onEnterAction
:
any
)
=>
{
if
(
evt
.
key
===
'ArrowDown'
)
{
evt
.
preventDefault
();
this
.
goToNext
(
maxSelectedIndex
);
}
if
(
evt
.
key
===
'ArrowUp'
)
{
evt
.
preventDefault
();
this
.
goToPrev
(
maxSelectedIndex
);
}
if
(
evt
.
key
===
'Enter'
&&
onEnterAction
)
{
onEnterAction
();
}
};
onMouseEnter
=
(
mouseEnterIndex
:
number
)
=>
{
this
.
setState
({
selected
:
mouseEnterIndex
,
});
};
render
()
{
return
(
<
WrappedComponent
selected=
{
this
.
state
.
selected
}
onKeyDown=
{
this
.
onKeyDown
}
onMouseEnter=
{
this
.
onMouseEnter
}
{
...
this
.
props
}
/>
);
}
};
};
export
default
withKeyboardNavigation
;
public/sass/components/_panel_editor.scss
View file @
47986908
...
...
@@ -157,21 +157,15 @@
padding-bottom
:
6px
;
transition
:
transform
1
ease
;
&
:hover
{
box-shadow
:
$panel-editor-viz-item-shadow-hover
;
background
:
$panel-editor-viz-item-bg-hover
;
border
:
$panel-editor-viz-item-border-hover
;
}
&
--selected
{
&
--current
{
box-shadow
:
0
0
6px
$orange
;
border
:
1px
solid
$orange
;
}
&
:hover
{
box-shadow
:
0
0
6px
$orange
;
border
:
1px
solid
$orange
;
background
:
$panel-editor-viz-item-bg-hover-active
;
}
&
--selected
{
box-shadow
:
$panel-editor-viz-item-shadow-hover
;
background
:
$panel-editor-viz-item-bg-hover
;
border
:
$panel-editor-viz-item-border-hover
;
}
}
...
...
@@ -263,13 +257,13 @@
align-items
:
center
;
height
:
44px
;
&
:hover
{
&
--selected
{
background
:
$panel-editor-viz-item-bg-hover
;
border
:
$panel-editor-viz-item-border-hover
;
box-shadow
:
$panel-editor-viz-item-shadow-hover
;
}
&
--
selected
{
&
--
active
{
box-shadow
:
0
0
6px
$orange
;
border
:
1px
solid
$orange
;
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment