Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
jobily
Questdb
提交
8fa21f67
Q
Questdb
项目概览
jobily
/
Questdb
10 个月 前同步成功
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
DevOps
流水线
流水线任务
计划
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
Q
Questdb
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
DevOps
DevOps
流水线
流水线任务
计划
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
流水线任务
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
未验证
提交
8fa21f67
编写于
6月 11, 2020
作者:
M
Méril
提交者:
GitHub
6月 11, 2020
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
feat(Console): migrate grid/chart wrapper to React + add row count (#386)
上级
78dd24ca
变更
38
隐藏空白更改
内联
并排
Showing
38 changed file
with
640 addition
and
407 deletion
+640
-407
ui/package-lock.json
ui/package-lock.json
+5
-0
ui/package.json
ui/package.json
+2
-1
ui/src/components/Button/index.tsx
ui/src/components/Button/index.tsx
+18
-42
ui/src/components/Hooks/index.ts
ui/src/components/Hooks/index.ts
+1
-0
ui/src/components/Hooks/useMediaQuery.tsx
ui/src/components/Hooks/useMediaQuery.tsx
+27
-0
ui/src/components/PaneMenu/index.tsx
ui/src/components/PaneMenu/index.tsx
+1
-1
ui/src/components/Splitter/index.tsx
ui/src/components/Splitter/index.tsx
+159
-57
ui/src/components/ToggleButton/index.tsx
ui/src/components/ToggleButton/index.tsx
+24
-18
ui/src/index.hbs
ui/src/index.hbs
+1
-1
ui/src/index.tsx
ui/src/index.tsx
+4
-1
ui/src/js/console/console-controller.js
ui/src/js/console/console-controller.js
+2
-90
ui/src/js/console/import-controller.js
ui/src/js/console/import-controller.js
+20
-40
ui/src/js/console/index.js
ui/src/js/console/index.js
+5
-10
ui/src/partials/console.hbs
ui/src/partials/console.hbs
+0
-67
ui/src/scenes/Editor/Ace/index.tsx
ui/src/scenes/Editor/Ace/index.tsx
+1
-0
ui/src/scenes/Editor/Menu/index.tsx
ui/src/scenes/Editor/Menu/index.tsx
+6
-6
ui/src/scenes/Editor/QueryPicker/index.tsx
ui/src/scenes/Editor/QueryPicker/index.tsx
+1
-1
ui/src/scenes/Editor/QueryResult/index.tsx
ui/src/scenes/Editor/QueryResult/index.tsx
+1
-1
ui/src/scenes/Footer/index.tsx
ui/src/scenes/Footer/index.tsx
+1
-1
ui/src/scenes/Layout/index.tsx
ui/src/scenes/Layout/index.tsx
+65
-8
ui/src/scenes/Notifications/Notification/index.tsx
ui/src/scenes/Notifications/Notification/index.tsx
+3
-3
ui/src/scenes/Notifications/index.tsx
ui/src/scenes/Notifications/index.tsx
+1
-1
ui/src/scenes/Result/index.tsx
ui/src/scenes/Result/index.tsx
+188
-0
ui/src/scenes/Schema/Row/index.tsx
ui/src/scenes/Schema/Row/index.tsx
+10
-6
ui/src/scenes/Schema/index.tsx
ui/src/scenes/Schema/index.tsx
+12
-6
ui/src/scenes/Sidebar/index.tsx
ui/src/scenes/Sidebar/index.tsx
+9
-7
ui/src/store/Query/actions.ts
ui/src/store/Query/actions.ts
+8
-0
ui/src/store/Query/reducers.ts
ui/src/store/Query/reducers.ts
+7
-0
ui/src/store/Query/selectors.ts
ui/src/store/Query/selectors.ts
+6
-0
ui/src/store/Query/types.ts
ui/src/store/Query/types.ts
+10
-0
ui/src/styles/_base.scss
ui/src/styles/_base.scss
+2
-1
ui/src/styles/_editor.scss
ui/src/styles/_editor.scss
+1
-7
ui/src/styles/_grid.scss
ui/src/styles/_grid.scss
+1
-0
ui/src/styles/_import.scss
ui/src/styles/_import.scss
+12
-1
ui/src/styles/_navigation.scss
ui/src/styles/_navigation.scss
+1
-1
ui/src/styles/_splitter.scss
ui/src/styles/_splitter.scss
+14
-19
ui/src/utils/bus.ts
ui/src/utils/bus.ts
+1
-0
ui/src/utils/questdb.ts
ui/src/utils/questdb.ts
+10
-10
未找到文件。
ui/package-lock.json
浏览文件 @
8fa21f67
...
...
@@ -12750,6 +12750,11 @@
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==",
"dev": true
},
"use-media": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-media/-/use-media-1.4.0.tgz",
"integrity": "sha512-XsgyUAf3nhzZmEfhc5MqLHwyaPjs78bgytpVJ/xDl0TF4Bptf3vEpBNBBT/EIKOmsOc8UbuECq3mrP3mt1QANA=="
},
"util": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
...
...
ui/package.json
浏览文件 @
8fa21f67
...
...
@@ -50,7 +50,8 @@
"rxjs"
:
"6.5.5"
,
"slim-select"
:
"1.26.0"
,
"styled-components"
:
"5.1.1"
,
"styled-icons"
:
"10.2.1"
"styled-icons"
:
"10.2.1"
,
"use-media"
:
"1.4.0"
},
"devDependencies"
:
{
"@babel/cli"
:
"7.10.1"
,
...
...
ui/src/components/Button/index.tsx
浏览文件 @
8fa21f67
import
{
darken
}
from
"
polished
"
import
React
,
{
forwardRef
,
MouseEvent
,
ReactNode
,
Ref
}
from
"
react
"
import
{
MouseEvent
,
ReactNode
}
from
"
react
"
import
styled
,
{
css
}
from
"
styled-components
"
import
type
{
Color
}
from
"
types
"
...
...
@@ -10,18 +10,22 @@ import { bezierTransition } from "../Transition"
type
Size
=
"
sm
"
|
"
md
"
type
Type
=
"
button
"
|
"
submit
"
const
defaultProps
:
Readonly
<
{
const
size
:
Size
=
"
md
"
const
type
:
Type
=
"
button
"
const
defaultProps
=
{
size
,
type
,
}
export
type
ButtonProps
=
Readonly
<
{
children
?:
ReactNode
className
?:
string
disabled
?:
boolean
onClick
?:
(
event
:
MouseEvent
)
=>
void
size
:
Size
type
:
Type
}
>
=
{
size
:
"
md
"
,
type
:
"
button
"
,
}
export
type
ButtonProps
=
Partial
<
typeof
defaultProps
>
size
?:
Size
type
?:
Type
}
>
type
ThemeShape
=
{
background
:
Color
...
...
@@ -82,7 +86,7 @@ const getTheme = (
}
`
const
Primary
=
styled
.
button
<
ButtonProps
>
`
export
const
PrimaryButton
=
styled
.
button
<
ButtonProps
>
`
${
baseCss
}
;
${
getTheme
(
{
...
...
@@ -103,16 +107,9 @@ const Primary = styled.button<ButtonProps>`
)}
;
`
const
PrimaryButtonWithRef
=
(
props
:
ButtonProps
,
ref
:
Ref
<
HTMLButtonElement
>
,
)
=>
<
Primary
{
...
props
}
ref
=
{
ref
}
/>
export
const
PrimaryButton
=
forwardRef
(
PrimaryButtonWithRef
)
PrimaryButton
.
defaultProps
=
defaultProps
const
Secondary
=
styled
.
button
<
ButtonProps
>
`
export
const
SecondaryButton
=
styled
.
button
<
ButtonProps
>
`
${
baseCss
}
;
${
getTheme
(
{
...
...
@@ -133,16 +130,9 @@ const Secondary = styled.button<ButtonProps>`
)}
;
`
const
SecondaryButtonWithRef
=
(
props
:
ButtonProps
,
ref
:
Ref
<
HTMLButtonElement
>
,
)
=>
<
Secondary
{
...
props
}
ref
=
{
ref
}
/>
export
const
SecondaryButton
=
forwardRef
(
SecondaryButtonWithRef
)
SecondaryButton
.
defaultProps
=
defaultProps
const
Success
=
styled
.
button
<
ButtonProps
>
`
export
const
SuccessButton
=
styled
.
button
<
ButtonProps
>
`
${
baseCss
}
;
${
getTheme
(
{
...
...
@@ -163,16 +153,9 @@ const Success = styled.button<ButtonProps>`
)}
;
`
const
SuccessButtonWithRef
=
(
props
:
ButtonProps
,
ref
:
Ref
<
HTMLButtonElement
>
,
)
=>
<
Success
{
...
props
}
ref
=
{
ref
}
/>
export
const
SuccessButton
=
forwardRef
(
SuccessButtonWithRef
)
SuccessButton
.
defaultProps
=
defaultProps
const
Error
=
styled
.
button
<
ButtonProps
>
`
export
const
ErrorButton
=
styled
.
button
<
ButtonProps
>
`
${
baseCss
}
;
${
getTheme
(
{
...
...
@@ -193,11 +176,4 @@ const Error = styled.button<ButtonProps>`
)}
;
`
const
ErrorButtonWithRef
=
(
props
:
ButtonProps
,
ref
:
Ref
<
HTMLButtonElement
>
,
)
=>
<
Error
{
...
props
}
ref
=
{
ref
}
/>
export
const
ErrorButton
=
forwardRef
(
ErrorButtonWithRef
)
ErrorButton
.
defaultProps
=
defaultProps
ui/src/components/Hooks/index.ts
浏览文件 @
8fa21f67
export
*
from
"
./useKeyPress
"
export
*
from
"
./useMediaQuery
"
export
*
from
"
./usePopperStyles
"
export
*
from
"
./useTransition
"
ui/src/components/Hooks/useMediaQuery.tsx
0 → 100644
浏览文件 @
8fa21f67
import
React
,
{
createContext
,
ReactNode
,
useContext
,
useMemo
}
from
"
react
"
import
useMedia
from
"
use-media
"
type
Props
=
Readonly
<
{
children
:
ReactNode
}
>
const
mediaQueries
=
{
smScreen
:
"
(max-width: 767px)
"
,
}
const
MediaQueryContext
=
createContext
({
smScreen
:
false
,
})
export
const
MediaQueryProvider
=
({
children
}:
Props
)
=>
{
const
smScreen
=
useMedia
(
mediaQueries
.
smScreen
)
const
value
=
useMemo
(()
=>
({
smScreen
}),
[
smScreen
])
return
(
<
MediaQueryContext
.
Provider
value
=
{
value
}
>
{
children
}
</
MediaQueryContext
.
Provider
>
)
}
export
const
useMediaQuery
=
()
=>
useContext
(
MediaQueryContext
)
ui/src/components/PaneMenu/index.tsx
浏览文件 @
8fa21f67
...
...
@@ -11,7 +11,7 @@ type Props = Readonly<{
export
const
PaneMenu
=
styled
.
div
`
position: relative;
display: flex;
height: 4
1px
;
height: 4
rem
;
padding: 0 1rem;
align-items: center;
background:
${
color
(
"
draculaBackgroundDarker
"
)}
;
...
...
ui/src/components/Splitter/index.tsx
浏览文件 @
8fa21f67
import
React
,
{
MouseEvent
as
ReactMouseEvent
,
TouchEvent
as
ReactTouchEvent
,
useCallback
,
useEffect
,
useRef
,
useState
,
}
from
"
react
"
import
styled
from
"
styled-components
"
import
styled
,
{
createGlobalStyle
,
css
}
from
"
styled-components
"
import
{
DragIndicator
}
from
"
@styled-icons/material/DragIndicator
"
import
{
color
}
from
"
utils
"
const
PreventUserSelectionHorizontal
=
createGlobalStyle
`
html {
user-select: none;
cursor: ew-resize !important;
pointer-events: none;
}
`
const
PreventUserSelectionVertical
=
createGlobalStyle
`
html {
user-select: none;
cursor: row-resize !important;
pointer-events: none;
}
`
type
Props
=
Readonly
<
{
direction
:
"
vertical
"
|
"
horizontal
"
max
?:
number
min
?:
number
onChange
:
(
x
:
number
)
=>
void
onChange
:
(
value
:
number
)
=>
void
}
>
const
DragIcon
=
styled
(
DragIndicator
)
`
const
Horizontal
DragIcon
=
styled
(
DragIndicator
)
`
position: absolute;
color:
${
color
(
"
gray1
"
)}
;
`
const
Wrapper
=
styled
.
div
`
const
VerticalDragIcon
=
styled
(
HorizontalDragIcon
)
`
transform: rotate(90deg);
`
const
wrapperStyles
=
css
`
display: flex;
width: 8px;
height: 100%;
align-items: center;
justify-content: center;
border: 1px solid rgba(0, 0, 0, 0.1);
background:
${
color
(
"
draculaBackgroundDarker
"
)}
;
border: 1px solid rgba(255, 255, 255, 0.03);
border-top: none;
border-bottom: none;
color:
${
color
(
"
gray1
"
)}
;
&:hover {
background:
${
color
(
"
draculaSelection
"
)}
;
c
ursor: ew-resize
;
c
olor:
${
color
(
"
draculaForeground
"
)}
;
}
`
const
Ghost
=
styled
(
Wrapper
)
`
const
HorizontalWrapper
=
styled
.
div
`
${
wrapperStyles
}
;
width: 1rem;
height: 100%;
border-top: none;
border-bottom: none;
cursor: ew-resize;
`
const
VerticalWrapper
=
styled
.
div
`
${
wrapperStyles
}
;
width: 100%;
height: 1rem;
border-left: none;
border-right: none;
cursor: row-resize;
`
const
ghostStyles
=
css
`
position: absolute;
width: 8px;
top: 0;
bottom: 0;
z-index: 20;
background:
${
color
(
"
draculaPurple
"
)}
;
&:hover {
background:
${
color
(
"
draculaPurple
"
)}
;
cursor: ew-resize;
}
`
type
Position
=
Readonly
<
{
x
:
number
}
>
const
HorizontalGhost
=
styled
.
div
`
${
ghostStyles
}
;
width: 1rem;
top: 0;
bottom: 0;
`
const
VerticalGhost
=
styled
.
div
`
${
ghostStyles
}
;
height: 1rem;
left: 0;
right: 0;
`
export
const
Splitter
=
({
max
,
min
,
onChange
}:
Props
)
=>
{
export
const
Splitter
=
({
direction
,
max
,
min
,
onChange
}:
Props
)
=>
{
const
[
offset
,
setOffset
]
=
useState
(
0
)
const
[
originalPosition
,
setOriginalPosition
]
=
useState
(
0
)
const
[
ghostPosition
,
setGhostPosition
]
=
useState
(
0
)
const
[
pressed
,
setPressed
]
=
useState
(
false
)
const
[
left
,
setLeft
]
=
useState
<
number
>
(
0
)
const
[
xOffset
,
setXOffset
]
=
useState
<
number
>
(
0
)
const
splitter
=
useRef
<
HTMLDivElement
|
null
>
(
null
)
const
[
position
,
setPosition
]
=
useState
<
Position
>
({
x
:
0
})
const
handleMouseMove
=
useCallback
(
(
event
:
MouseEvent
)
=>
{
(
event
:
TouchEvent
|
MouseEvent
)
=>
{
event
.
stopPropagation
()
event
.
preventDefault
()
const
clientPosition
=
direction
===
"
horizontal
"
?
"
clientX
"
:
"
clientY
"
const
side
=
direction
===
"
horizontal
"
?
"
outerWidth
"
:
"
outerHeight
"
let
position
=
0
if
(
event
instanceof
TouchEvent
)
{
position
=
event
.
touches
[
0
][
clientPosition
]
}
if
(
event
instanceof
MouseEvent
)
{
position
=
event
[
clientPosition
]
}
if
(
(
min
&&
max
&&
event
.
clientX
>
min
&&
event
.
clientX
<
window
.
outerWidth
-
max
)
||
(
!
min
&&
max
&&
event
.
clientX
<
window
.
outerWidth
-
max
)
||
(
!
max
&&
min
&&
event
.
clientX
>
min
)
||
(
min
&&
max
&&
position
>
min
&&
position
<
window
[
side
]
-
max
)
||
(
!
min
&&
max
&&
position
<
window
[
side
]
-
max
)
||
(
!
max
&&
min
&&
position
>
min
)
||
(
!
min
&&
!
max
)
)
{
setPosition
({
x
:
event
.
clientX
,
})
setGhostPosition
(
position
)
}
},
[
max
,
min
],
[
direction
,
max
,
min
],
)
const
handleMouseUp
=
useCallback
(()
=>
{
document
.
removeEventListener
(
"
mouseup
"
,
handleMouseUp
)
document
.
removeEventListener
(
"
mousemove
"
,
handleMouseMove
)
document
.
removeEventListener
(
"
touchend
"
,
handleMouseUp
)
document
.
removeEventListener
(
"
touchmove
"
,
handleMouseMove
)
setPressed
(
false
)
},
[
handleMouseMove
])
const
handleMouseDown
=
useCallback
(
(
event
:
React
MouseEvent
<
HTMLDivElement
>
)
=>
{
(
event
:
React
TouchEvent
|
ReactMouseEvent
)
=>
{
if
(
splitter
.
current
&&
splitter
.
current
.
parentElement
)
{
const
{
x
}
=
splitter
.
current
.
parentElement
.
getBoundingClientRect
()
setLeft
(
event
.
clientX
-
x
)
setXOffset
(
x
)
const
clientPosition
=
direction
===
"
horizontal
"
?
"
clientX
"
:
"
clientY
"
const
coordinate
=
direction
===
"
horizontal
"
?
"
x
"
:
"
y
"
const
offset
=
splitter
.
current
.
parentElement
.
getBoundingClientRect
()[
coordinate
]
let
position
=
0
if
(
event
.
nativeEvent
instanceof
TouchEvent
)
{
position
=
event
.
nativeEvent
.
touches
[
0
][
clientPosition
]
}
if
(
event
.
nativeEvent
instanceof
MouseEvent
)
{
position
=
event
.
nativeEvent
[
clientPosition
]
}
setOriginalPosition
(
position
)
setOffset
(
offset
)
setPressed
(
true
)
document
.
addEventListener
(
"
mouseup
"
,
handleMouseUp
)
document
.
addEventListener
(
"
mousemove
"
,
handleMouseMove
)
document
.
addEventListener
(
"
mousemove
"
,
handleMouseMove
,
{
passive
:
true
,
})
document
.
addEventListener
(
"
touchend
"
,
handleMouseUp
)
document
.
addEventListener
(
"
touchmove
"
,
handleMouseMove
,
{
passive
:
true
,
})
}
},
[
handleMouseMove
,
handleMouseUp
],
[
direction
,
handleMouseMove
,
handleMouseUp
],
)
useEffect
(()
=>
{
if
(
!
pressed
&&
position
.
x
)
{
onChange
(
position
.
x
-
left
-
xOffset
)
set
Left
(
0
)
set
Position
({
x
:
0
}
)
if
(
!
pressed
&&
ghostPosition
)
{
onChange
(
ghostPosition
-
originalPosition
)
set
OriginalPosition
(
0
)
set
GhostPosition
(
0
)
}
},
[
onChange
,
position
,
pressed
,
left
,
xOffset
])
},
[
onChange
,
ghostPosition
,
pressed
,
originalPosition
])
if
(
direction
===
"
horizontal
"
)
{
return
(
<>
<
HorizontalWrapper
onMouseDown
=
{
handleMouseDown
}
onTouchStart
=
{
handleMouseDown
}
ref
=
{
splitter
}
>
<
HorizontalDragIcon
size
=
"16px"
/>
</
HorizontalWrapper
>
{
ghostPosition
>
0
&&
(
<>
<
HorizontalGhost
style
=
{
{
left
:
`
${
ghostPosition
-
offset
}
px`
,
}
}
/>
<
PreventUserSelectionHorizontal
/>
</>
)
}
</>
)
}
return
(
<>
<
Wrapper
onMouseDown
=
{
handleMouseDown
}
ref
=
{
splitter
}
>
<
DragIcon
size
=
"12px"
/>
</
Wrapper
>
{
position
.
x
>
0
&&
(
<
Ghost
style
=
{
{
left
:
`
${
position
.
x
-
xOffset
}
px`
,
}
}
/>
<
VerticalWrapper
onMouseDown
=
{
handleMouseDown
}
onTouchStart
=
{
handleMouseDown
}
ref
=
{
splitter
}
>
<
VerticalDragIcon
size
=
"16px"
/>
</
VerticalWrapper
>
{
ghostPosition
>
0
&&
(
<>
<
VerticalGhost
style
=
{
{
top
:
`
${
ghostPosition
-
offset
}
px`
,
}
}
/>
<
PreventUserSelectionVertical
/>
</>
)
}
</>
)
...
...
ui/src/components/ToggleButton/index.tsx
浏览文件 @
8fa21f67
import
{
darken
}
from
"
polished
"
import
React
,
{
forwardRef
,
Ref
}
from
"
react
"
import
styled
,
{
css
}
from
"
styled-components
"
import
type
{
Color
}
from
"
types
"
...
...
@@ -8,17 +7,21 @@ import { color } from "utils"
import
{
ButtonProps
,
getButtonSize
,
PrimaryButton
}
from
"
../Button
"
import
{
bezierTransition
}
from
"
../Transition
"
const
defaultProps
:
ButtonProps
&
Readonly
<
{
direction
:
"
top
"
|
"
right
"
|
"
bottom
"
|
"
left
"
selected
:
boolean
}
>
=
{
type
Direction
=
"
top
"
|
"
right
"
|
"
bottom
"
|
"
left
"
const
direction
:
Direction
=
"
bottom
"
const
defaultProps
=
{
...
PrimaryButton
.
defaultProps
,
direction
:
"
bottom
"
,
direction
,
selected
:
false
,
}
type
Props
=
typeof
defaultProps
type
Props
=
Readonly
<
{
direction
?:
Direction
selected
?:
boolean
}
>
&
ButtonProps
type
ThemeShape
=
{
background
:
Color
...
...
@@ -30,13 +33,20 @@ const baseCss = css<Props>`
height:
${
getButtonSize
}
;
padding: 0 1rem;
align-items: center;
justify-content: center;
background: transparent;
border: none;
font-weight: 400;
outline: 0;
line-height: 1.15;
opacity:
${({
selected
})
=>
(
selected
?
"
1
"
:
"
0.5
"
)}
;
border: none;
${({
direction
})
=>
`border-
${
direction
||
defaultProps
.
direction
}
: 2px solid transparent;`
}
;
${
bezierTransition
}
;
svg + span {
margin-left: 1rem;
}
`
const
getTheme
=
(
normal
:
ThemeShape
,
hover
:
ThemeShape
)
=>
...
...
@@ -44,7 +54,10 @@ const getTheme = (normal: ThemeShape, hover: ThemeShape) =>
background:
${
color
(
normal
.
background
)}
;
color:
${
color
(
normal
.
color
)}
;
${({
direction
,
selected
,
theme
})
=>
selected
&&
`border-
${
direction
}
: 2px solid
${
theme
.
color
.
draculaPink
}
;`
}
;
selected
&&
`border-
${
direction
||
defaultProps
.
direction
}
-color:
${
theme
.
color
.
draculaPink
}
;`
}
;
&:focus {
box-shadow: inset 0 0 0 1px
${
color
(
"
draculaForeground
"
)}
;
...
...
@@ -61,7 +74,7 @@ const getTheme = (normal: ThemeShape, hover: ThemeShape) =>
}
`
const
Primary
=
styled
.
button
<
Props
>
`
export
const
PrimaryToggleButton
=
styled
.
button
<
Props
>
`
${
baseCss
}
;
${
getTheme
(
{
...
...
@@ -75,11 +88,4 @@ const Primary = styled.button<Props>`
)}
;
`
const
PrimaryToggleButtonWithRef
=
(
props
:
Props
,
ref
:
Ref
<
HTMLButtonElement
>
,
)
=>
<
Primary
{
...
props
}
ref
=
{
ref
}
/>
export
const
PrimaryToggleButton
=
forwardRef
(
PrimaryToggleButtonWithRef
)
PrimaryToggleButton
.
defaultProps
=
defaultProps
ui/src/index.hbs
浏览文件 @
8fa21f67
...
...
@@ -18,7 +18,7 @@
<div
id=
"root"
></div>
<div
id=
"page-wrapper"
class=
"gray-bg"
>
{{>
partials
/
console
}}
<div
class=
"js-sql-panel"
id=
"console"
></div>
{{>
partials
/
import
}}
<div
id=
"footer"
></div>
</div>
...
...
ui/src/index.tsx
浏览文件 @
8fa21f67
...
...
@@ -8,6 +8,7 @@ import { applyMiddleware, compose, createStore } from "redux"
import
{
createEpicMiddleware
}
from
"
redux-observable
"
import
{
ThemeProvider
}
from
"
styled-components
"
import
{
MediaQueryProvider
}
from
"
components
"
import
{
actions
,
rootEpic
,
rootReducer
}
from
"
store
"
import
{
StoreAction
,
StoreShape
}
from
"
types
"
...
...
@@ -28,7 +29,9 @@ store.dispatch(actions.console.bootstrap())
ReactDOM
.
render
(
<
Provider
store
=
{
store
}
>
<
ThemeProvider
theme
=
{
theme
}
>
<
Layout
/>
<
MediaQueryProvider
>
<
Layout
/>
</
MediaQueryProvider
>
</
ThemeProvider
>
</
Provider
>,
document
.
getElementById
(
"
root
"
),
...
...
ui/src/js/console/console-controller.js
浏览文件 @
8fa21f67
import
Clipboard
from
"
clipboard
"
import
$
from
"
jquery
"
import
*
as
qdb
from
"
./globals
"
const
divSqlPanel
=
$
(
"
.js-sql-panel
"
)
const
divExportUrl
=
$
(
"
.js-export-url
"
)
const
win
=
$
(
window
)
const
grid
=
$
(
"
#grid
"
)
const
quickVis
=
$
(
"
#quick-vis
"
)
const
toggleChartBtn
=
$
(
"
#js-toggle-chart
"
)
const
toggleGridBtn
=
$
(
"
#js-toggle-grid
"
)
let
topHeight
=
350
function
resize
()
{
$
(
"
#editor
"
).
css
(
"
flex-basis
"
,
topHeight
)
}
function
loadSplitterPosition
()
{
if
(
typeof
Storage
!==
"
undefined
"
)
{
const
n
=
localStorage
.
getItem
(
"
splitter.position
"
)
if
(
n
)
{
topHeight
=
parseInt
(
n
)
if
(
!
topHeight
)
{
topHeight
=
350
}
}
}
}
function
saveSplitterPosition
()
{
if
(
typeof
Storage
!==
"
undefined
"
)
{
localStorage
.
setItem
(
"
splitter.position
"
,
topHeight
)
}
}
function
toggleVisibility
(
x
,
name
)
{
if
(
name
===
"
console
"
)
{
visible
=
true
divSqlPanel
.
show
()
}
else
{
visible
=
false
divSqlPanel
.
hide
()
}
}
function
toggleChart
()
{
toggleChartBtn
.
addClass
(
"
active
"
)
toggleGridBtn
.
removeClass
(
"
active
"
)
grid
.
css
(
"
display
"
,
"
none
"
)
quickVis
.
css
(
"
display
"
,
"
flex
"
)
}
function
toggleGrid
()
{
toggleChartBtn
.
removeClass
(
"
active
"
)
toggleGridBtn
.
addClass
(
"
active
"
)
grid
.
css
(
"
display
"
,
"
flex
"
)
quickVis
.
css
(
"
display
"
,
"
none
"
)
}
export
function
setupConsoleController
(
bus
)
{
win
.
bind
(
"
resize
"
,
resize
)
bus
.
on
(
qdb
.
MSG_QUERY_DATASET
,
function
(
e
,
m
)
{
divExportUrl
.
val
(
qdb
.
toExportUrl
(
m
.
query
))
})
divExportUrl
.
click
(
function
()
{
this
.
select
()
})
/* eslint-disable no-new */
new
Clipboard
(
"
.js-export-copy-url
"
)
$
(
"
.js-query-refresh
"
).
click
(
function
()
{
$
(
"
.js-query-refresh .fa
"
).
addClass
(
"
fa-spin
"
)
bus
.
trigger
(
"
grid.refresh
"
)
})
// named splitter
bus
.
on
(
"
splitter.console.resize
"
,
function
(
x
,
e
)
{
topHeight
+=
e
win
.
trigger
(
"
resize
"
)
bus
.
trigger
(
"
preferences.save
"
)
})
bus
.
on
(
"
preferences.save
"
,
saveSplitterPosition
)
bus
.
on
(
"
preferences.load
"
,
loadSplitterPosition
)
bus
.
on
(
qdb
.
MSG_ACTIVE_PANEL
,
toggleVisibility
)
const
grid
=
$
(
"
#grid
"
)
const
quickVis
=
$
(
"
#quick-vis
"
)
grid
.
grid
(
bus
)
quickVis
.
quickVis
(
bus
)
$
(
"
#console-splitter
"
).
splitter
(
bus
,
"
console
"
,
200
,
200
)
// wire query publish
toggleChartBtn
.
click
(
toggleChart
)
toggleGridBtn
.
click
(
toggleGrid
)
bus
.
on
(
qdb
.
MSG_QUERY_DATASET
,
toggleGrid
)
}
ui/src/js/console/import-controller.js
浏览文件 @
8fa21f67
...
...
@@ -2,59 +2,39 @@ import $ from "jquery"
import
*
as
qdb
from
"
./globals
"
const
divImportPanel
=
$
(
"
.js-import-panel
"
)
const
importTopPanel
=
$
(
"
#import-top
"
)
const
importDetail
=
$
(
"
#import-detail
"
)
const
importMenu
=
$
(
"
#import-menu
"
)[
0
]
const
footer
=
$
(
"
#footer
"
)[
0
]
const
canvasPanel
=
importTopPanel
.
find
(
"
.ud-canvas
"
)
const
w
=
$
(
window
)
let
visible
=
false
let
upperHalfHeight
=
450
let
upperHalfHeight
=
515
function
resize
()
{
if
(
visible
)
{
const
h
=
w
[
0
].
innerHeight
const
footerHeight
=
footer
.
offsetHeight
qdb
.
setHeight
(
importTopPanel
,
upperHalfHeight
)
qdb
.
setHeight
(
importDetail
,
h
-
footerHeight
-
upperHalfHeight
-
importMenu
.
offsetHeight
-
10
,
)
let
r1
=
importTopPanel
[
0
].
getBoundingClientRect
()
let
r2
=
canvasPanel
[
0
].
getBoundingClientRect
()
// qdb.setHeight(importTopPanel, upperHalfHeight);
qdb
.
setHeight
(
canvasPanel
,
upperHalfHeight
-
(
r2
.
top
-
r1
.
top
)
-
10
)
}
}
function
toggleVisibility
(
x
,
name
)
{
if
(
name
===
"
import
"
)
{
visible
=
true
divImportPanel
.
show
()
w
.
trigger
(
"
resize
"
)
}
else
{
visible
=
false
divImportPanel
.
hide
()
}
const
footer
=
$
(
"
.footer
"
)
const
importTopPanel
=
$
(
"
#import-top
"
)
const
canvasPanel
=
importTopPanel
.
find
(
"
.ud-canvas
"
)
const
importDetail
=
$
(
"
#import-detail
"
)
const
importMenu
=
$
(
"
#import-menu
"
)[
0
]
const
h
=
$
(
window
)[
0
].
innerHeight
const
footerHeight
=
footer
.
offsetHeight
qdb
.
setHeight
(
importTopPanel
,
upperHalfHeight
)
qdb
.
setHeight
(
importDetail
,
h
-
footerHeight
-
upperHalfHeight
-
importMenu
.
offsetHeight
-
50
,
)
let
r1
=
importTopPanel
[
0
].
getBoundingClientRect
()
let
r2
=
canvasPanel
[
0
].
getBoundingClientRect
()
}
function
splitterResize
(
x
,
p
)
{
upperHalfHeight
+=
p
w
.
trigger
(
"
resize
"
)
$
(
window
)
.
trigger
(
"
resize
"
)
}
export
function
setupImportController
(
bus
)
{
w
.
bind
(
"
resize
"
,
resize
)
$
(
window
)
.
bind
(
"
resize
"
,
resize
)
$
(
"
#dragTarget
"
).
dropbox
(
bus
)
$
(
"
#import-file-list
"
).
importManager
(
bus
)
$
(
"
#import-detail
"
).
importEditor
(
bus
)
$
(
"
#import-splitter
"
).
splitter
(
bus
,
"
import
"
,
4
70
,
30
0
)
$
(
"
#import-splitter
"
).
splitter
(
bus
,
"
import
"
,
4
20
,
25
0
)
bus
.
on
(
"
splitter.import.resize
"
,
splitterResize
)
bus
.
on
(
qdb
.
MSG_ACTIVE_PANEL
,
toggleVisibility
)
}
ui/src/js/console/index.js
浏览文件 @
8fa21f67
...
...
@@ -25,18 +25,13 @@ let messageBus = $({})
window
.
bus
=
messageBus
$
(
document
).
ready
(
function
()
{
setupConsoleController
(
messageBus
)
setupImportController
(
messageBus
)
function
exportClick
(
e
)
{
e
.
preventDefault
()
messageBus
.
trigger
(
"
grid.publish.query
"
)
}
$
(
"
.js-query-export
"
).
click
(
exportClick
)
messageBus
.
trigger
(
"
preferences.load
"
)
const
win
=
$
(
window
)
win
.
trigger
(
"
resize
"
)
})
messageBus
.
on
(
"
react.ready
"
,
()
=>
{
setupConsoleController
(
messageBus
)
setupImportController
(
messageBus
)
})
ui/src/partials/console.hbs
已删除
100644 → 0
浏览文件 @
78dd24ca
<div
class=
"js-sql-panel"
>
<div
class=
"console-wrapper"
id=
"editor"
></div>
<div
id=
"console-splitter"
class=
"splitter"
>
<svg
viewBox=
"0 0 24 24"
height=
"12px"
width=
"12px"
aria-hidden=
"true"
focusable=
"false"
fill=
"currentColor"
xmlns=
"http://www.w3.org/2000/svg"
class=
"sc-AxjAm gXIMwt Splitter__DragIcon-kWuanA jGdWIe"
><path
fill=
"none"
d=
"M0 0h24v24H0V0z"
></path><path
d=
"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z"
></path></svg>
</div>
<div
class=
"result-wrapper"
>
<div
class=
"menu-bar"
>
<button
id=
"js-toggle-grid"
class=
"active button-toggle"
>
<i
class=
"fa fa-bars"
></i>
<span>
Grid
</span>
</button>
<button
id=
"js-toggle-chart"
class=
"button-toggle"
>
<i
class=
"fa fa-chart-pie"
></i>
<span>
Chart
</span>
</button>
<div
class=
"flex-separator"
></div>
<button
class=
"js-query-refresh button-primary"
>
<i
class=
"fa fa-sync"
></i>
</button>
<button
class=
"js-query-export button-primary"
>
<i
class=
"fa fa-download"
></i>
<span>
CSV
</span>
</button>
</div>
<div
id=
"grid"
>
<div
class=
"qg-header-row"
></div>
<div
class=
"qg-viewport"
>
<div
class=
"qg-canvas"
></div>
</div>
</div>
<div
id=
"quick-vis"
>
<div
class=
"quick-vis-controls"
>
<form
class=
"v-fit"
role=
"form"
>
<div
class=
"form-group"
>
<label>
Chart type
</label>
<select
id=
"_qvis_frm_chart_type"
>
<option>
bar
</option>
<option>
line
</option>
<option>
area
</option>
</select>
</div>
<div
class=
"form-group"
>
<label>
Labels
</label>
<select
id=
"_qvis_frm_axis_x"
></select>
</div>
<div
class=
"form-group"
>
<label>
Series
</label>
<select
id=
"_qvis_frm_axis_y"
multiple
></select>
</div>
<button
class=
"button-primary js-chart-draw"
id=
"_qvis_frm_draw"
>
<i
class=
"fa fa-play"
></i>
<span>
Draw
</span>
</button>
</form>
</div>
<div
class=
"quick-vis-canvas"
></div>
</div>
</div>
</div>
ui/src/scenes/Editor/Ace/index.tsx
浏览文件 @
8fa21f67
...
...
@@ -96,6 +96,7 @@ const Ace = () => {
.
then
((
result
)
=>
{
setRequest
(
undefined
)
dispatch
(
actions
.
query
.
stopRunning
())
dispatch
(
actions
.
query
.
setResult
(
result
))
if
(
result
.
type
===
QuestDB
.
Type
.
DDL
)
{
dispatch
(
...
...
ui/src/scenes/Editor/Menu/index.tsx
浏览文件 @
8fa21f67
...
...
@@ -2,9 +2,9 @@ import docsearch from "docsearch.js"
import
React
,
{
useCallback
,
useEffect
,
useState
}
from
"
react
"
import
{
useDispatch
,
useSelector
}
from
"
react-redux
"
import
styled
from
"
styled-components
"
import
{
ControllerPlay
}
from
"
@styled-icons/entypo/Controller
Play
"
import
{
ControllerStop
}
from
"
@styled-icons/entypo/Controller
Stop
"
import
{
Plus
}
from
"
@styled-icons/entypo/Plus
"
import
{
Play
}
from
"
@styled-icons/remix-line/
Play
"
import
{
Stop
}
from
"
@styled-icons/remix-line/
Stop
"
import
{
Add
}
from
"
@styled-icons/remix-line/Add
"
import
{
ErrorButton
,
...
...
@@ -85,14 +85,14 @@ const Menu = () => {
<
Wrapper
>
{
running
&&
(
<
ErrorButton
onClick
=
{
handleClick
}
>
<
Controller
Stop
size
=
"18px"
/>
<
Stop
size
=
"18px"
/>
<
span
>
Cancel
</
span
>
</
ErrorButton
>
)
}
{
!
running
&&
(
<
SuccessButton
onClick
=
{
handleClick
}
>
<
Controller
Play
size
=
"18px"
/>
<
Play
size
=
"18px"
/>
<
span
>
Run
</
span
>
</
SuccessButton
>
)
}
...
...
@@ -104,7 +104,7 @@ const Menu = () => {
onToggle
=
{
handleToggle
}
trigger
=
{
<
QueryPickerButton
onClick
=
{
handleClick
}
>
<
Plus
size
=
"18px"
/>
<
Add
size
=
"18px"
/>
<
span
>
Saved queries
</
span
>
</
QueryPickerButton
>
}
...
...
ui/src/scenes/Editor/QueryPicker/index.tsx
浏览文件 @
8fa21f67
...
...
@@ -22,7 +22,7 @@ const Wrapper = styled.div`
padding: 0.6rem 0;
flex-direction: column;
background:
${
color
(
"
draculaBackgroundDarker
"
)}
;
box-shadow:
rgb(0, 0, 0)
0px 5px 8px;
box-shadow:
${
color
(
"
black
"
)}
0px 5px 8px;
border: 1px solid
${
color
(
"
black
"
)}
;
border-radius: 4px;
`
...
...
ui/src/scenes/Editor/QueryResult/index.tsx
浏览文件 @
8fa21f67
...
...
@@ -81,7 +81,7 @@ const QueryResult = ({ compiler, count, execute, fetch, rowCount }: Props) => {
<
Wrapper
_height
=
{
95
}
duration
=
{
TransitionDuration
.
FAST
}
>
<
div
>
<
Text
color
=
"draculaForeground"
>
{
rowCount
}
row
s
in
{
rowCount
}
row
{
rowCount
>
1
?
"
s
"
:
""
}
in
{
formatTiming
(
fetch
)
}
</
Text
>
(
...
...
ui/src/scenes/Footer/index.tsx
浏览文件 @
8fa21f67
...
...
@@ -29,7 +29,7 @@ const Footer = () => (
rel
=
"noreferrer"
target
=
"_blank"
>
<
Github
size
=
"
22
px"
/>
<
Github
size
=
"
18
px"
/>
</
Link
>
</
Icons
>
</>
...
...
ui/src/scenes/Layout/index.tsx
浏览文件 @
8fa21f67
import
React
,
{
useCallback
,
useState
}
from
"
react
"
import
React
,
{
useCallback
,
use
Effect
,
useRef
,
use
State
}
from
"
react
"
import
{
createPortal
}
from
"
react-dom
"
import
styled
from
"
styled-components
"
import
{
Splitter
}
from
"
components
"
import
{
BusEvent
}
from
"
utils
"
import
Editor
from
"
../Editor
"
import
Footer
from
"
../Footer
"
import
Notifications
from
"
../Notifications
"
import
Result
from
"
../Result
"
import
Schema
from
"
../Schema
"
import
Sidebar
from
"
../Sidebar
"
const
Top
=
styled
.
div
<
{
basis
?:
number
}
>
`
position: relative;
display: flex;
flex-grow: 0;
flex-shrink: 1;
${({
basis
})
=>
basis
&&
`flex-basis:
${
basis
}
px`
}
;
overflow: hidden;
`
const
Layout
=
()
=>
{
const
editorNode
=
document
.
getElementById
(
"
editor
"
)
const
consoleNode
=
document
.
getElementById
(
"
console
"
)
const
footerNode
=
document
.
getElementById
(
"
footer
"
)
const
notificationsNode
=
document
.
getElementById
(
"
notifications
"
)
const
[
schemaWidthOffset
,
setSchemaWidthOffset
]
=
useState
<
number
>
()
const
handleSplitterChange
=
useCallback
((
offset
)
=>
{
const
[
topHeightOffset
,
setTopHeightOffset
]
=
useState
<
number
>
()
const
topElement
=
useRef
<
HTMLDivElement
|
null
>
(
null
)
const
handleSchemaSplitterChange
=
useCallback
((
offset
)
=>
{
setSchemaWidthOffset
(
offset
)
},
[])
const
handleResultSplitterChange
=
useCallback
((
change
:
number
)
=>
{
if
(
topElement
.
current
)
{
const
offset
=
topElement
.
current
.
getBoundingClientRect
().
height
+
change
localStorage
.
setItem
(
"
splitter.position
"
,
`
${
offset
}
`
)
setTopHeightOffset
(
offset
)
setTimeout
(()
=>
{
window
.
bus
.
trigger
(
BusEvent
.
MSG_ACTIVE_PANEL
)
},
0
)
}
},
[])
useEffect
(()
=>
{
const
size
=
parseInt
(
localStorage
.
getItem
(
"
splitter.position
"
)
||
"
0
"
,
10
)
if
(
size
)
{
setTopHeightOffset
(
size
)
}
else
{
setTopHeightOffset
(
350
)
}
},
[])
useEffect
(()
=>
{
if
(
topElement
.
current
)
{
window
.
bus
.
trigger
(
BusEvent
.
REACT_READY
)
}
},
[
topElement
])
return
(
<>
<
Sidebar
/>
{
editor
Node
&&
{
console
Node
&&
createPortal
(
<>
<
Schema
widthOffset
=
{
schemaWidthOffset
}
/>
<
Splitter
max
=
{
300
}
min
=
{
200
}
onChange
=
{
handleSplitterChange
}
/>
<
Editor
/>
<
Top
basis
=
{
topHeightOffset
}
ref
=
{
topElement
}
>
<
Schema
widthOffset
=
{
schemaWidthOffset
}
/>
<
Splitter
direction
=
"horizontal"
max
=
{
300
}
min
=
{
200
}
onChange
=
{
handleSchemaSplitterChange
}
/>
<
Editor
/>
</
Top
>
<
Splitter
direction
=
"vertical"
max
=
{
300
}
min
=
{
200
}
onChange
=
{
handleResultSplitterChange
}
/>
<
Result
/>
</>,
editor
Node
,
console
Node
,
)
}
{
footerNode
&&
createPortal
(<
Footer
/>,
footerNode
)
}
{
notificationsNode
&&
createPortal
(<
Notifications
/>,
notificationsNode
)
}
...
...
ui/src/scenes/Notifications/Notification/index.tsx
浏览文件 @
8fa21f67
...
...
@@ -138,12 +138,12 @@ const Notification = ({ createdAt, line1, title, type, ...rest }: Props) => {
/>
)
}
<
CloseIcon
onClick
=
{
handleCloseClick
}
size
=
"1
6
px"
/>
<
CloseIcon
onClick
=
{
handleCloseClick
}
size
=
"1
8
px"
/>
{
pinned
?
(
<
Unpin
onClick
=
{
handlePinClick
}
size
=
"1
4
px"
/>
<
Unpin
onClick
=
{
handlePinClick
}
size
=
"1
6
px"
/>
)
:
(
<
Pin
onClick
=
{
handlePinClick
}
size
=
"1
4
px"
/>
<
Pin
onClick
=
{
handlePinClick
}
size
=
"1
6
px"
/>
)
}
</
Wrapper
>
</
CSSTransition
>
...
...
ui/src/scenes/Notifications/index.tsx
浏览文件 @
8fa21f67
...
...
@@ -12,7 +12,7 @@ const NotificationsStyles = createGlobalStyle`
.notifications{
position: fixed;
top: 4rem;
right: 1
.5
rem;
right: 1rem;
display: flex;
flex-direction: column;
z-index: 10;
...
...
ui/src/scenes/Result/index.tsx
0 → 100644
浏览文件 @
8fa21f67
import
React
,
{
useCallback
,
useEffect
,
useState
}
from
"
react
"
import
{
useSelector
}
from
"
react-redux
"
import
styled
from
"
styled-components
"
import
{
Download2
}
from
"
@styled-icons/remix-line/Download2
"
import
{
Grid
}
from
"
@styled-icons/remix-line/Grid
"
import
{
PieChart
}
from
"
@styled-icons/remix-line/PieChart
"
import
{
Refresh
}
from
"
@styled-icons/remix-line/Refresh
"
import
{
PaneContent
,
PaneWrapper
,
PaneMenu
,
PopperHover
,
PrimaryToggleButton
,
SecondaryButton
,
Text
,
Tooltip
,
}
from
"
components
"
import
{
selectors
}
from
"
store
"
import
{
BusEvent
,
color
}
from
"
utils
"
import
*
as
QuestDB
from
"
utils/questdb
"
const
Menu
=
styled
(
PaneMenu
)
`
justify-content: space-between;
`
const
Wrapper
=
styled
(
PaneWrapper
)
`
overflow: hidden;
`
const
Content
=
styled
(
PaneContent
)
`
color:
${
color
(
"
draculaForeground
"
)}
;
*::selection {
background:
${
color
(
"
draculaRed
"
)}
;
color:
${
color
(
"
draculaForeground
"
)}
;
}
`
const
ButtonWrapper
=
styled
.
div
`
display: flex;
align-items: center;
${
/* sc-selector */
SecondaryButton
}
:not(:last-child) {
margin-right: 1rem;
}
${
/* sc-selector */
Text
}
{
margin-right: 2rem;
}
`
const
ToggleButton
=
styled
(
PrimaryToggleButton
)
`
height: 4rem;
width: 10rem;
`
const
Result
=
()
=>
{
const
[
selected
,
setSelected
]
=
useState
<
"
chart
"
|
"
grid
"
>
(
"
grid
"
)
const
[
count
,
setCount
]
=
useState
<
number
|
undefined
>
()
const
handleToggle
=
useCallback
(()
=>
{
setSelected
(
selected
===
"
grid
"
?
"
chart
"
:
"
grid
"
)
},
[
selected
])
const
handleExportClick
=
useCallback
(()
=>
{
window
.
bus
.
trigger
(
"
grid.publish.query
"
)
},
[])
const
handleRefreshClick
=
useCallback
(()
=>
{
window
.
bus
.
trigger
(
"
grid.refresh
"
)
},
[])
const
result
=
useSelector
(
selectors
.
query
.
getResult
)
useEffect
(()
=>
{
if
(
result
?.
type
===
QuestDB
.
Type
.
DQL
)
{
setCount
(
result
.
count
)
}
},
[
result
])
useEffect
(()
=>
{
const
grid
=
document
.
getElementById
(
"
grid
"
)
const
chart
=
document
.
getElementById
(
"
quick-vis
"
)
if
(
!
grid
||
!
chart
)
{
return
}
if
(
selected
===
"
grid
"
)
{
grid
.
style
.
display
=
"
flex
"
chart
.
style
.
display
=
"
none
"
window
.
bus
.
trigger
(
BusEvent
.
MSG_ACTIVE_PANEL
)
}
else
{
grid
.
style
.
display
=
"
none
"
chart
.
style
.
display
=
"
flex
"
}
},
[
selected
])
return
(
<
Wrapper
>
<
Menu
>
<
ButtonWrapper
>
<
ToggleButton
onClick
=
{
handleToggle
}
selected
=
{
selected
===
"
grid
"
}
>
<
Grid
size
=
"18px"
/>
<
span
>
Grid
</
span
>
</
ToggleButton
>
<
ToggleButton
onClick
=
{
handleToggle
}
selected
=
{
selected
===
"
chart
"
}
>
<
PieChart
size
=
"18px"
/>
<
span
>
Chart
</
span
>
</
ToggleButton
>
</
ButtonWrapper
>
<
ButtonWrapper
>
{
count
&&
(
<
Text
color
=
"draculaForeground"
>
{
`
${
count
}
row
${
count
>
1
?
"
s
"
:
""
}
`
}
</
Text
>
)
}
<
PopperHover
delay
=
{
350
}
placement
=
"bottom"
trigger
=
{
<
SecondaryButton
onClick
=
{
handleRefreshClick
}
>
<
Refresh
size
=
"18px"
/>
</
SecondaryButton
>
}
>
<
Tooltip
>
Refresh
</
Tooltip
>
</
PopperHover
>
<
PopperHover
delay
=
{
350
}
placement
=
"bottom"
trigger
=
{
<
SecondaryButton
onClick
=
{
handleExportClick
}
>
<
Download2
size
=
"18px"
/>
<
span
>
CSV
</
span
>
</
SecondaryButton
>
}
>
<
Tooltip
>
Download result as a CSV file
</
Tooltip
>
</
PopperHover
>
</
ButtonWrapper
>
</
Menu
>
<
Content
>
<
div
id
=
"grid"
>
<
div
className
=
"qg-header-row"
/>
<
div
className
=
"qg-viewport"
>
<
div
className
=
"qg-canvas"
/>
</
div
>
</
div
>
<
div
id
=
"quick-vis"
>
<
div
className
=
"quick-vis-controls"
>
<
form
className
=
"v-fit"
role
=
"form"
>
<
div
className
=
"form-group"
>
<
label
>
Chart type
</
label
>
<
select
id
=
"_qvis_frm_chart_type"
>
<
option
>
bar
</
option
>
<
option
>
line
</
option
>
<
option
>
area
</
option
>
</
select
>
</
div
>
<
div
className
=
"form-group"
>
<
label
>
Labels
</
label
>
<
select
id
=
"_qvis_frm_axis_x"
/>
</
div
>
<
div
className
=
"form-group"
>
<
label
>
Series
</
label
>
<
select
id
=
"_qvis_frm_axis_y"
multiple
/>
</
div
>
<
button
className
=
"button-primary js-chart-draw"
id
=
"_qvis_frm_draw"
>
<
i
className
=
"fa fa-play"
/>
<
span
>
Draw
</
span
>
</
button
>
</
form
>
</
div
>
<
div
className
=
"quick-vis-canvas"
/>
</
div
>
</
Content
>
</
Wrapper
>
)
}
export
default
Result
ui/src/scenes/Schema/Row/index.tsx
浏览文件 @
8fa21f67
import
React
,
{
MouseEvent
,
ReactNode
,
useCallback
}
from
"
react
"
import
styled
from
"
styled-components
"
import
{
Code
}
from
"
@styled-icons/entypo/Code
"
import
{
Info
}
from
"
@styled-icons/entypo/Info
"
import
{
Code
SSlash
}
from
"
@styled-icons/remix-line/CodeSSlash
"
import
{
Info
rmation
}
from
"
@styled-icons/remix-line/Information
"
import
{
PopperHover
,
...
...
@@ -45,11 +45,15 @@ const Wrapper = styled.div<Pick<Props, "expanded">>`
padding-left: 1rem;
transition: background
${
TransitionDuration
.
REG
}
ms;
&:hover
${
/* sc-selector */
PlusButton
}
{
&:hover
${
/* sc-selector */
PlusButton
}
,
&:active
${
/* sc-selector */
PlusButton
}
{
opacity: 1;
}
&:hover {
&:hover,
&:active {
background:
${
color
(
"
draculaSelection
"
)}
;
}
...
...
@@ -67,7 +71,7 @@ const Spacer = styled.span`
flex: 1;
`
const
InfoIcon
=
styled
(
Info
)
`
const
InfoIcon
=
styled
(
Info
rmation
)
`
color:
${
color
(
"
draculaPurple
"
)}
;
`
...
...
@@ -115,7 +119,7 @@ const Row = ({
)
}
<
PlusButton
onClick
=
{
handleClick
}
size
=
"sm"
tooltip
=
{
tooltip
}
>
<
Code
size
=
"16px"
/>
<
Code
SSlash
size
=
"16px"
/>
<
span
>
Add
</
span
>
</
PlusButton
>
...
...
ui/src/scenes/Schema/index.tsx
浏览文件 @
8fa21f67
...
...
@@ -2,8 +2,8 @@ import React, { useCallback, useEffect, useRef, useState } from "react"
import
{
from
,
combineLatest
,
of
}
from
"
rxjs
"
import
{
delay
,
startWith
}
from
"
rxjs/operators
"
import
styled
,
{
css
}
from
"
styled-components
"
import
{
Database
}
from
"
@styled-icons/entypo/Database
"
import
{
Loader3
}
from
"
@styled-icons/remix-
fill
/Loader3
"
import
{
Database
2
}
from
"
@styled-icons/remix-line/Database2
"
import
{
Loader3
}
from
"
@styled-icons/remix-
line
/Loader3
"
import
{
Refresh
}
from
"
@styled-icons/remix-line/Refresh
"
import
{
...
...
@@ -43,15 +43,21 @@ const Menu = styled(PaneMenu)`
justify-content: space-between;
`
const
Header
=
styled
(
Text
)
`
display: flex;
align-items: center;
`
const
Content
=
styled
(
PaneContent
)
<
{
_loading
:
boolean
}
>
`
display: block;
font-family:
${({
theme
})
=>
theme
.
fontMonospace
}
;
overflow: auto;
${({
_loading
})
=>
_loading
&&
loadingStyles
}
;
`
const
DatabaseIcon
=
styled
(
Database
)
`
const
DatabaseIcon
=
styled
(
Database
2
)
`
margin-right: 1rem;
`
...
...
@@ -121,17 +127,17 @@ const Schema = ({ widthOffset }: Props) => {
return
(
<
Wrapper
basis
=
{
width
}
ref
=
{
element
}
>
<
Menu
>
<
Text
color
=
"draculaForeground"
>
<
Header
color
=
"draculaForeground"
>
<
DatabaseIcon
size
=
"18px"
/>
Tables
</
Text
>
</
Header
>
<
PopperHover
delay
=
{
350
}
placement
=
"bottom"
trigger
=
{
<
SecondaryButton
onClick
=
{
fetchTables
}
>
<
Refresh
size
=
"1
6
px"
/>
<
Refresh
size
=
"1
8
px"
/>
</
SecondaryButton
>
}
>
...
...
ui/src/scenes/Sidebar/index.tsx
浏览文件 @
8fa21f67
import
React
,
{
useCallback
,
useEffect
,
useState
}
from
"
react
"
import
{
useSelector
}
from
"
react-redux
"
import
styled
from
"
styled-components
"
import
{
Code
}
from
"
@styled-icons/entypo/Code
"
import
{
Upload
}
from
"
@styled-icons/entypo/Upload
"
import
{
Code
SSlash
}
from
"
@styled-icons/remix-line/CodeSSlash
"
import
{
Upload
2
}
from
"
@styled-icons/remix-line/Upload2
"
import
{
PopperHover
,
PrimaryToggleButton
,
Tooltip
}
from
"
components
"
import
{
selectors
}
from
"
store
"
...
...
@@ -10,14 +10,16 @@ import { color } from "utils"
const
Wrapper
=
styled
.
div
`
display: flex;
flex: 0 0 45px;
height: calc(100% - 4rem);
flex: 0 0 4.5rem;
flex-direction: column;
border-right: 1px solid rgba(0, 0, 0, 0.1);
`
const
Logo
=
styled
.
div
`
position: relative;
display: flex;
flex: 0 0 4
1px
;
flex: 0 0 4
rem
;
background:
${
color
(
"
black
"
)}
;
z-index: 1;
...
...
@@ -96,7 +98,7 @@ const Sidebar = () => {
onClick
=
{
handleConsoleClick
}
selected
=
{
selected
===
"
console
"
}
>
<
Code
size
=
"18px"
/>
<
Code
SSlash
size
=
"18px"
/>
</
Navigation
>
}
>
...
...
@@ -115,7 +117,7 @@ const Sidebar = () => {
onClick
=
{
handleImportClick
}
selected
=
{
selected
===
"
import
"
}
>
<
Upload
size
=
"16
px"
/>
<
Upload
2
size
=
"18
px"
/>
</
Navigation
>
</
DisabledNavigation
>
)
:
(
...
...
@@ -124,7 +126,7 @@ const Sidebar = () => {
onClick
=
{
handleImportClick
}
selected
=
{
selected
===
"
import
"
}
>
<
Upload
size
=
"16
px"
/>
<
Upload
2
size
=
"18
px"
/>
</
Navigation
>
)
}
...
...
ui/src/store/Query/actions.ts
浏览文件 @
8fa21f67
import
type
{
ReactNode
}
from
"
react
"
import
type
{
QueryRawResult
}
from
"
utils/questdb
"
import
{
NotificationShape
,
NotificationType
,
...
...
@@ -27,6 +29,11 @@ const removeNotification = (payload: Date): QueryAction => ({
type
:
QueryAT
.
REMOVE_NOTIFICATION
,
})
const
setResult
=
(
payload
:
QueryRawResult
):
QueryAction
=>
({
payload
,
type
:
QueryAT
.
SET_RESULT
,
})
const
stopRunning
=
():
QueryAction
=>
({
type
:
QueryAT
.
STOP_RUNNING
,
})
...
...
@@ -39,6 +46,7 @@ export default {
addNotification
,
cleanupNotifications
,
removeNotification
,
setResult
,
stopRunning
,
toggleRunning
,
}
ui/src/store/Query/reducers.ts
浏览文件 @
8fa21f67
...
...
@@ -46,6 +46,13 @@ const query = (state = initialState, action: QueryAction): QueryStateShape => {
}
}
case
QueryAT
.
SET_RESULT
:
{
return
{
...
state
,
result
:
action
.
payload
,
}
}
case
QueryAT
.
STOP_RUNNING
:
{
return
{
...
state
,
...
...
ui/src/store/Query/selectors.ts
浏览文件 @
8fa21f67
import
{
NotificationShape
,
StoreShape
}
from
"
types
"
import
type
{
QueryRawResult
}
from
"
utils/questdb
"
const
getNotifications
:
(
store
:
StoreShape
)
=>
NotificationShape
[]
=
(
store
)
=>
store
.
query
.
notifications
const
getResult
:
(
store
:
StoreShape
)
=>
undefined
|
QueryRawResult
=
(
store
)
=>
store
.
query
.
result
const
getRunning
:
(
store
:
StoreShape
)
=>
boolean
=
(
store
)
=>
store
.
query
.
running
export
default
{
getNotifications
,
getResult
,
getRunning
,
}
ui/src/store/Query/types.ts
浏览文件 @
8fa21f67
import
type
{
ReactNode
}
from
"
react
"
import
type
{
QueryRawResult
}
from
"
utils/questdb
"
export
enum
NotificationType
{
ERROR
=
"
error
"
,
INFO
=
"
info
"
,
...
...
@@ -16,6 +18,7 @@ export type NotificationShape = Readonly<{
export
type
QueryStateShape
=
Readonly
<
{
notifications
:
NotificationShape
[]
result
?:
QueryRawResult
running
:
boolean
}
>
...
...
@@ -23,6 +26,7 @@ export enum QueryAT {
ADD_NOTIFICATION
=
"
QUERY/ADD_NOTIFICATION
"
,
CLEANUP_NOTIFICATIONS
=
"
QUERY/CLEANUP_NOTIFICATIONS
"
,
REMOVE_NOTIFICATION
=
"
QUERY/REMOVE_NOTIFICATION
"
,
SET_RESULT
=
"
QUERY/SET_RESULT
"
,
STOP_RUNNING
=
"
QUERY/STOP_RUNNING
"
,
TOGGLE_RUNNING
=
"
QUERY/TOGGLE_RUNNING
"
,
}
...
...
@@ -41,6 +45,11 @@ type RemoveNotificationAction = Readonly<{
type
:
QueryAT
.
REMOVE_NOTIFICATION
}
>
type
SetResultAction
=
Readonly
<
{
payload
:
QueryRawResult
type
:
QueryAT
.
SET_RESULT
}
>
type
StopRunningAction
=
Readonly
<
{
type
:
QueryAT
.
STOP_RUNNING
}
>
...
...
@@ -53,5 +62,6 @@ export type QueryAction =
|
AddNotificationAction
|
CleanupNotificationsAction
|
RemoveNotificationAction
|
SetResultAction
|
StopRunningAction
|
ToggleRunningAction
ui/src/styles/_base.scss
浏览文件 @
8fa21f67
...
...
@@ -280,6 +280,7 @@ ol.unstyled {
background
:
#21222c
;
flex
:
0
0
40px
;
color
:
#f8f8f2
;
border-top
:
1px
solid
rgba
(
0
,
0
,
0
,
0
.1
);
}
.footer.fixed_full
{
...
...
@@ -328,7 +329,7 @@ body.body-small .footer.fixed {
.page-heading
{
border-top
:
0
;
padding
:
0
10px
0
10px
;
height
:
4
1px
;
height
:
4
rem
;
display
:
flex
;
align-items
:
center
;
padding
:
0
15px
;
...
...
ui/src/styles/_editor.scss
浏览文件 @
8fa21f67
...
...
@@ -259,17 +259,11 @@
flex
:
1
;
overflow
:
hidden
;
background
:
#282a36
;
color
:
#f8f8f2
;
::selection
{
background
:
#ff5555
;
color
:
#f9f9f2
;
}
}
.menu-bar
{
display
:
flex
;
height
:
4
1px
;
height
:
4
rem
;
padding
:
0
1rem
;
align-items
:
center
;
background
:
#21222c
;
...
...
ui/src/styles/_grid.scss
浏览文件 @
8fa21f67
...
...
@@ -32,6 +32,7 @@
.qg-header-row
{
overflow
:
hidden
;
height
:
33px
;
}
.qg-header-row
,
...
...
ui/src/styles/_import.scss
浏览文件 @
8fa21f67
...
...
@@ -46,8 +46,19 @@ $drag-drop-border: #c6c6c6;
.ud-canvas
{
position
:
relative
;
height
:
180px
;
overflow
:
auto
;
flex
:
1
;
}
#import-file-list
{
display
:
flex
;
flex-direction
:
column
;
flex
:
1
;
}
#import-top
{
display
:
flex
;
flex-direction
:
column
;
}
.ud-row
{
...
...
ui/src/styles/_navigation.scss
浏览文件 @
8fa21f67
...
...
@@ -348,7 +348,7 @@ body.mini-navbar .navbar-default .nav > li > .nav-second-level li a {
.logo-element
{
position
:
relative
;
display
:
flex
;
height
:
4
1px
;
height
:
4
rem
;
align-items
:
stretch
;
justify-content
:
stretch
;
font-size
:
18px
;
...
...
ui/src/styles/_splitter.scss
浏览文件 @
8fa21f67
...
...
@@ -22,34 +22,29 @@
*
******************************************************************************/
.splitter
,
.qs-ghost
{
cursor
:
row-resize
;
.splitter
,
.qs-ghost
{
cursor
:
row-resize
;
}
.splitter
{
display
:
flex
;
width
:
100%
;
height
:
8px
;
max-height
:
8px
;
flex
:
0
0
8px
;
align-items
:
center
;
justify-content
:
center
;
background
:
#21222c
;
border
:
1px
solid
rgba
(
255
,
255
,
255
,
0
.03
);
border-left
:
none
;
border-right
:
none
;
width
:
100%
;
flex
:
0
0
6px
;
height
:
6px
;
margin
:
3px
0
;
border
:
none
;
background
:
#585858
;
}
.splitter
sv
g
{
transform
:
rotate
(
90deg
)
;
.splitter
.qs-draggin
g
{
opacity
:
0
;
}
.splitter
:hover
{
background
:
#44475
a
;
background
:
#6a6a6
a
;
}
.qs-ghost
{
background
:
#bd93f9
;
z-index
:
20
;
background
:
#6a6a6a
;
opacity
:
0
.4
;
z-index
:
10
;
}
ui/src/utils/bus.ts
浏览文件 @
8fa21f67
...
...
@@ -12,4 +12,5 @@ export enum BusEvent {
MSG_QUERY_FIND_N_EXEC
=
"
query.build.execute
"
,
MSG_QUERY_OK
=
"
query.out.ok
"
,
MSG_QUERY_RUNNING
=
"
query.out.running
"
,
REACT_READY
=
"
react.ready
"
,
}
ui/src/utils/questdb.ts
浏览文件 @
8fa21f67
...
...
@@ -51,7 +51,12 @@ export type ErrorResult = RawErrorResult & {
type
:
Type
.
ERROR
}
export
type
Result
<
T
extends
Record
<
string
,
any
>>
=
export
type
QueryRawResult
=
|
(
Omit
<
RawDqlResult
,
"
ddl
"
>
&
{
type
:
Type
.
DQL
})
|
DdlResult
|
ErrorResult
export
type
QueryResult
<
T
extends
Record
<
string
,
any
>>
=
|
{
columns
:
ColumnDefinition
[]
count
:
number
...
...
@@ -122,7 +127,7 @@ export class Client {
this
.
_controllers
=
[]
}
async
query
<
T
>
(
query
:
string
,
options
?:
Options
):
Promise
<
Result
<
T
>>
{
async
query
<
T
>
(
query
:
string
,
options
?:
Options
):
Promise
<
Query
Result
<
T
>>
{
const
result
=
await
this
.
queryRaw
(
query
,
options
)
if
(
result
.
type
===
Type
.
DQL
)
{
...
...
@@ -151,12 +156,7 @@ export class Client {
return
result
}
async
queryRaw
(
query
:
string
,
options
?:
Options
,
):
Promise
<
(
Omit
<
RawDqlResult
,
"
ddl
"
>
&
{
type
:
Type
.
DQL
})
|
DdlResult
|
ErrorResult
>
{
async
queryRaw
(
query
:
string
,
options
?:
Options
):
Promise
<
QueryRawResult
>
{
const
controller
=
new
AbortController
()
const
payload
=
{
...
options
,
...
...
@@ -245,7 +245,7 @@ export class Client {
})
}
async
showTables
():
Promise
<
Result
<
Table
>>
{
async
showTables
():
Promise
<
Query
Result
<
Table
>>
{
const
response
=
await
this
.
query
<
Table
>
(
"
SHOW TABLES;
"
)
if
(
response
.
type
===
Type
.
DQL
)
{
...
...
@@ -268,7 +268,7 @@ export class Client {
return
response
}
async
showColumns
(
table
:
string
):
Promise
<
Result
<
Column
>>
{
async
showColumns
(
table
:
string
):
Promise
<
Query
Result
<
Column
>>
{
return
await
this
.
query
<
Column
>
(
`SHOW COLUMNS FROM '
${
table
}
';`
)
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录