Skip to content

Commit 9bf8759

Browse files
committed
feat(table): implement table component
1 parent 2c8b782 commit 9bf8759

File tree

10 files changed

+202
-10
lines changed

10 files changed

+202
-10
lines changed

example/table.dart

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import 'package:commander_ui/src/commander.dart';
2+
import 'package:commander_ui/src/level.dart';
3+
4+
enum Shape { square, circle, triangle }
5+
6+
Future<void> main() async {
7+
final commander = Commander(level: Level.verbose);
8+
print('Hello World !');
9+
10+
commander.table(
11+
columns: ['Name', 'Age', 'Country', 'City'],
12+
lineSeparator: false,
13+
columnSeparator: false,
14+
data: [
15+
['Alice', '20', 'USA', 'New York'],
16+
['Bob', '25', 'Canada', 'Toronto'],
17+
['Charlie', '30', 'France', 'Paris'],
18+
['David', '35', 'Germany', 'Berlin'],
19+
['Eve', '40', 'Italy', 'Rome'],
20+
['Frank', '45', 'Japan', 'Tokyo'],
21+
['John', '50', 'China', 'Beijing'],
22+
],
23+
);
24+
}

lib/src/application/components/ask.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import 'package:commander_ui/src/application/utils/terminal_tools.dart';
66
import 'package:commander_ui/src/domains/models/component.dart';
77
import 'package:mansion/mansion.dart';
88

9-
final class Ask with TerminalTools implements Component<String?> {
9+
final class Ask with TerminalTools implements Component<Future<String?>> {
1010
final _completer = Completer<String?>();
1111

1212
final Terminal _terminal;

lib/src/application/components/checkbox.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'package:commander_ui/src/domains/models/component.dart';
77
import 'package:commander_ui/src/io.dart';
88
import 'package:mansion/mansion.dart';
99

10-
final class Checkbox<T> with TerminalTools implements Component<List<T>> {
10+
final class Checkbox<T> with TerminalTools implements Component<Future<List<T>>> {
1111
final _completer = Completer<List<T>>();
1212

1313
final Terminal _terminal;

lib/src/application/components/select.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'package:commander_ui/src/domains/models/component.dart';
77
import 'package:commander_ui/src/io.dart';
88
import 'package:mansion/mansion.dart';
99

10-
final class Select<T> with TerminalTools implements Component<T> {
10+
final class Select<T> with TerminalTools implements Component<Future<T>> {
1111
final _completer = Completer<T>();
1212

1313
final Terminal _terminal;

lib/src/application/components/swap.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import 'package:commander_ui/src/domains/models/component.dart';
77
import 'package:commander_ui/src/io.dart';
88
import 'package:mansion/mansion.dart';
99

10-
final class Swap<T> with TerminalTools implements Component<bool> {
10+
final class Swap<T> with TerminalTools implements Component<Future<bool>> {
1111
final _completer = Completer<bool>();
1212

1313
final Terminal _terminal;
@@ -31,7 +31,7 @@ final class Swap<T> with TerminalTools implements Component<bool> {
3131
}
3232

3333
@override
34-
Future<bool> handle() async {
34+
Future<bool> handle() {
3535
saveCursorPosition();
3636
hideCursor();
3737

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import 'dart:io';
2+
import 'dart:math';
3+
4+
import 'package:commander_ui/src/application/utils/terminal_tools.dart';
5+
import 'package:commander_ui/src/domains/models/component.dart';
6+
import 'package:mansion/mansion.dart';
7+
8+
final class Table with TerminalTools implements Component<void> {
9+
final List<List<String>> data;
10+
final List<String> columns;
11+
final bool lineSeparator;
12+
final bool columnSeparator;
13+
14+
/// Creates a new instance of [Table].
15+
Table({
16+
this.data = const [],
17+
this.columns = const [],
18+
this.lineSeparator = false,
19+
this.columnSeparator = false,
20+
});
21+
22+
@override
23+
void handle() {
24+
_render();
25+
}
26+
27+
void _render() async {
28+
saveCursorPosition();
29+
30+
final buffer = StringBuffer();
31+
32+
_drawLineSeparator(buffer,
33+
left: '┌',
34+
middle: columnSeparator ? '┬' : '─',
35+
right: '┐',
36+
separator: '─');
37+
_drawHeader(buffer);
38+
_drawLineSeparator(buffer,
39+
left: '├',
40+
middle: columnSeparator ? '┼' : '─',
41+
right: '┤',
42+
separator: '─');
43+
44+
for (var row in data) {
45+
final currentIndex = data.indexOf(row);
46+
_drawLine(buffer, currentIndex, row);
47+
}
48+
49+
_drawLineSeparator(buffer,
50+
left: '└',
51+
middle: columnSeparator ? '┴' : '─',
52+
right: '┘',
53+
separator: '─');
54+
55+
clearFromCursorToEnd();
56+
restoreCursorPosition();
57+
saveCursorPosition();
58+
59+
stdout.write(buffer.toString());
60+
}
61+
62+
List<int> getMaxCellWidths() {
63+
final List combined = [...columns, ...data];
64+
return List.generate(columns.length, (col) {
65+
int maxWidth = 0;
66+
for (var row in combined) {
67+
if (row is List<String>) {
68+
for (final cell in row) {
69+
maxWidth = max(maxWidth, cell.length);
70+
}
71+
}
72+
73+
if (row is String) {
74+
maxWidth = max(maxWidth, row.length);
75+
}
76+
}
77+
return maxWidth;
78+
});
79+
}
80+
81+
void _drawLineSeparator(
82+
StringBuffer buffer, {
83+
required String left,
84+
required String middle,
85+
required String right,
86+
required String separator,
87+
}) {
88+
final maxColWidths = getMaxCellWidths();
89+
90+
String line = left;
91+
for (int i = 0; i < maxColWidths.length; i++) {
92+
final isLast = i == maxColWidths.length - 1;
93+
94+
line += separator * (maxColWidths[i] + 2);
95+
line += isLast ? right : middle;
96+
}
97+
98+
buffer.writeln(line);
99+
}
100+
101+
void _drawHeader(StringBuffer buffer) {
102+
final maxColWidths = getMaxCellWidths();
103+
final headerBuffer = StringBuffer();
104+
105+
headerBuffer.write('│');
106+
107+
for (var i = 0; i < columns.length; i++) {
108+
headerBuffer.writeAnsiAll([
109+
SetStyles(Style.bold),
110+
Print(' ${columns[i].padRight(maxColWidths[i])}'),
111+
SetStyles.reset,
112+
]);
113+
114+
if (i == columns.length - 1) {
115+
headerBuffer.writeAnsi(Print(' │'));
116+
} else {
117+
headerBuffer.writeAnsi(Print(columnSeparator ? ' │' : ' '));
118+
}
119+
}
120+
121+
buffer.writeln(headerBuffer.toString());
122+
}
123+
124+
void _drawLine(StringBuffer buffer, int currentIndex, List<String> row) {
125+
final maxColWidths = getMaxCellWidths();
126+
127+
if (![0, data.length].contains(currentIndex) && lineSeparator) {
128+
_drawLineSeparator(buffer,
129+
left: '├',
130+
middle: lineSeparator
131+
? columnSeparator
132+
? '┼'
133+
: '─'
134+
: '┼',
135+
right: '┤',
136+
separator: '─');
137+
}
138+
139+
String rowLine = '│';
140+
for (var i = 0; i < columns.length; i++) {
141+
rowLine += ' ${row[i].padRight(maxColWidths[i])}';
142+
143+
if (i == columns.length - 1) {
144+
// headerBuffer.writeAnsi(Print(' │'));
145+
rowLine += ' │';
146+
} else {
147+
// headerBuffer.writeAnsi(Print(columnSeparator ? ' │' : ' '));
148+
rowLine += columnSeparator ? ' │' : ' ';
149+
}
150+
}
151+
152+
buffer.writeln(rowLine);
153+
}
154+
}

lib/src/application/components/task.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import 'package:mansion/mansion.dart';
88

99
final List<String> _loadingSteps = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
1010

11-
final class Task with TerminalTools implements Component<StepManager> {
11+
final class Task with TerminalTools implements Component<Future<StepManager>> {
1212
final Terminal _terminal;
1313
final bool _colored;
1414

lib/src/application/utils/terminal_tools.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,10 @@ mixin TerminalTools {
114114
final key = _readKey();
115115
terminal.disableRawMode();
116116

117-
if (key.controlChar == ControlCharacter.ctrlC) exit(130);
117+
if (key.controlChar == ControlCharacter.ctrlC) {
118+
showCursor();
119+
exit(130);
120+
}
118121
return key;
119122
}
120123

lib/src/commander.dart

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'dart:io' as io;
44
import 'package:commander_ui/src/application/components/ask.dart';
55
import 'package:commander_ui/src/application/components/checkbox.dart';
66
import 'package:commander_ui/src/application/components/select.dart';
7+
import 'package:commander_ui/src/application/components/table.dart';
78
import 'package:commander_ui/src/application/components/task.dart';
89
import 'package:commander_ui/src/application/components/swap.dart';
910
import 'package:commander_ui/src/application/terminals/terminal.dart';
@@ -79,4 +80,16 @@ class Commander with TerminalTools {
7980

8081
Future<StepManager> task<T>(String message, {bool colored = false}) =>
8182
Task(_terminal, colored: colored).handle();
83+
84+
void table(
85+
{required List<List<String>> data,
86+
required List<String> columns,
87+
bool lineSeparator = false,
88+
bool columnSeparator = false}) =>
89+
Table(
90+
data: data,
91+
columns: columns,
92+
lineSeparator: lineSeparator,
93+
columnSeparator: columnSeparator)
94+
.handle();
8295
}

lib/src/domains/models/component.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
import 'dart:async';
2-
31
abstract interface class Component<T> {
4-
FutureOr<T> handle();
2+
T handle();
53
}

0 commit comments

Comments
 (0)