Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions client/src/actions/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ export const UPDATE_QUERY_AND_ACTION = 'query/UPDATE_QUERY_AND_ACTION'
export const UPDATE_QUERY_VARS = 'query/UPDATE_QUERY_VARS'
export const UPDATE_READ_ONLY = 'query/UPDATE_READ_ONLY'
export const UPDATE_BEST_EFFORT = 'query/UPDATE_BEST_EFFORT'
export const ADD_TAB = 'query/ADD_TAB'
export const CLOSE_TAB = 'query/CLOSE_TAB'
export const SWITCH_TAB = 'query/SWITCH_TAB'
export const RENAME_TAB = 'query/RENAME_TAB'

export function updateQuery(query) {
return {
Expand Down Expand Up @@ -50,3 +54,23 @@ export const updateQueryVars = (newVars) => ({
type: UPDATE_QUERY_VARS,
newVars,
})

export const addTab = () => ({
type: ADD_TAB,
})

export const closeTab = (id) => ({
type: CLOSE_TAB,
id,
})

export const switchTab = (id) => ({
type: SWITCH_TAB,
id,
})

export const renameTab = (id, name) => ({
type: RENAME_TAB,
id,
name,
})
2 changes: 2 additions & 0 deletions client/src/components/EditorPanel.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
updateReadOnly,
} from 'actions/query'

import EditorTabs from 'components/EditorTabs'
import QueryVarsEditor from 'components/QueryVarsEditor'
import Editor from 'containers/Editor'

Expand Down Expand Up @@ -92,6 +93,7 @@ export default function EditorPanel() {

return (
<div className='editor-panel'>
<EditorTabs />
<div className='header'>
<div className='actions'>
{renderRadioBtn('query', 'Query', action, onUpdateAction)}
Expand Down
101 changes: 101 additions & 0 deletions client/src/components/EditorTabs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc.
* SPDX-License-Identifier: Apache-2.0
*/

import classnames from 'classnames'
import React, { useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'

import { addTab, closeTab, renameTab, switchTab } from 'actions/query'

import './EditorTabs.scss'

export default function EditorTabs() {
const dispatch = useDispatch()
const { tabs = [], activeTabId } = useSelector((state) => state.query)
const [editingId, setEditingId] = useState(null)
const [draftName, setDraftName] = useState('')

const startRename = (tab) => {
setEditingId(tab.id)
setDraftName(tab.name)
}

const commitRename = () => {
if (editingId !== null) {
dispatch(renameTab(editingId, draftName))
setEditingId(null)
}
}

const onInputKeyDown = (e) => {
e.stopPropagation()
if (e.key === 'Enter') {
commitRename()
} else if (e.key === 'Escape') {
setEditingId(null)
}
}

return (
<div className='editor-tabs' role='tablist'>
{tabs.map((tab) => (
<div
key={tab.id}
role='tab'
aria-selected={tab.id === activeTabId}
tabIndex={0}
className={classnames('editor-tab', {
active: tab.id === activeTabId,
})}
onClick={() => dispatch(switchTab(tab.id))}
onDoubleClick={() => startRename(tab)}
onKeyDown={(e) => {
if (e.key === 'Enter') {
dispatch(switchTab(tab.id))
}
}}
>
{editingId === tab.id ? (
<input
className='editor-tab-rename-input'
value={draftName}
autoFocus
onChange={(e) => setDraftName(e.target.value)}
onBlur={commitRename}
onKeyDown={onInputKeyDown}
onClick={(e) => e.stopPropagation()}
onDoubleClick={(e) => e.stopPropagation()}
/>
) : (
<span className='editor-tab-name' title={tab.name}>
{tab.name}
</span>
)}
{tabs.length > 1 && (
<button
type='button'
className='editor-tab-close'
title='Close tab'
onClick={(e) => {
e.stopPropagation()
dispatch(closeTab(tab.id))
}}
>
×
</button>
)}
</div>
))}
<button
type='button'
className='editor-tab-add'
title='New tab'
onClick={() => dispatch(addTab())}
>
+
</button>
</div>
)
}
98 changes: 98 additions & 0 deletions client/src/components/EditorTabs.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* SPDX-FileCopyrightText: © 2017-2026 Istari Digital, Inc.
* SPDX-License-Identifier: Apache-2.0
*/

.editor-tabs {
display: flex;
align-items: flex-end;
overflow-x: auto;
padding: 3px 4px 0;
background-color: #f3f3f3;
border-bottom: 1px solid #d2d2d2;

.editor-tab {
display: inline-flex;
align-items: center;
max-width: 180px;
margin-right: 2px;
margin-bottom: -1px;
padding: 3px 8px;
border: 1px solid transparent;
border-bottom: 1px solid #d2d2d2;
border-radius: 3px 3px 0 0;
background: transparent;
color: #8a8a8a;
font-size: 12px;
line-height: 18px;
white-space: nowrap;
cursor: pointer;
user-select: none;
outline: none;

&:hover {
background: #ececec;
color: #555;
}

&.active {
background: #fff;
border-color: #d2d2d2;
border-bottom-color: #fff;
color: #333;
}

.editor-tab-name {
overflow: hidden;
text-overflow: ellipsis;
}

.editor-tab-close {
visibility: hidden;
margin-left: 6px;
padding: 0 2px;
border: none;
background: transparent;
color: #8a8a8a;
font-size: 12px;
line-height: 1;
cursor: pointer;

&:hover {
color: #333;
}
}

&:hover .editor-tab-close,
&.active .editor-tab-close {
visibility: visible;
}

.editor-tab-rename-input {
width: 100px;
padding: 0 2px;
border: 1px solid #d2d2d2;
border-radius: 2px;
font-size: 12px;
line-height: 16px;
color: #333;
outline: none;
}
}

.editor-tab-add {
flex: none;
margin-bottom: 1px;
padding: 1px 8px 3px;
border: none;
background: transparent;
color: #8a8a8a;
font-size: 14px;
line-height: 1;
cursor: pointer;

&:hover {
color: #333;
}
}
}
Loading
Loading