Skip to content

feat: Implement edgeDoubleClick to open a new editor with full-width and height table #448

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

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
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
22 changes: 11 additions & 11 deletions packages/ds-ext/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ Set [`datastory.additionalDsServerCliArgs`](vscode://settings/datastory.addition
```

## Development Notes
1. Clone the repository.
2. In the root directory, run `yarn` to install dependencies, then `yarn build` to build the project.
3. Open the repo with an editor supporting the VS Code extension API, such as VS Code, Windsurf, or Cursor (collectively "Editor").
4. In the "Run and Debug" tab, click "Run Extension" to launch a new Editor instance.
5. In the new instance, open a directory and create a *.ds file with some content.
![alt text](./image/watch-ds-ext.png)

6. The Editor's terminal will automatically run the `watch:ds-ext` task.
* After modifying code under `data-ui/core/ds-ext`, restart the extension (Ctrl/Cmd + Shift + F5, then type "Reload Window") to apply changes in the new Editor instance.
* After modifying code under `data-nodejs`, wait for the `watch:ds-ext` task to output `Copied ds-server.min.js`, then restart the DataStory server to apply changes in the new Editor instance.
![alt text](./image/datastory-restart-server.png)

1. clone repo
2. in root, also run `yarn` and `yarn build`
3. open repo with VS Code
4. cd <repo_root>/nodejs
5. run `yarn watch:server -- -w <path_to_diagram_files_dir>`
* e.g. `yarn watch:server -- -w C:\User\s\Doc\Code\test-file`
6. In "Run and Debug" tab, click "Run Extension"
* This opens a new instance of VS Code
* The sixth step should follow the fifth step, as the fifth step will start the development server locally; otherwise, VSCode will automatically launch a socket server.
7. With the new instance, open a directory
8. Create a *.ds file (must have a content)

## publish ds-ext to VSCode extension marketplace

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added packages/ds-ext/image/watch-ds-ext.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 12 additions & 12 deletions packages/ds-ext/src/app/DiagramApp.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,30 @@
import React, { useEffect, useCallback } from 'react';
import { debounce, Diagram } from '@data-story/core';
import { AddNodeControl, CopyAsJsonControl, DataStory, RunControl } from '@data-story/ui';
import { AddNodeControl, CopyAsJsonControl, DataStory, RunControl, WorkspaceApiClientImplement } from '@data-story/ui';
import { VsCodeToast } from './VsCodeToast';
import { onDrop } from './onDrop';
import { createSocketClient } from './createSocketClient';
import { dsExtensionInitialData } from './dsExtensionInitialData';
import { createVsCodeClient } from './createVsCodeClient';

export default function DiagramApp() {
interface DiagramAppProps {
client: WorkspaceApiClientImplement;
}

export default function DiagramApp({ client: socketClient}: DiagramAppProps) {
/**
* socketClient: This is the main client of ds-ext, responsible for handling diagram data.
* vscodeClient: Primarily used to manage native operations in VSCode.
*/
const { client: socketClient, dispose } = createSocketClient();
const vscodeClient = createVsCodeClient(window.vscode);

const handleChange = useCallback(
debounce(async (diagram: Diagram) => {
socketClient!.updateDiagram?.(diagram, dsExtensionInitialData().documentId);
}, 100), // Debounced with 100ms delay11
[socketClient]);
[socketClient]
);

useEffect(() => {
return () => {
dispose();
};
}, [dispose]);
// useEffect for dispose is now handled in the parent component (index.tsx)

function handleEdgeDoubleClick(edgeId: string): void {
vscodeClient.onEdgeDoubleClick(edgeId);
Expand All @@ -44,8 +43,9 @@ export default function DiagramApp() {
onEdgeDoubleClick={handleEdgeDoubleClick}
onDrop={onDrop}
controls={[<RunControl />, <AddNodeControl />, <CopyAsJsonControl />]}
/>
<VsCodeToast postMessage={window.vscode.postMessage} />
>
<VsCodeToast postMessage={window.vscode.postMessage} />
</DataStory>
</div>
);
}
79 changes: 52 additions & 27 deletions packages/ds-ext/src/app/TableApp.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,57 @@
import React from 'react';
import { TableNodeComponent, DataStoryEvents, DataStoryNodeData } from '@data-story/ui';
import { Table } from '@data-story/core';
import React, { useRef, useState, useEffect } from 'react';
import { DataStoryNodeData, StandaloneTable, useObserverTable, WorkspaceApiClientImplement } from '@data-story/ui';
import { ItemValue, Table } from '@data-story/core';

export default function TableApp(props: { edgeId: string }) {
const tableId = `Table.${props.edgeId}`;
const tableProps = {
id: tableId,
data: {
...Table,
inputs: [
{
...Table.inputs[0],
id: `${tableId}.input`,
},
...Table.inputs.slice(1)
]
} as unknown as DataStoryNodeData,
selected: false
};
interface TableAppProps {
edgeId: string;
client: WorkspaceApiClientImplement;
}

const initialScreenCount = 20;
export const TableApp = ({ edgeId, client }: TableAppProps) => {
const parentRef = useRef<HTMLDivElement>(null);
const [items, setItems] = useState<ItemValue[]>([]);
const [isDataFetched, setIsDataFetched] = useState(true);
const data = {
id: edgeId,
...Table,
inputs: [
{
...Table.inputs[0],
id: `${edgeId}.input`,
schema: {},
},
],
outputs: [],
} as unknown as DataStoryNodeData;

const { loadMore } = useObserverTable({
linkIds: [edgeId],
client,
setIsDataFetched,
setItems,
items,
parentRef,
});

useEffect(() => {
async function fetchData() {
await loadMore.current(initialScreenCount);
}
fetchData();
}, [loadMore.current]);

// console.log('tableProps', tableProps);
return (
<div style={{ width: '100%', height: '100vh', padding: '20px' }}>
<h2 style={{ marginBottom: '20px' }}>Table Component Preview</h2>
<div style={{ width: '250px', height: '100px' }}>
<p>table place holder (todo: implement the table component)</p>
{/* <TableNodeComponent {...tableProps} /> */}
</div>
<div className="mt-4 p-4">
<StandaloneTable
isDataFetched={isDataFetched}
items={items}
data={data}
parentRef={parentRef}
rowCount={initialScreenCount}
/>
</div>
);
}
};

export default React.memo(TableApp);
41 changes: 36 additions & 5 deletions packages/ds-ext/src/app/index.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,63 @@
import React from 'react';
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import DiagramApp from './DiagramApp';
import TableApp from './TableApp';

import './fixCodeMirrorCopyPaste';
import { dsExtensionInitialData } from './dsExtensionInitialData';
import { createSocketClient } from './createSocketClient';

const rootElement = document.getElementById('root') as HTMLElement | null;

const getCurrentFilePath = () => {
const { documentId } = dsExtensionInitialData();
return documentId;
};

const getEdgeIdFromPath = (path: string) => {
const filename = path.split(/[\\/]/).pop() || '';
return filename.replace(/\.table\.ds$/, '');
};

const filePath = getCurrentFilePath();
const edgeId = getEdgeIdFromPath(filePath);
const isTableFile = filePath.endsWith('.table.ds');
const App = () => {
const filePath = getCurrentFilePath();
const edgeId = getEdgeIdFromPath(filePath);
const isTableFile = filePath.endsWith('.table.ds');

/**
* socketClient: This is the main client of ds-ext, responsible for handling diagram data.
* Moved from DiagramApp to be shared with both DiagramApp and TableApp.
*/
const { client: socketClient, dispose } = createSocketClient();

useEffect(() => {
return () => {
dispose();
};
}, [dispose]);

return (
<React.StrictMode>
{isTableFile ? (
<TableApp
edgeId={edgeId}
client={socketClient}
/>
) : (
<DiagramApp
client={socketClient}
/>
)}
</React.StrictMode>
);
};

if (rootElement) {
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
{isTableFile ? <TableApp edgeId={edgeId} /> : <DiagramApp />}
<App />
</React.StrictMode>,
);
} else {
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/components/Node/table/MemoizedTableBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ export const MemoizedTableBody = memo(({
return (
<td
key={cell.id}
className={`max-w-[256px] whitespace-nowrap text-left last:border-r-0 border-r-0.5 border-gray-300
${isLastColumn ? 'border-r-0' : ''}`}
className={`max-w-[256px] whitespace-nowrap text-left border-gray-300
${isLastColumn ? 'border-r-0' : 'border-r-0.5'}`}
style={{
display: 'flex',
position: 'relative',
Expand Down
4 changes: 2 additions & 2 deletions packages/ui/src/components/Node/table/MemoizedTableHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ export const MemoizedTableHeader = memo(({
position: 'relative',
width: `${columnWidth}px`,
}}
className={`max-w-[256px] whitespace-nowrap bg-gray-200 text-left last:border-r-0 border-r-0.5 border-gray-300
${isLastColumn ? 'border-r-0' : ''}`}
className={`max-w-[256px] whitespace-nowrap bg-gray-200 text-left border-gray-300
${isLastColumn ? 'border-r-0' : 'border-r-0.5'}`}
>
{flexRender(headerColumn.column.columnDef.header, headerColumn.getContext())}
</th>
Expand Down
19 changes: 11 additions & 8 deletions packages/ui/src/components/Node/table/StandaloneTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import { DataStoryNodeData } from '../ReactFlowNode';
export interface StandaloneTableProps {
items?: ItemValue[];
data: DataStoryNodeData,
wrapClassName?: string;
isDataFetched: boolean;
parentRef: React.MutableRefObject<HTMLDivElement | null>
wrapClassName?: string;
rowCount?: number;
}

/**
Expand All @@ -28,6 +29,7 @@ const StandaloneTable = ({
isDataFetched,
parentRef,
data,
rowCount = 10,
}: StandaloneTableProps) => {
const tableRef = useRef<HTMLTableElement>(null);

Expand Down Expand Up @@ -141,18 +143,19 @@ const StandaloneTable = ({
if (showNoData) {
return '40px';
}
if (rows.length <= 9) {
if (rows.length < rowCount) {
// + 1 for header
return (rows.length + 1) * FIXED_HEIGHT + 'px';
}
// The 14px extra height is determined through testing to ensure that the table scrollbar does not obscure the height of the table body when it appears.
return 11 * FIXED_HEIGHT + 14 + 'px';
}, [showNoData, rows.length]);
return (rowCount + 1) * FIXED_HEIGHT + 14 + 'px';
}, [showNoData, rows.length, rowCount]);

return (
<div
className={`text-xs border rounded border-gray-300 ${wrapClassName}`}
className={`${wrapClassName ?? ''} max-h-[4050px] max-w-[4050px] text-xs border rounded border-gray-300`}
>
<div data-cy={'data-story-table'} className="text-gray-600 max-w-[750px] bg-gray-100 rounded font-mono">
<div data-cy={'data-story-table'} className="text-gray-600 w-full h-full bg-gray-100 rounded font-mono">
{isDataFetched ? (
<div
ref={parentRef}
Expand All @@ -162,9 +165,9 @@ const StandaloneTable = ({
...virtualPaddingVars,
}}
data-cy={'data-story-table-scroll'}
className="max-h-64 min-w-6 nowheel overflow-auto scrollbar rounded-sm w-full"
className="h-full min-w-6 nowheel overflow-auto scrollbar rounded-sm w-full"
>
<table className="table-fixed grid max-w-[750px]">
<table className="table-fixed grid">
<MemoizedTableHeader
headerGroups={getHeaderGroups()}
virtualColumns={virtualColumns}
Expand Down
3 changes: 1 addition & 2 deletions packages/ui/src/components/Node/table/TableNodeComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,12 @@ const TableNodeComponent = ({
}, []);
useDataStoryEvent(dataStoryEvent);
const input = useMemo(() => data.inputs[0], [data]);
console.log('table node input', input, 'items :', items);

return (
<div>
<CustomHandle id={input.id} isConnectable={true} isInput={true} />
<StandaloneTable
wrapClassName={`${selected ? 'shadow-xl shadow-blue-100 ring-1 ring-blue-200' : ''} mt-[-12px]`}
wrapClassName={`${selected ? 'shadow-xl shadow-blue-100 ring-1 ring-blue-200' : ''} mt-[-12px] max-h-64 max-w-[750px]`}
isDataFetched={isDataFetched}
items={items}
parentRef={parentRef}
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/src/components/Node/table/UseObserverTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function useObserverTable({ linkIds = [], client, setIsDataFetched, setIt
parentRef: React.MutableRefObject<HTMLDivElement | null>;
client?: WorkspaceApiClientImplement;
}): {
loadMore: MutableRefObject<() => Promise<void> | undefined>
loadMore: MutableRefObject<(limit?: number) => Promise<void> | undefined>
}{
const pendingRequest = useRef(false);
const linkOffsets = useRef<Map<string, number>>(new Map());
Expand All @@ -23,7 +23,7 @@ export function useObserverTable({ linkIds = [], client, setIsDataFetched, setIt

const linkIdsString = JSON.stringify(linkIds);

const loadMore = useLatest(async () => {
const loadMore = useLatest(async (limit = initialScreenCount) => {
if (pendingRequest.current) return;

if (!client?.getDataFromStorage || !linkIds) return;
Expand All @@ -43,7 +43,7 @@ export function useObserverTable({ linkIds = [], client, setIsDataFetched, setIt
const result = await client?.getDataFromStorage?.({
type: 'getDataFromStorage',
linkId,
limit: initialScreenCount,
limit,
offset: currentOffset,
});

Expand Down
Loading