Skip to content

Commit 64575a5

Browse files
committed
feat: order
1 parent 36ec7e8 commit 64575a5

File tree

7 files changed

+113
-11
lines changed

7 files changed

+113
-11
lines changed

src/libs/QueryBuilder.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ interface QueryStates {
2525
select: (string | QueryRaw)[];
2626
limit?: number;
2727
offset?: number;
28+
orderBy: { name: string; order: 'ASC' | 'DESC' }[];
2829
}
2930

3031
abstract class QueryDialect {
@@ -48,6 +49,7 @@ export class QueryBuilder {
4849
type: 'select',
4950
where: [],
5051
select: [],
52+
orderBy: [],
5153
};
5254

5355
constructor(dialect: 'mysql') {
@@ -129,6 +131,10 @@ export class QueryBuilder {
129131
return this;
130132
}
131133

134+
orderBy(name: string, order: 'ASC' | 'DESC' = 'ASC') {
135+
this.states.orderBy.push({ name, order });
136+
}
137+
132138
protected buildWhere(where: QueryWhere[]): {
133139
sql: string;
134140
binding: unknown[];
@@ -226,6 +232,14 @@ export class QueryBuilder {
226232
'FROM',
227233
this.dialect.escapeIdentifier(this.states.table),
228234
whereSql ? 'WHERE ' + whereSql : whereSql,
235+
this.states.orderBy.length > 0
236+
? 'ORDER BY ' +
237+
this.states.orderBy
238+
.map(
239+
({ name, order }) => `${this.escapeIdentifier(name)} ${order}`
240+
)
241+
.join(',')
242+
: undefined,
229243
limitPart,
230244
]
231245
.filter(Boolean)

src/renderer/components/OptimizeTable/TableHeader.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ export default function TableHeader({
3030

3131
<div className={styles.tableCellContent}>{header.name}</div>
3232

33+
{header.rightIcon && (
34+
<div className={styles.tableHeaderIcon}>{header.rightIcon}</div>
35+
)}
36+
3337
{header.resizable && (
3438
<TableHeaderResizeHandler idx={idx} onResize={onHeaderResize} />
3539
)}

src/renderer/components/OptimizeTable/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ export interface OptimizeTableHeaderProps {
1515
initialSize: number;
1616
resizable?: boolean;
1717
icon?: ReactElement;
18+
rightIcon?: ReactElement;
1819
tooltip?: string;
1920
menu?: ContextMenuItemProps[];
2021
}

src/renderer/components/OptimizeTable/styles.module.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434

3535
.tableCellContent {
3636
overflow: hidden;
37+
flex-grow: 1;
3738
white-space: nowrap;
3839
}
3940

src/renderer/screens/DatabaseScreen/QueryResultViewer/QueryResultTable.tsx

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,29 @@ import { useTableCellManager } from './TableCellManager';
99
import OptimizeTable from 'renderer/components/OptimizeTable';
1010
import Icon from 'renderer/components/Icon';
1111
import useDataTableContextMenu from './useDataTableContextMenu';
12+
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
13+
import {
14+
faArrowDown,
15+
faArrowUp,
16+
faChevronDown,
17+
faChevronUp,
18+
} from '@fortawesome/free-solid-svg-icons';
1219

1320
interface QueryResultTableProps {
1421
headers: QueryResultHeader[];
1522
result: QueryResultWithIndex[];
23+
onSortHeader?: (header: QueryResultHeader, by: 'ASC' | 'DESC') => void;
24+
onSortReset?: () => void;
25+
sortedHeader?: { header: QueryResultHeader; by: 'ASC' | 'DESC' };
1626
}
1727

18-
function QueryResultTable({ headers, result }: QueryResultTableProps) {
28+
function QueryResultTable({
29+
headers,
30+
result,
31+
onSortHeader,
32+
onSortReset,
33+
sortedHeader,
34+
}: QueryResultTableProps) {
1935
const [newRowCount, setNewRowCount] = useState(0);
2036
const { collector } = useQueryResultChange();
2137
const { cellManager } = useTableCellManager();
@@ -104,17 +120,53 @@ function QueryResultTable({ headers, result }: QueryResultTableProps) {
104120
icon: header?.schema?.primaryKey ? (
105121
<Icon.GreenKey size="sm" />
106122
) : undefined,
123+
rightIcon:
124+
sortedHeader && sortedHeader.header.name === header.name ? (
125+
sortedHeader.by === 'ASC' ? (
126+
<FontAwesomeIcon icon={faChevronDown} />
127+
) : (
128+
<FontAwesomeIcon icon={faChevronUp} />
129+
)
130+
) : undefined,
107131
tooltip: header.columnDefinition?.comment,
108132
menu: [
109-
{ text: 'Order by ASC', disabled: true },
110-
{ text: 'Order by DESC', disabled: true },
133+
{
134+
text: 'Reset Order',
135+
separator: true,
136+
disabled: !onSortHeader,
137+
onClick: () => {
138+
if (onSortReset) {
139+
onSortReset();
140+
}
141+
},
142+
},
143+
{
144+
text: 'Order by ASC',
145+
icon: <FontAwesomeIcon icon={faArrowDown} />,
146+
disabled: !onSortHeader,
147+
onClick: () => {
148+
if (onSortHeader) {
149+
onSortHeader(header, 'ASC');
150+
}
151+
},
152+
},
153+
{
154+
text: 'Order by DESC',
155+
icon: <FontAwesomeIcon icon={faArrowUp} />,
156+
disabled: !onSortHeader,
157+
onClick: () => {
158+
if (onSortHeader) {
159+
onSortHeader(header, 'DESC');
160+
}
161+
},
162+
},
111163
],
112164
initialSize: Math.max(
113165
header.name.length * 10,
114166
getInitialSizeByHeaderType(idx, header)
115167
),
116168
}));
117-
}, [result]);
169+
}, [result, onSortHeader, sortedHeader]);
118170

119171
const renderCell = useCallback(
120172
(y: number, x: number) => {

src/renderer/screens/DatabaseScreen/TableDataViewer/index.tsx

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,16 @@ export default function TableDataViewer({
3030
const [loading, setLoading] = useState(true);
3131
const [totalRows, setTotalRows] = useState(0);
3232
const [data, setData] = useState<QueryResultWithIndex[]>([]);
33-
const [headers, setHeaders] = useState<QueryResultHeader[]>([]);
3433

34+
const [sortedHeader, setSortedHeader] = useState<
35+
| {
36+
by: 'ASC' | 'DESC';
37+
header: QueryResultHeader;
38+
}
39+
| undefined
40+
>();
41+
42+
const [headers, setHeaders] = useState<QueryResultHeader[]>([]);
3543
const [rowRange, setRowRange] = useState<{ start: number; end: number }>({
3644
start: 0,
3745
end: 0,
@@ -52,12 +60,17 @@ export default function TableDataViewer({
5260
}, [databaseName, tableName, setTotalRows]);
5361

5462
useEffect(() => {
55-
const selectSql = qb()
63+
const builder = qb()
5664
.table(`${databaseName}.${tableName}`)
5765
.select()
5866
.offset(PAGE_SIZE * page)
59-
.limit(PAGE_SIZE)
60-
.toRawSQL();
67+
.limit(PAGE_SIZE);
68+
69+
if (sortedHeader) {
70+
builder.orderBy(sortedHeader.header.name, sortedHeader.by);
71+
}
72+
73+
const selectSql = builder.toRawSQL();
6174

6275
setLoading(true);
6376
runner
@@ -80,7 +93,15 @@ export default function TableDataViewer({
8093
});
8194
})
8295
.catch();
83-
}, [runner, page, setHeaders, setData, setTotalRows, setLoading]);
96+
}, [
97+
runner,
98+
page,
99+
sortedHeader,
100+
setHeaders,
101+
setData,
102+
setTotalRows,
103+
setLoading,
104+
]);
84105

85106
const onNextPage = useCallback(() => {
86107
setPage((prev) => prev + 1);
@@ -97,7 +118,13 @@ export default function TableDataViewer({
97118
{!loading && (
98119
<QueryResultChangeProvider>
99120
<TableCellManagerProvider>
100-
<QueryResultTable headers={headers} result={data} />
121+
<QueryResultTable
122+
headers={headers}
123+
result={data}
124+
onSortHeader={(header, by) => setSortedHeader({ by, header })}
125+
onSortReset={() => setSortedHeader(undefined)}
126+
sortedHeader={sortedHeader}
127+
/>
101128
</TableCellManagerProvider>
102129
</QueryResultChangeProvider>
103130
)}

src/stories/OptimizedTable.stories.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ function StoryPage() {
1616
<ContextMenuProvider>
1717
<div
1818
style={{ width: '700px', height: 400, overflow: 'auto' }}
19-
onContextMenu={(e) => e.preventDefault()}
19+
onContextMenu={(e) => {
20+
e.preventDefault();
21+
}}
2022
>
2123
<OptimizeTable
2224
headers={[
@@ -26,6 +28,7 @@ function StoryPage() {
2628
initialSize: 250,
2729
tooltip: 'Hello Tooltip with resize',
2830
resizable: true,
31+
rightIcon: <FontAwesomeIcon icon={faArrowDown} />,
2932
menu: [
3033
{
3134
text: 'Clear Order',

0 commit comments

Comments
 (0)