Skip to content
Draft
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
4 changes: 2 additions & 2 deletions app/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export type Option = {
export type PrimaryDataType = 'issue' | 'commit' | 'pull-request' | 'snippet' | 'item';
type GridCellState = 'empty' | 'generating' | 'done' | 'error';

export type ColumnType = 'text' | 'select' | 'select-user' | 'file' | 'issue-pr' | 'commit';
export type ColumnType = 'text' | 'select' | 'select-user' | 'file' | 'boolean' | 'issue-pr' | 'commit';

export type ColumnResponse = {
text: string;
Expand All @@ -24,6 +24,7 @@ export type ColumnResponse = {
file:
| { file: { path: string; repository: string } }
| { files: { path: string; repository: string }[] };
boolean: { value: boolean };
'issue-pr': {
reference?: {
number: number;
Expand All @@ -50,7 +51,6 @@ export type ColumnResponse = {
message?: string;
}>;
};
boolean: boolean;
};

export type GridCell<T extends keyof ColumnResponse = ColumnType> = {
Expand Down
2 changes: 1 addition & 1 deletion app/columns/IssuePRColumnType.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export const IssuePRColumnType: BaseColumnType<'issue-pr'> = {
parsed = JSON.parse(responseContent);
} catch (error) {
console.error('Failed to parse response content:', error);
return multiple ? { references: [] } : { reference: null };
return multiple ? { references: [] } : { reference: undefined };
}
return multiple ? { references: parsed.references } : { reference: parsed.reference };
},
Expand Down
18 changes: 14 additions & 4 deletions app/components/Cell.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ type CellProps = {
rowIndex?: number;
};

export default function Cell({ sx, cell, onClick, isSelected = false, rowIndex }: CellProps) {
function Cell({ sx, cell, onClick, isSelected = false, rowIndex }: CellProps) {
const { deleteRow } = useGridContext();
const isPrimaryCell = rowIndex !== undefined;

Expand All @@ -31,6 +31,12 @@ export default function Cell({ sx, cell, onClick, isSelected = false, rowIndex }
backgroundColor: 'canvas.inset',
};

const handleDeleteRow = React.useCallback(() => {
if (rowIndex !== undefined) {
deleteRow(rowIndex);
}
}, [deleteRow, rowIndex]);

return (
<Box
sx={{
Expand Down Expand Up @@ -73,7 +79,7 @@ export default function Cell({ sx, cell, onClick, isSelected = false, rowIndex }
</ActionMenu.Anchor>
<ActionMenu.Overlay width="medium">
<ActionList>
<ActionList.Item onSelect={() => deleteRow(rowIndex)}>
<ActionList.Item onSelect={handleDeleteRow}>
<ActionList.LeadingVisual>
<TrashIcon />
</ActionList.LeadingVisual>
Expand All @@ -88,7 +94,7 @@ export default function Cell({ sx, cell, onClick, isSelected = false, rowIndex }
);
}

export function GridCellContent({ cell }: { cell: GridCell }) {
const GridCellContent = React.memo(function GridCellContent({ cell }: { cell: GridCell }) {
if (cell.state === 'error') {
return cell.errorMessage;
}
Expand All @@ -98,4 +104,8 @@ export function GridCellContent({ cell }: { cell: GridCell }) {

const columnType = columnTypes[cell.columnType] as BaseColumnType<ColumnType>;
return columnType.renderCell(cell);
}
});

// Memoize Cell component to prevent unnecessary re-renders
export default React.memo(Cell);
export { GridCellContent };
94 changes: 53 additions & 41 deletions app/components/ColumnTitle.tsx
Original file line number Diff line number Diff line change
@@ -1,62 +1,74 @@
import { IconButton, Box, ActionMenu, ActionList } from '@primer/react';
import { KebabHorizontalIcon, PencilIcon, TrashIcon } from '@primer/octicons-react';
import React, { useCallback } from 'react';
import { Box, Text, IconButton, ActionMenu, ActionList } from '@primer/react';
import { KebabHorizontalIcon, TrashIcon } from '@primer/octicons-react';
import { useGridContext } from './GridContext';

export default function ColumnTitle({ title, index }: { title: string; index?: number }) {
const { deleteColumnByIndex } = useGridContext();
type ColumnTitleProps = {
title: string;
index?: number;
};

function ColumnTitle({ title, index }: ColumnTitleProps) {
const { deleteColumnByIndex, setGroupBy, setFilterBy } = useGridContext();
const hasIndex = index !== undefined;

const handleDeleteColumn = useCallback(() => {
if (index !== undefined) {
deleteColumnByIndex(index);
}
}, [deleteColumnByIndex, index]);

const handleGroupBy = useCallback(() => {
setGroupBy(title);
}, [setGroupBy, title]);

const handleClearFilters = useCallback(() => {
setFilterBy(undefined, undefined);
}, [setFilterBy]);

return (
<Box
sx={{
p: 2,
pl: 3,
flex: 1,
position: 'relative',
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
zIndex: 2,
position: 'sticky',
top: 0,
justifyContent: 'space-between',
p: 3,
fontSize: 1,
backgroundColor: 'white',
color: 'fg.default',
fontWeight: 'semibold',
borderRight: '1px solid',
borderColor: 'border.default',
flex: 1,
boxSizing: 'border-box',
minWidth: '260px',
borderRight: '1px solid',
borderColor: '#f0f0f0',
fontWeight: 'bold',
'&:last-child': {
border: 0,
},
}}
>
<Box sx={{ flex: 1 }}>{title}</Box>

<ActionMenu>
<ActionMenu.Anchor>
<IconButton
variant="invisible"
aria-labelledby="Column menu"
icon={KebabHorizontalIcon}
/>
</ActionMenu.Anchor>
<ActionMenu.Overlay width="medium">
<ActionList>
{index !== undefined && (
<ActionList.Item onSelect={() => deleteColumnByIndex(index)}>
<Text>{title}</Text>
{hasIndex && (
<ActionMenu>
<ActionMenu.Anchor>
<IconButton variant="invisible" aria-labelledby="Column menu" icon={KebabHorizontalIcon} />
</ActionMenu.Anchor>
<ActionMenu.Overlay width="medium">
<ActionList>
<ActionList.Item onSelect={handleGroupBy}>Group by {title}</ActionList.Item>
<ActionList.Item onSelect={handleClearFilters}>Clear filters</ActionList.Item>
<ActionList.Divider />
<ActionList.Item variant="danger" onSelect={handleDeleteColumn}>
<ActionList.LeadingVisual>
<TrashIcon />
</ActionList.LeadingVisual>
Delete
Delete column
</ActionList.Item>
)}
<ActionList.Item onSelect={() => alert('Copy link clicked')}>
<ActionList.LeadingVisual>
<PencilIcon />
</ActionList.LeadingVisual>
Edit
</ActionList.Item>
</ActionList>
</ActionMenu.Overlay>
</ActionMenu>
</ActionList>
</ActionMenu.Overlay>
</ActionMenu>
)}
</Box>
);
}

export default React.memo(ColumnTitle);
25 changes: 18 additions & 7 deletions app/components/DebugDialog.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
import { useState } from 'react';
import React, { useState, useCallback } from 'react';
import { Box } from '@primer/react';
import { Dialog } from '@primer/react/experimental';

export default function DebugDialog({ prompt, sources }: { prompt: string; sources: string[] }) {
interface DebugDialogProps {
prompt: string;
sources: string[];
}

const DebugDialog = React.memo(function DebugDialog({ prompt, sources }: DebugDialogProps) {
const [open, setOpen] = useState<boolean>(false);

const handleOpen = useCallback(() => setOpen(true), []);
const handleClose = useCallback(() => setOpen(false), []);

return (
<>
<Box
Expand All @@ -19,20 +28,20 @@ export default function DebugDialog({ prompt, sources }: { prompt: string; sourc
color: 'fg.default',
},
}}
onClick={() => setOpen(true)}
onClick={handleOpen}
>
Debug
</Box>

{open && (
<Dialog title="Debug" onClose={() => setOpen(false)}>
<Dialog title="Debug" onClose={handleClose}>
{sources.length > 0 && (
<>
<Box sx={{ fontSize: 0, pb: 2, fontWeight: 'semibold', color: 'fg.muted' }}>
Sources used:
</Box>
{sources.map((source) => (
<Box key={source} sx={{ fontSize: 0, pb: 2, color: 'fg.muted' }}>
{sources.map((source, index) => (
<Box key={`${source}-${index}`} sx={{ fontSize: 0, pb: 2, color: 'fg.muted' }}>
{source}
</Box>
))}
Expand Down Expand Up @@ -66,4 +75,6 @@ export default function DebugDialog({ prompt, sources }: { prompt: string; sourc
)}
</>
);
}
});

export default DebugDialog;
48 changes: 48 additions & 0 deletions app/components/Grid.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,15 @@

.markdownContainer {
font-size: 14px;
contain: content;
word-wrap: break-word;
overflow-wrap: break-word;
}

.markdownContainer img {
max-width: 100%;
height: auto;
loading: lazy;
}

.markdownContainer > p {
Expand All @@ -26,3 +31,46 @@
margin-bottom: 0;
padding-bottom: 0;
}

/* Performance optimizations */
.grid-app {
contain: layout style;
}

.virtualized-table {
contain: strict;
will-change: transform;
}

.grid-row {
contain: layout style;
}

.grid-cell {
contain: layout style;
}

/* Smooth scrolling optimizations */
.scroll-container {
scrollbar-width: thin;
scrollbar-color: rgba(0, 0, 0, 0.2) transparent;
-webkit-overflow-scrolling: touch;
}

.scroll-container::-webkit-scrollbar {
width: 8px;
height: 8px;
}

.scroll-container::-webkit-scrollbar-track {
background: transparent;
}

.scroll-container::-webkit-scrollbar-thumb {
background-color: rgba(0, 0, 0, 0.2);
border-radius: 4px;
}

.scroll-container::-webkit-scrollbar-thumb:hover {
background-color: rgba(0, 0, 0, 0.3);
}
Loading
Loading