dbn-cli 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,254 @@
1
+ import type { DatabaseAdapter } from '../adapter/base.ts';
2
+ import type { ViewState, TablesViewState, TableDetailViewState, SchemaViewState, RowDetailViewState } from '../types.ts';
3
+
4
+ /**
5
+ * Navigation state manager
6
+ * Handles view hierarchy and cursor position
7
+ */
8
+ export class Navigator {
9
+ private adapter: DatabaseAdapter;
10
+ private states: ViewState[] = [];
11
+ private currentState: ViewState | null = null;
12
+
13
+ constructor(adapter: DatabaseAdapter) {
14
+ this.adapter = adapter;
15
+ }
16
+
17
+ /**
18
+ * Initialize navigator with tables list view
19
+ */
20
+ init(): void {
21
+ const tables = this.adapter.getTables();
22
+ this.currentState = {
23
+ type: 'tables',
24
+ tables: tables,
25
+ cursor: 0,
26
+ scrollOffset: 0
27
+ } as TablesViewState;
28
+ this.states = [this.currentState];
29
+ }
30
+
31
+ /**
32
+ * Get current view state
33
+ * @returns Current view state
34
+ */
35
+ getState(): ViewState {
36
+ if (!this.currentState) {
37
+ throw new Error('Navigator not initialized');
38
+ }
39
+ return this.currentState;
40
+ }
41
+
42
+ /**
43
+ * Move cursor up
44
+ */
45
+ moveUp(): void {
46
+ const state = this.currentState;
47
+ if (!state) return;
48
+
49
+ if (state.type === 'tables') {
50
+ if (state.cursor > 0) {
51
+ state.cursor--;
52
+ }
53
+ } else if (state.type === 'table-detail') {
54
+ if (state.dataCursor > 0) {
55
+ state.dataCursor--;
56
+ } else if (state.dataOffset > 0) {
57
+ state.dataOffset--;
58
+ // Reload data if needed
59
+ this.reload();
60
+ }
61
+ } else if (state.type === 'schema-view') {
62
+ if (state.cursor > 0) {
63
+ state.cursor--;
64
+ }
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Move cursor down
70
+ */
71
+ moveDown(): void {
72
+ const state = this.currentState;
73
+ if (!state) return;
74
+
75
+ if (state.type === 'tables') {
76
+ if (state.cursor < state.tables.length - 1) {
77
+ state.cursor++;
78
+ }
79
+ } else if (state.type === 'table-detail') {
80
+ const maxCursor = Math.min(state.data.length - 1, state.visibleRows - 1);
81
+ if (state.dataCursor < maxCursor) {
82
+ state.dataCursor++;
83
+ } else if (state.dataOffset + state.dataCursor < state.totalRows - 1) {
84
+ state.dataOffset++;
85
+ // Reload data if needed
86
+ this.reload();
87
+ }
88
+ } else if (state.type === 'schema-view') {
89
+ if (state.cursor < state.schema.length - 1) {
90
+ state.cursor++;
91
+ }
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Jump to top
97
+ */
98
+ jumpToTop(): void {
99
+ const state = this.currentState;
100
+ if (!state) return;
101
+
102
+ if (state.type === 'tables') {
103
+ state.cursor = 0;
104
+ } else if (state.type === 'table-detail') {
105
+ state.dataOffset = 0;
106
+ state.dataCursor = 0;
107
+ this.reload();
108
+ } else if (state.type === 'schema-view') {
109
+ state.cursor = 0;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Jump to bottom
115
+ */
116
+ jumpToBottom(): void {
117
+ const state = this.currentState;
118
+ if (!state) return;
119
+
120
+ if (state.type === 'tables') {
121
+ state.cursor = state.tables.length - 1;
122
+ } else if (state.type === 'table-detail') {
123
+ const lastPageOffset = Math.max(0, state.totalRows - state.visibleRows);
124
+ state.dataOffset = lastPageOffset;
125
+ state.dataCursor = Math.min(state.visibleRows - 1, state.totalRows - 1 - lastPageOffset);
126
+ this.reload();
127
+ } else if (state.type === 'schema-view') {
128
+ state.cursor = state.schema.length - 1;
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Toggle schema display in table detail view
134
+ */
135
+ toggleSchema(): void {
136
+ const state = this.currentState;
137
+ if (state && state.type === 'table-detail') {
138
+ // Toggle showSchema flag
139
+ state.showSchema = !state.showSchema;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * View schema in full screen mode
145
+ */
146
+ viewSchema(): void {
147
+ const state = this.currentState;
148
+ if (state && state.type === 'table-detail') {
149
+ // Create full screen schema view
150
+ const newState: SchemaViewState = {
151
+ type: 'schema-view',
152
+ tableName: state.tableName,
153
+ schema: state.schema,
154
+ cursor: 0,
155
+ scrollOffset: 0
156
+ };
157
+
158
+ this.states.push(newState);
159
+ this.currentState = newState;
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Enter the selected item (go deeper in hierarchy)
165
+ */
166
+ enter(): void {
167
+ const state = this.currentState;
168
+ if (!state) return;
169
+
170
+ if (state.type === 'tables') {
171
+ const selectedTable = state.tables[state.cursor];
172
+ if (!selectedTable) return;
173
+
174
+ // Load table details
175
+ const schema = this.adapter.getTableSchema(selectedTable.name);
176
+ const totalRows = selectedTable.row_count;
177
+ const data = this.adapter.getTableData(selectedTable.name, { limit: 100, offset: 0 });
178
+
179
+ const newState: TableDetailViewState = {
180
+ type: 'table-detail',
181
+ tableName: selectedTable.name,
182
+ schema: schema,
183
+ data: data,
184
+ totalRows: totalRows,
185
+ dataOffset: 0,
186
+ dataCursor: 0,
187
+ visibleRows: 20, // Will be updated by renderer
188
+ showSchema: false // Schema display toggle
189
+ };
190
+
191
+ this.states.push(newState);
192
+ this.currentState = newState;
193
+ } else if (state.type === 'table-detail') {
194
+ // Enter row detail view
195
+ if (state.data.length > 0) {
196
+ const selectedRow = state.data[state.dataCursor];
197
+ if (!selectedRow) return;
198
+
199
+ const newState: RowDetailViewState = {
200
+ type: 'row-detail',
201
+ tableName: state.tableName,
202
+ row: selectedRow,
203
+ rowIndex: state.dataOffset + state.dataCursor,
204
+ schema: state.schema
205
+ };
206
+
207
+ this.states.push(newState);
208
+ this.currentState = newState;
209
+ }
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Go back to previous view
215
+ */
216
+ back(): void {
217
+ if (this.states.length > 1) {
218
+ this.states.pop();
219
+ this.currentState = this.states[this.states.length - 1];
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Reload current view data
225
+ */
226
+ reload(): void {
227
+ const state = this.currentState;
228
+
229
+ if (state && state.type === 'table-detail') {
230
+ // Reload table data with current offset
231
+ const loadOffset = Math.max(0, state.dataOffset);
232
+ state.data = this.adapter.getTableData(state.tableName, {
233
+ limit: 100,
234
+ offset: loadOffset
235
+ });
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Get breadcrumb path
241
+ * @returns Breadcrumb path string
242
+ */
243
+ getPath(): string {
244
+ const parts: string[] = [];
245
+ for (const state of this.states) {
246
+ if (state.type === 'tables') {
247
+ // Root level, no need to add
248
+ } else if (state.type === 'table-detail') {
249
+ parts.push(state.tableName);
250
+ }
251
+ }
252
+ return parts.length > 0 ? parts.join(' > ') : '';
253
+ }
254
+ }