Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement connections list screen #5153

Open
wants to merge 32 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6ac5086
Move schema navigation to a separate component
dfalbel Oct 24, 2024
205b111
Rename schema navigation action bar file
dfalbel Oct 24, 2024
5a38f35
Initial POC with two pages and listing instances in the first page
dfalbel Oct 24, 2024
012e50e
Initial table header version
dfalbel Oct 24, 2024
fc2c036
Style connections entries
dfalbel Oct 24, 2024
0173a6b
Style the action bar
dfalbel Oct 24, 2024
3bfa52c
Allow selecting and deleting a connection
dfalbel Oct 24, 2024
c05c4f8
Only display information of the active instance
dfalbel Oct 24, 2024
0d4e810
Show connection details on top
dfalbel Oct 24, 2024
f7a5eed
Refactor so we have one cache per instance instead
dfalbel Oct 24, 2024
a7bbc59
cleanup console.log
dfalbel Oct 28, 2024
dbd39fd
Make sure Connections list is updated when connection status changes
dfalbel Oct 28, 2024
cf13ae2
Initial setup for the resume connections modal
dfalbel Oct 28, 2024
38008b9
Style the modal dialog
dfalbel Oct 29, 2024
8e8a818
Implement copy code behavior
dfalbel Oct 29, 2024
bdd5084
Implement edit handler
dfalbel Oct 29, 2024
0608981
Add the resume connection handler
dfalbel Oct 29, 2024
2638e25
Revamp action bar + some cleanups
dfalbel Oct 29, 2024
9d847fa
Decouple instance from items.
dfalbel Oct 29, 2024
679114d
Cleanup cache entries definitions
dfalbel Oct 29, 2024
7d081ba
Simplify the cache class into a single function that's also more expl…
dfalbel Oct 29, 2024
f697f04
Rename from cache to utils
dfalbel Oct 29, 2024
6a2511d
Simplify `id` of connection item + do not require a separate client.
dfalbel Oct 29, 2024
3fddb91
Expand state in the instance instead of items
dfalbel Oct 30, 2024
208f3b2
Fix height of elements and store scroll state
dfalbel Oct 30, 2024
0d3ca70
Fix focusing using `%connection_show` magic.
dfalbel Oct 30, 2024
af4aa33
Remove filters placeholders for now
dfalbel Oct 30, 2024
fe1e4b2
Close the refresh notification after some time
dfalbel Oct 30, 2024
eeac4b6
Localize text in action bars
dfalbel Oct 30, 2024
3e71716
Close notification after a few seconds
dfalbel Oct 30, 2024
c600942
Fix opacity of icons that are not clickable.
dfalbel Oct 30, 2024
aea5da8
Cleanup console.log
dfalbel Oct 30, 2024
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

.connections-list-container {
height: 100%;
position: relative;
left: 0.5px;
}

.connections-list-header {
display: flex;
align-items: center;
border-bottom: 1px solid var(--vscode-positronVariables-border);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.connections-list-header .vertical-splitter {
background-color: var(--vscode-positronVariables-border);
}

.positron-connections-list .action-bar-button.disabled {
cursor: not-allowed;
}

.positron-connections-list .action-bar-button.disabled .action-bar-button-text {
color: var(--vscode-positronSideActionBar-disabledForeground);
}

.connections-list-item {
margin-top: 5px;
display: flex;
align-items: center;
}

.connections-list-container .col-icon,
.connections-list-container .col-name,
.connections-list-container .col-language,
.connections-list-container .col-status,
.connections-list-container .col-action {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.connections-list-container .col-icon {
flex-shrink: 0;
height: 100%;
}

.connections-list-container .col-name {
padding-left: 5px;
flex-basis: 35%;
flex-grow: 1;
}

.connections-list-container .col-status {
padding-left: 5px;
flex-basis: 25%;
flex-shrink: 0;
}

.connections-list-item .col-status.disabled {
color: var(--vscode-positronSideActionBar-disabledForeground);
}

.connections-list-container .col-language {
padding-left: 5px;
flex-basis: 20%;
flex-shrink: 0;
}

.connections-list-container.col-action {
flex-shrink: 0;
height: 100%;
}

.connections-list-item .col-action {
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}

.connections-list-item.selected {
color: var(--vscode-positronVariables-activeSelectionForeground);
background: var(--vscode-positronVariables-activeSelectionBackground);
outline: 1px;
outline-offset: -1px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

import React, { useState, useEffect, useRef } from 'react';
import { useStateRef } from 'vs/base/browser/ui/react/useStateRef';
import * as DOM from 'vs/base/browser/dom';
import { ActionBarButton } from 'vs/platform/positronActionBar/browser/components/actionBarButton';
import { ActionBarRegion } from 'vs/platform/positronActionBar/browser/components/actionBarRegion';
import { PositronActionBar } from 'vs/platform/positronActionBar/browser/positronActionBar';
import { PositronActionBarContextProvider } from 'vs/platform/positronActionBar/browser/positronActionBarContext';
import { ViewsProps } from 'vs/workbench/contrib/positronConnections/browser/positronConnections';
import { PositronConnectionsServices, usePositronConnectionsContext } from 'vs/workbench/contrib/positronConnections/browser/positronConnectionsContext';
import { FixedSizeList as List } from 'react-window';
import 'vs/css!./listConnections';
import { positronClassNames } from 'vs/base/common/positronUtilities';
import { languageIdToName } from 'vs/workbench/contrib/positronConnections/browser/components/schemaNavigation';
import { IPositronConnectionInstance } from 'vs/workbench/services/positronConnections/browser/interfaces/positronConnectionsInstance';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { showResumeConnectionModalDialog } from 'vs/workbench/contrib/positronConnections/browser/components/resumeConnectionModalDialog';
import { localize } from 'vs/nls';

export interface ListConnnectionsProps extends ViewsProps { }

export const ListConnections = (props: React.PropsWithChildren<ListConnnectionsProps>) => {

const context = usePositronConnectionsContext();
const { height, setActiveInstanceId } = props;

const [instances, setInstances] = useState<IPositronConnectionInstance[]>(context.connectionsService.getConnections);
useEffect(() => {
const disposableStore = new DisposableStore();
disposableStore.add(context.connectionsService.onDidChangeConnections((connections) => {
// Makes sure react recognises changes as this is a new array
setInstances([...connections]);
}));
return () => disposableStore.dispose();
}, [context.connectionsService]);

// We're required to save the scroll state because browsers will automatically
// scrollTop when an object becomes visible again.
const [, setScrollState, scrollStateRef] = useStateRef<number[] | undefined>(undefined);
const innerRef = useRef<HTMLElement>(undefined!);
useEffect(() => {
const disposableStore = new DisposableStore();
disposableStore.add(context.reactComponentContainer.onSaveScrollPosition(() => {
if (innerRef.current) {
setScrollState(DOM.saveParentsScrollTop(innerRef.current));
}
}));
disposableStore.add(context.reactComponentContainer.onRestoreScrollPosition(() => {
if (scrollStateRef.current) {
if (innerRef.current) {
DOM.restoreParentsScrollTop(innerRef.current, scrollStateRef.current);
}
setScrollState(undefined);
}
}));
return () => disposableStore.dispose();
}, [context.reactComponentContainer, scrollStateRef, setScrollState]);

const [selectedInstanceId, setSelectedInstanceId] = useState<string | undefined>(undefined);

const ItemEntry = (props: { index: number; style: any }) => {
const itemProps = instances[props.index];
const { language_id, name } = itemProps.metadata;

return (
<div
style={props.style}
className={positronClassNames(
'connections-list-item',
{ 'selected': itemProps.id === selectedInstanceId }
)}
onMouseDown={() => setSelectedInstanceId(itemProps.id)}
>
<div className='col-icon' style={{ width: `${26}px` }}></div>
<div className='col-name'>{name}</div>
<div className='col-language'>
{language_id ? languageIdToName(language_id) : ''}
</div>
<div
className={positronClassNames('col-status', { 'disabled': !itemProps.active })}
>
{
itemProps.active ?
localize('positron.listConnections.connected', 'Connected') :
localize('positron.listConnections.disconnected', 'Disconnected')
}
</div>
<div
className='col-action' style={{ width: `${26}px` }}
onMouseDown={() => {
if (itemProps.active) {
setActiveInstanceId(itemProps.id);
} else {
showResumeConnectionModalDialog(context, itemProps.id, setActiveInstanceId);
}
}}
>
<div
className={`codicon codicon-arrow-circle-right`}
>
</div>
</div>
</div>
);
};

const TABLE_HEADER_HEIGHT = 24;

return (
<div className='positron-connections-list'>
<ActionBar
{...context}
deleteConnectionHandler={
selectedInstanceId ?
() => {
context.connectionsService.removeConnection(selectedInstanceId);
} :
undefined
}
>
</ActionBar>
<div className='connections-list-container'>
<div className='connections-list-header' style={{ height: `${TABLE_HEADER_HEIGHT}px` }}>
<div className='col-icon' style={{ width: `${26}px` }}></div>
<VerticalSplitter />
<div className='col-name'>
{localize('positron.listConnections.connection', 'Connection')}
</div>
<VerticalSplitter />
<div className='col-language'>
{localize('positron.listConnections.language', 'Language')}
</div>
<VerticalSplitter />
<div className='col-status'>
{localize('positron.listConnections.status', 'Status')}
</div>
<VerticalSplitter />
<div className='col-action' style={{ width: `${26}px` }}></div>
</div>
<List
itemCount={instances.length}
itemSize={26}
height={height - ACTION_BAR_HEIGHT - TABLE_HEADER_HEIGHT}
width={'calc(100% - 2px)'}
itemKey={index => instances[index].id}
innerRef={innerRef}
>
{ItemEntry}
</List>
</div>
</div>
);
};

const VerticalSplitter = () => {
return (
<div className='vertical-splitter' style={{ width: '1px' }}>
<div className='sash' style={{ left: '-2px', width: '4px', cursor: 'auto' }}></div>
</div>
);
};


const ACTION_BAR_PADDING_LEFT = 8;
const ACTION_BAR_PADDING_RIGHT = 8;
const ACTION_BAR_HEIGHT = 32;

interface ActionBarProps extends PositronConnectionsServices {
deleteConnectionHandler?: () => void;
}

const ActionBar = (props: React.PropsWithChildren<ActionBarProps>) => {

return (
<div style={{ height: ACTION_BAR_HEIGHT }}>
<PositronActionBarContextProvider {...props}>
<PositronActionBar
size='small'
borderTop={true}
borderBottom={true}
paddingLeft={ACTION_BAR_PADDING_LEFT}
paddingRight={ACTION_BAR_PADDING_RIGHT}
>
<ActionBarRegion location='left'>
<ActionBarButton
align='left'
iconId='positron-new-connection'
text={localize('positron.listConnections.newConnection', 'New Connection')}
disabled={true}
/>
</ActionBarRegion>
<ActionBarRegion location='right'>
<ActionBarButton
align='right'
iconId='close'
text={localize('positron.listConnections.deleteConnection', 'Delete Connection')}
disabled={props.deleteConnectionHandler === undefined}
onPressed={props.deleteConnectionHandler}
/>
</ActionBarRegion>
</PositronActionBar>
</PositronActionBarContextProvider>
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (C) 2024 Posit Software, PBC. All rights reserved.
* Licensed under the Elastic License 2.0. See LICENSE.txt for license information.
*--------------------------------------------------------------------------------------------*/

.connections-resume-connection-modal .positron-modal-dialog-box .content-area {
bottom: 0px;
}

.connections-resume-connection-modal .content {
height: 100%;
display: grid;
gap: 10px;
grid-template-rows: 18px auto 28px;
grid-template-columns: auto 90px;
grid-template-areas:
"title title"
"code buttons"
"footer buttons";
}

.connections-resume-connection-modal .title {
grid-area: title;
}

.connections-resume-connection-modal .code {
grid-area: code;
overflow: auto;
padding: 10px;
border-radius: 10px;
border: solid 1px var(--vscode-positronVariables-border);
}

.connections-resume-connection-modal .code code {
white-space: pre;
}

.connections-resume-connection-modal .footer {
grid-area: footer;
display: flex;
}

.connections-resume-connection-modal .footer .button {
margin-left: auto;
padding-left: 15px;
padding-right: 15px;
}

.connections-resume-connection-modal .buttons {
grid-area: buttons;
display: flex;
flex-direction: column;
gap: 10px;
}

.connections-resume-connection-modal .buttons .top {
flex: 1;
}

.connections-resume-connection-modal .buttons .bottom {
display: flex;
flex-direction: column;
gap: 10px;
}
Loading