Skip to content
Merged
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
9 changes: 9 additions & 0 deletions src/drivers/SQLLikeConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ export interface SqliteConnectionConfig {
path: string;
}

export interface ConnectionConfigTree {
id: string;
name: string;
nodeType: 'folder' | 'connection';
config?: ConnectionStoreItem;
parentId?: string;
children?: ConnectionConfigTree[];
}

export interface ConnectionStoreItem {
id: string;
name: string;
Expand Down
60 changes: 55 additions & 5 deletions src/renderer/components/ListViewItem/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,25 @@
import { ReactElement, useMemo } from 'react';
import { ReactElement, useCallback, useEffect, useMemo, useState } from 'react';
import styles from './styles.module.scss';
import Icon from '../Icon';
import { useAppFeature } from 'renderer/contexts/AppFeatureProvider';

interface ListViewItemProps {
text: string;
draggable?: boolean;
highlight?: string;
icon?: ReactElement;
changed?: boolean;
selected?: boolean;
onClick?: () => void;
onDoubleClick?: () => void;
onDragStart?: (e: React.DragEvent<HTMLDivElement>) => void;
onDragOver?: (e: React.DragEvent<HTMLDivElement>) => void;
onDrop?: (e: React.DragEvent<HTMLDivElement>) => void;
onContextMenu?: React.MouseEventHandler<HTMLDivElement>;

renaming?: boolean;
onRenamed?: (newName: string | null) => void;

// This is used for rendering TreeView
depth?: number;
hasCollapsed?: boolean;
Expand All @@ -36,6 +43,12 @@ export default function ListViewItem({
onClick,
onDoubleClick,
onContextMenu,
draggable,
onDragStart,
onDragOver,
onDrop,
renaming,
onRenamed,

// For TreeView
depth,
Expand All @@ -44,6 +57,18 @@ export default function ListViewItem({
onCollapsedClick,
}: ListViewItemProps) {
const { theme } = useAppFeature();
const [renameDraftValue, setRenameDraftValue] = useState('');

useEffect(() => {
setRenameDraftValue(text);
}, [renaming, text, setRenameDraftValue]);

const onRenameDone = useCallback(
(value: string | null) => {
if (onRenamed) onRenamed(value);
},
[onRenamed]
);

const finalText = useMemo(() => {
if (highlight) {
Expand Down Expand Up @@ -75,6 +100,10 @@ export default function ListViewItem({
onClick={onClick}
onDoubleClick={onDoubleClick}
onContextMenu={onContextMenu}
draggable={draggable}
onDragOver={onDragOver}
onDragStart={onDragStart}
onDrop={onDrop}
>
{!!depth &&
new Array(depth)
Expand Down Expand Up @@ -103,10 +132,31 @@ export default function ListViewItem({
</div>
))}
{!hasCollapsed && <div className={styles.icon}>{icon}</div>}
<div
className={styles.text}
dangerouslySetInnerHTML={{ __html: finalText }}
/>
{renaming ? (
<div className={styles.text}>
<input
autoFocus
onBlur={() => {
onRenameDone(renameDraftValue);
}}
onKeyDown={(e) => {
if (e.key === 'Enter') {
onRenameDone(renameDraftValue);
} else if (e.key === 'Escape') {
onRenameDone(null);
}
}}
type="text"
value={renameDraftValue}
onChange={(e) => setRenameDraftValue(e.currentTarget.value)}
/>
</div>
) : (
<div
className={styles.text}
dangerouslySetInnerHTML={{ __html: finalText }}
/>
)}
</div>
);
}
12 changes: 10 additions & 2 deletions src/renderer/components/ListViewItem/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@
text-overflow: ellipsis;
flex-grow: 1;
overflow: hidden;

input {
border: 0;
background: inherit;
color: inherit;
outline: none;
width: 100%;
}
}

.icon {
Expand All @@ -34,7 +42,7 @@
display: flex;
align-items: center;
justify-content: center;

img {
width: 20px;
height: 20px;
Expand Down Expand Up @@ -63,4 +71,4 @@
border-top: 1px solid var(--color-list-line-guide);
width: 10px;
}
}
}
159 changes: 99 additions & 60 deletions src/renderer/components/TreeView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import styles from './styles.module.scss';
import ListViewItem from '../ListViewItem';
import { ReactElement, useCallback } from 'react';

let GLOBAL_TREE_DRAG_ITEM: unknown;

export interface TreeViewItemData<T> {
id: string;
text?: string;
Expand All @@ -10,44 +12,62 @@ export interface TreeViewItemData<T> {
children?: TreeViewItemData<T>[];
}

interface TreeViewProps<T> {
items: TreeViewItemData<T>[];
selected?: TreeViewItemData<T>;
collapsedKeys?: string[];
interface TreeViewCommonProps<T> {
draggable?: boolean;
onDragItem?: (from: TreeViewItemData<T>, to: TreeViewItemData<T>) => void;
onCollapsedChange?: (value?: string[]) => void;
onSelectChange?: (value?: TreeViewItemData<T>) => void;
onDoubleClick?: (value: TreeViewItemData<T>) => void;
onContextMenu?: React.MouseEventHandler;
selected?: TreeViewItemData<T>;
collapsedKeys?: string[];
highlight?: string;
highlightDepth?: number;
}

function TreeViewItem<T>({
item,
depth,
/**
* When renameSelectedItem is true, it will render, the current
* selected item as editable field.
*/
renameSelectedItem?: boolean;

selected,
onSelectChange,
/**
* When Enter or Lost Focus, it will treat as successful rename
* If user press escape, it will cancel the rename
*
* @param newName The new name that we just rename into.
* If it is null, it means we cancel the rename
* @returns
*/
onRenamedSelectedItem?: (newName: string | null) => void;
}

collapsedKeys,
onCollapsedChange,
onDoubleClick,
interface TreeViewProps<T> extends TreeViewCommonProps<T> {
items: TreeViewItemData<T>[];
onBeforeSelectChange?: () => Promise<boolean>;
onContextMenu?: React.MouseEventHandler;
emptyState?: ReactElement;
}

highlight,
highlightDepth,
}: {
interface TreeViewItemProps<T> extends TreeViewCommonProps<T> {
item: TreeViewItemData<T>;
depth: number;
}

selected?: TreeViewItemData<T>;
onSelectChange?: (value?: TreeViewItemData<T>) => void;
function TreeViewItem<T>(props: TreeViewItemProps<T>) {
const { depth, item, ...common } = props;
const {
collapsedKeys,
draggable,
onDragItem,
onDoubleClick,
highlight,
selected,
onSelectChange,
onCollapsedChange,
highlightDepth,
renameSelectedItem,
onRenamedSelectedItem,
} = props;

onCollapsedChange?: (value?: string[]) => void;
collapsedKeys?: string[];
onDoubleClick?: (value: TreeViewItemData<T>) => void;
highlight?: string;
highlightDepth?: number;
}) {
const hasCollapsed = item.children && item.children.length > 0;
const isCollapsed = collapsedKeys?.includes(item.id);

Expand All @@ -57,9 +77,23 @@ function TreeViewItem<T>({
}
}, [onSelectChange, item]);

const isSelected = selected?.id === item.id;

return (
<div>
<ListViewItem
draggable={draggable}
onDragStart={() => {
GLOBAL_TREE_DRAG_ITEM = item;
}}
onDragOver={(e) => e.preventDefault()}
onDrop={() => {
if (onDragItem) {
if (GLOBAL_TREE_DRAG_ITEM) {
onDragItem(GLOBAL_TREE_DRAG_ITEM as TreeViewItemData<T>, item);
}
}
}}
key={item.id}
text={item.text || ''}
icon={item.icon}
Expand All @@ -72,7 +106,9 @@ function TreeViewItem<T>({
highlight={depth === highlightDepth ? highlight : undefined}
hasCollapsed={hasCollapsed}
collapsed={isCollapsed}
selected={selected?.id === item.id}
selected={isSelected}
renaming={isSelected && renameSelectedItem}
onRenamed={onRenamedSelectedItem}
onClick={onSelectChangeCallback}
onContextMenu={onSelectChangeCallback}
onCollapsedClick={() => {
Expand All @@ -94,16 +130,10 @@ function TreeViewItem<T>({
{item.children?.map((item) => {
return (
<TreeViewItem
{...common}
key={item.id}
item={item}
depth={depth + 1}
highlight={highlight}
highlightDepth={highlightDepth}
selected={selected}
onSelectChange={onSelectChange}
collapsedKeys={collapsedKeys}
onCollapsedChange={onCollapsedChange}
onDoubleClick={onDoubleClick}
/>
);
})}
Expand All @@ -113,35 +143,44 @@ function TreeViewItem<T>({
);
}

export default function TreeView<T>({
items,
selected,
onSelectChange,
onCollapsedChange,
collapsedKeys,
onDoubleClick,
onContextMenu,
highlight,
highlightDepth,
}: TreeViewProps<T>) {
export default function TreeView<T>(props: TreeViewProps<T>) {
const {
items,
onSelectChange,
onBeforeSelectChange,
onContextMenu,
emptyState,
...common
} = props;

const onSelectChangeWithHook = useCallback(
(item: TreeViewItemData<T> | undefined) => {
if (onSelectChange) {
if (onBeforeSelectChange) {
onBeforeSelectChange().then(() => onSelectChange(item));
} else {
onSelectChange(item);
}
}
},
[onSelectChange, onBeforeSelectChange]
);

return (
<div className={`${styles.treeView} scroll`} onContextMenu={onContextMenu}>
{items.map((item) => {
return (
<TreeViewItem
key={item.id}
item={item}
depth={0}
highlight={highlight}
highlightDepth={highlightDepth}
selected={selected}
onSelectChange={onSelectChange}
onDoubleClick={onDoubleClick}
onCollapsedChange={onCollapsedChange}
collapsedKeys={collapsedKeys}
/>
);
})}
{items.length > 0
? items.map((item) => {
return (
<TreeViewItem
{...common}
onSelectChange={onSelectChangeWithHook}
key={item.id}
item={item}
depth={0}
/>
);
})
: emptyState}
</div>
);
}
Loading