rozenite-sqlite 0.0.7 → 0.0.9

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.
@@ -1,220 +0,0 @@
1
- import React, { useState } from 'react';
2
- import {
3
- View,
4
- Text,
5
- StyleSheet,
6
- Pressable,
7
- TextInput,
8
- ActivityIndicator,
9
- ScrollView,
10
- } from 'react-native';
11
- import { C } from '../theme';
12
- import type { RowData } from '../theme';
13
-
14
- interface SqlPanelProps {
15
- selectedDB: string | null;
16
- runCustomQuery: (sql: string) => Promise<{ rows: RowData[]; columns: string[]; error?: string }>;
17
- }
18
-
19
- export function SqlPanel({ selectedDB, runCustomQuery }: SqlPanelProps) {
20
- const [sql, setSql] = useState('');
21
- const [queryRunning, setQueryRunning] = useState(false);
22
- const [queryResult, setQueryResult] = useState<{ rows: RowData[]; columns: string[] } | null>(null);
23
- const [queryError, setQueryError] = useState<string | null>(null);
24
-
25
- const handleRun = () => {
26
- if (!sql.trim() || queryRunning || !selectedDB) return;
27
- setQueryRunning(true);
28
- setQueryResult(null);
29
- setQueryError(null);
30
- runCustomQuery(sql.trim()).then(
31
- (result) => {
32
- setQueryRunning(false);
33
- if (result.error) {
34
- setQueryError(result.error);
35
- } else {
36
- setQueryResult({ rows: result.rows, columns: result.columns });
37
- }
38
- },
39
- (err: unknown) => {
40
- setQueryRunning(false);
41
- setQueryError(err instanceof Error ? err.message : String(err));
42
- },
43
- );
44
- };
45
-
46
- return (
47
- <View style={s.panel}>
48
- <View style={s.inputRow}>
49
- <TextInput
50
- style={s.sqlInput}
51
- value={sql}
52
- onChangeText={setSql}
53
- placeholder={'SELECT * FROM "table_name"'}
54
- placeholderTextColor={C.textMuted}
55
- multiline
56
- blurOnSubmit={false}
57
- />
58
- <View style={s.actions}>
59
- <Pressable
60
- style={[s.runBtn, (queryRunning || !sql.trim() || !selectedDB) && s.runBtnDisabled]}
61
- onPress={handleRun}
62
- disabled={queryRunning || !sql.trim() || !selectedDB}
63
- >
64
- {queryRunning
65
- ? <ActivityIndicator size="small" color="#0d1117" />
66
- : <Text style={s.runBtnText}>▶ Run</Text>
67
- }
68
- </Pressable>
69
- {(queryResult !== null || queryError !== null) && (
70
- <Pressable
71
- style={s.clearBtn}
72
- onPress={() => { setQueryResult(null); setQueryError(null); }}
73
- >
74
- <Text style={s.clearBtnText}>Clear</Text>
75
- </Pressable>
76
- )}
77
- </View>
78
- </View>
79
-
80
- {queryError !== null && (
81
- <View style={s.errorRow}>
82
- <Text style={s.errorIcon}>⚠</Text>
83
- <Text style={s.errorText}>{queryError}</Text>
84
- </View>
85
- )}
86
-
87
- {queryResult !== null && (
88
- <View style={s.resultArea}>
89
- <Text style={s.resultMeta}>
90
- {queryResult.rows.length === 0
91
- ? 'Query executed · 0 rows'
92
- : `${queryResult.rows.length} row${queryResult.rows.length !== 1 ? 's' : ''} · ${queryResult.columns.length} col${queryResult.columns.length !== 1 ? 's' : ''}`}
93
- </Text>
94
- {queryResult.rows.length > 0 && (
95
- <ScrollView horizontal showsHorizontalScrollIndicator style={s.resultScroll}>
96
- <View>
97
- <View style={s.miniHeaderRow}>
98
- {queryResult.columns.map((col) => (
99
- <Text key={col} style={s.miniHeaderCell} numberOfLines={1}>{col}</Text>
100
- ))}
101
- </View>
102
- {queryResult.rows.slice(0, 200).map((row, i) => (
103
- <View key={i} style={[s.miniRow, i % 2 === 0 ? s.miniRowEven : s.miniRowOdd]}>
104
- {queryResult.columns.map((col) => {
105
- const val = row[col];
106
- return (
107
- <Text key={col} style={[s.miniCell, val == null && s.miniCellNull]} numberOfLines={1}>
108
- {val == null ? 'NULL' : String(val)}
109
- </Text>
110
- );
111
- })}
112
- </View>
113
- ))}
114
- </View>
115
- </ScrollView>
116
- )}
117
- </View>
118
- )}
119
- </View>
120
- );
121
- }
122
-
123
- const s = StyleSheet.create({
124
- panel: {
125
- backgroundColor: C.surface,
126
- borderBottomWidth: 1,
127
- borderBottomColor: C.border,
128
- paddingHorizontal: 20,
129
- paddingTop: 12,
130
- paddingBottom: 14,
131
- gap: 10,
132
- },
133
- inputRow: { flexDirection: 'row', gap: 10, alignItems: 'flex-start' },
134
- sqlInput: {
135
- flex: 1,
136
- backgroundColor: C.bg,
137
- borderWidth: 1,
138
- borderColor: C.border,
139
- borderRadius: 8,
140
- paddingHorizontal: 12,
141
- paddingVertical: 10,
142
- fontSize: 13,
143
- color: C.text,
144
- fontFamily: 'monospace',
145
- outlineStyle: 'none' as any,
146
- minHeight: 64,
147
- textAlignVertical: 'top',
148
- lineHeight: 20,
149
- },
150
- actions: { gap: 6 },
151
- runBtn: {
152
- height: 34,
153
- paddingHorizontal: 14,
154
- borderRadius: 7,
155
- backgroundColor: C.accent,
156
- alignItems: 'center',
157
- justifyContent: 'center',
158
- cursor: 'pointer',
159
- minWidth: 72,
160
- },
161
- runBtnDisabled: { opacity: 0.4 },
162
- runBtnText: { fontSize: 12, fontWeight: '700', color: '#0d1117' },
163
- clearBtn: {
164
- height: 28,
165
- paddingHorizontal: 10,
166
- borderRadius: 6,
167
- backgroundColor: C.surface2,
168
- borderWidth: 1,
169
- borderColor: C.border,
170
- alignItems: 'center',
171
- justifyContent: 'center',
172
- cursor: 'pointer',
173
- },
174
- clearBtnText: { fontSize: 11, color: C.textSecondary },
175
- errorRow: {
176
- flexDirection: 'row',
177
- alignItems: 'flex-start',
178
- gap: 8,
179
- backgroundColor: 'rgba(248,81,73,0.08)',
180
- borderWidth: 1,
181
- borderColor: 'rgba(248,81,73,0.25)',
182
- borderRadius: 7,
183
- padding: 10,
184
- },
185
- errorIcon: { fontSize: 13, color: C.danger, marginTop: 1 },
186
- errorText: { flex: 1, fontSize: 12, color: C.danger, lineHeight: 18, fontFamily: 'monospace' },
187
- resultArea: { gap: 6 },
188
- resultMeta: { fontSize: 11, color: C.textMuted },
189
- resultScroll: { maxHeight: 240, borderRadius: 7, borderWidth: 1, borderColor: C.border },
190
- miniHeaderRow: {
191
- flexDirection: 'row',
192
- backgroundColor: C.surface2,
193
- borderBottomWidth: 1,
194
- borderBottomColor: C.border,
195
- },
196
- miniHeaderCell: {
197
- width: 130,
198
- paddingHorizontal: 10,
199
- paddingVertical: 6,
200
- fontSize: 10,
201
- fontWeight: '700',
202
- color: C.textSecondary,
203
- textTransform: 'uppercase',
204
- borderRightWidth: 1,
205
- borderRightColor: C.borderSubtle,
206
- },
207
- miniRow: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: C.borderSubtle },
208
- miniRowEven: { backgroundColor: C.bg },
209
- miniRowOdd: { backgroundColor: C.surface },
210
- miniCell: {
211
- width: 130,
212
- paddingHorizontal: 10,
213
- paddingVertical: 7,
214
- fontSize: 12,
215
- color: C.text,
216
- borderRightWidth: 1,
217
- borderRightColor: C.borderSubtle,
218
- },
219
- miniCellNull: { color: C.textMuted, fontStyle: 'italic' },
220
- });
@@ -1,168 +0,0 @@
1
- import React from 'react';
2
- import { View, Text, StyleSheet, Pressable } from 'react-native';
3
- import { C } from '../theme';
4
- import { Dropdown } from './Dropdown';
5
-
6
- interface ToolbarProps {
7
- databases: string[];
8
- selectedDB: string | null;
9
- tables: string[];
10
- selectedTable: string | null;
11
- loadingTables: boolean;
12
- loadingData: boolean;
13
- rowCount: number;
14
- columnCount: number;
15
- onSelectDB: (db: string) => void;
16
- onSelectTable: (table: string) => void;
17
- onRefresh: () => void;
18
- sqlOpen: boolean;
19
- onToggleSql: () => void;
20
- }
21
-
22
- export function Toolbar({
23
- databases,
24
- selectedDB,
25
- tables,
26
- selectedTable,
27
- loadingTables,
28
- loadingData,
29
- rowCount,
30
- columnCount,
31
- onSelectDB,
32
- onSelectTable,
33
- onRefresh,
34
- sqlOpen,
35
- onToggleSql,
36
- }: ToolbarProps) {
37
- return (
38
- <View style={s.toolbar}>
39
- <View style={s.topRow}>
40
- <View style={s.selectors}>
41
- <Dropdown
42
- label="Database"
43
- value={selectedDB}
44
- options={databases}
45
- onSelect={onSelectDB}
46
- loading={loadingTables && !selectedTable}
47
- zIndex={20}
48
- />
49
- <View style={s.divider} />
50
- <Dropdown
51
- label="Table"
52
- value={selectedTable}
53
- options={tables}
54
- onSelect={onSelectTable}
55
- loading={loadingTables}
56
- disabled={!selectedDB}
57
- zIndex={10}
58
- />
59
- <View style={s.divider} />
60
- <Pressable
61
- style={({ pressed }) => [s.refreshBtn, pressed && s.refreshBtnPressed]}
62
- onPress={onRefresh}
63
- disabled={!selectedDB}
64
- >
65
- <Text style={[s.refreshIcon, !selectedDB && s.refreshIconDisabled]}>↻</Text>
66
- </Pressable>
67
- <View style={s.divider} />
68
- <Pressable
69
- style={({ pressed }) => [s.sqlBtn, sqlOpen && s.sqlBtnActive, !selectedDB && s.sqlBtnDisabled, pressed && { opacity: 0.8 }]}
70
- onPress={onToggleSql}
71
- disabled={!selectedDB}
72
- >
73
- <Text style={[s.sqlBtnText, sqlOpen && s.sqlBtnTextActive]}>›_ SQL</Text>
74
- </Pressable>
75
- </View>
76
-
77
- <View style={s.badges}>
78
- {selectedTable && !loadingData && (
79
- <View style={s.badge}>
80
- <Text style={s.badgeText}>{rowCount} rows</Text>
81
- </View>
82
- )}
83
- {columnCount > 0 && !loadingData && (
84
- <View style={[s.badge, s.badgeMuted]}>
85
- <Text style={s.badgeText}>{columnCount} cols</Text>
86
- </View>
87
- )}
88
- </View>
89
- </View>
90
- </View>
91
- );
92
- }
93
-
94
- const s = StyleSheet.create({
95
- toolbar: {
96
- flexDirection: 'column',
97
- paddingHorizontal: 20,
98
- paddingTop: 16,
99
- backgroundColor: C.surface,
100
- borderBottomWidth: 1,
101
- borderBottomColor: C.border,
102
- zIndex: 50,
103
- overflow: 'visible',
104
- },
105
- topRow: {
106
- flexDirection: 'row',
107
- alignItems: 'flex-end',
108
- paddingBottom: 14,
109
- gap: 20,
110
- },
111
- brand: {
112
- flexDirection: 'row',
113
- alignItems: 'center',
114
- gap: 8,
115
- paddingBottom: 2,
116
- marginRight: 4,
117
- },
118
- brandMark: { fontSize: 18, color: C.accent },
119
- brandName: { fontSize: 14, fontWeight: '700', color: C.text, letterSpacing: 0.2 },
120
- selectors: {
121
- flexDirection: 'row',
122
- alignItems: 'flex-end',
123
- flex: 1,
124
- overflow: 'visible',
125
- },
126
- divider: { width: 12 },
127
- refreshBtn: {
128
- width: 32,
129
- height: 32,
130
- borderRadius: 7,
131
- backgroundColor: C.surface2,
132
- borderWidth: 1,
133
- borderColor: C.border,
134
- alignItems: 'center',
135
- justifyContent: 'center',
136
- marginBottom: 1,
137
- cursor: 'pointer',
138
- },
139
- refreshBtnPressed: { backgroundColor: C.accentSubtle, borderColor: C.accent },
140
- refreshIcon: { fontSize: 16, color: C.textSecondary, lineHeight: 18 },
141
- refreshIconDisabled: { color: C.textMuted, opacity: 0.4 },
142
- badges: { flexDirection: 'row', alignItems: 'center', gap: 8, paddingBottom: 3 },
143
- badge: {
144
- paddingHorizontal: 10,
145
- paddingVertical: 4,
146
- backgroundColor: C.accentSubtle,
147
- borderRadius: 20,
148
- },
149
- badgeMuted: { backgroundColor: C.surface2 },
150
- badgeText: { fontSize: 11, fontWeight: '600', color: C.textSecondary },
151
- sqlBtn: {
152
- height: 32,
153
- paddingHorizontal: 10,
154
- borderRadius: 7,
155
- backgroundColor: C.surface2,
156
- borderWidth: 1,
157
- borderColor: C.border,
158
- alignItems: 'center',
159
- justifyContent: 'center',
160
- marginBottom: 1,
161
- cursor: 'pointer',
162
- marginLeft: 40
163
- },
164
- sqlBtnActive: { backgroundColor: C.accentSubtle, borderColor: C.accent },
165
- sqlBtnDisabled: { opacity: 0.4 },
166
- sqlBtnText: { fontSize: 12, fontWeight: '700', color: C.textSecondary, fontFamily: 'monospace' },
167
- sqlBtnTextActive: { color: C.accent },
168
- });
package/src/constants.ts DELETED
@@ -1,13 +0,0 @@
1
- export const PLUGIN_ID = 'rozenite-sqlite' as const;
2
-
3
- export const EVENTS = {
4
- GET_DB_LIST: 'get-db-list',
5
- SEND_DB_LIST: 'send-db-list',
6
- SQL_EXECUTE: 'sql-execute',
7
- SQL_EXEC_RESULT: 'sql-exec-result',
8
- } as const;
9
-
10
- export const QUERIES = {
11
- LIST_TABLES:
12
- "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name",
13
- } as const;
@@ -1,146 +0,0 @@
1
- import { useEffect, useRef, useCallback } from 'react';
2
- import { useRozeniteDevToolsClient } from '@rozenite/plugin-bridge';
3
- import { EVENTS, PLUGIN_ID, QUERIES } from '../constants';
4
- import type { RowData } from '../theme';
5
- import type { Action } from './useExplorerState';
6
-
7
- type BridgeClient = ReturnType<typeof useRozeniteDevToolsClient>;
8
-
9
- function fetchTables(client: NonNullable<BridgeClient>, dbName: string) {
10
- client.send(EVENTS.SQL_EXECUTE, { dbName, query: QUERIES.LIST_TABLES });
11
- }
12
-
13
- function fetchData(client: NonNullable<BridgeClient>, dbName: string, tableName: string) {
14
- client.send(EVENTS.SQL_EXECUTE, { dbName, query: `SELECT * FROM "${tableName}"` });
15
- }
16
-
17
- export function useBridgeSync(
18
- dispatch: React.Dispatch<Action>,
19
- selectedDB: string | null,
20
- selectedTable: string | null,
21
- ) {
22
- const client = useRozeniteDevToolsClient({ pluginId: PLUGIN_ID });
23
- const pendingRef = useRef<'tables' | 'data' | 'clear' | 'query' | null>(null);
24
- const selectedDBRef = useRef(selectedDB);
25
- const selectedTableRef = useRef(selectedTable);
26
- const customQueryResolverRef = useRef<((r: { rows: RowData[]; columns: string[]; error?: string }) => void) | null>(null);
27
- useEffect(() => { selectedDBRef.current = selectedDB; }, [selectedDB]);
28
- useEffect(() => { selectedTableRef.current = selectedTable; }, [selectedTable]);
29
-
30
- useEffect(() => {
31
- if (!client) return;
32
- // Reset state fully so the DB→tables→data cascade re-runs cleanly.
33
- // This prevents the race where effects 2 & 3 both fire on reconnect/hot-reload.
34
- dispatch({ type: 'RESET' });
35
- client.send(EVENTS.GET_DB_LIST, true);
36
-
37
- const sub1 = client.onMessage(EVENTS.SEND_DB_LIST, (payload: unknown) => {
38
- const databases = Array.isArray(payload) ? (payload as string[]) : [];
39
- dispatch({ type: 'SET_DATABASES', databases });
40
- });
41
-
42
- const sub2 = client.onMessage(EVENTS.SQL_EXEC_RESULT, (payload: unknown) => {
43
- const mode = pendingRef.current;
44
- const isError = payload !== null && typeof payload === 'object' && 'error' in payload;
45
- const errorMsg = isError ? String((payload as { error: unknown }).error) : null;
46
-
47
- if (mode === 'query') {
48
- const resolver = customQueryResolverRef.current;
49
- customQueryResolverRef.current = null;
50
- pendingRef.current = null;
51
- resolver?.(
52
- isError
53
- ? { rows: [], columns: [], error: errorMsg! }
54
- : (() => {
55
- const rows: RowData[] = Array.isArray(payload) ? (payload as RowData[]) : [];
56
- return { rows, columns: rows.length > 0 ? Object.keys(rows[0]) : [] };
57
- })(),
58
- );
59
- return;
60
- }
61
-
62
- if (isError) {
63
- dispatch({ type: 'SET_ERROR', error: errorMsg! });
64
- return;
65
- }
66
-
67
- if (mode === 'tables') {
68
- const tables = Array.isArray(payload)
69
- ? (payload as Array<{ name: string }>).map((r) => r.name)
70
- : [];
71
- dispatch({ type: 'SET_TABLES', tables });
72
- } else if (mode === 'data') {
73
- const rows: RowData[] = Array.isArray(payload) ? (payload as RowData[]) : [];
74
- dispatch({ type: 'SET_DATA', rows, columns: rows.length > 0 ? Object.keys(rows[0]) : [] });
75
- } else if (mode === 'clear') {
76
- const db = selectedDBRef.current;
77
- const table = selectedTableRef.current;
78
- if (db && table) {
79
- pendingRef.current = 'data';
80
- dispatch({ type: 'LOAD_DATA_START' });
81
- fetchData(client, db, table);
82
- }
83
- }
84
- });
85
-
86
- return () => {
87
- sub1.remove();
88
- sub2.remove();
89
- };
90
- }, [client, dispatch]);
91
-
92
- // No `client` in deps — driven purely by state changes from the cascade above.
93
- // This prevents effects 2 & 3 from firing simultaneously when client reconnects.
94
- useEffect(() => {
95
- if (!client || !selectedDB) return;
96
- pendingRef.current = 'tables';
97
- dispatch({ type: 'LOAD_TABLES_START' });
98
- fetchTables(client, selectedDB);
99
- // eslint-disable-next-line react-hooks/exhaustive-deps
100
- }, [selectedDB, dispatch]);
101
-
102
- useEffect(() => {
103
- if (!client || !selectedDB || !selectedTable) return;
104
- pendingRef.current = 'data';
105
- dispatch({ type: 'LOAD_DATA_START' });
106
- fetchData(client, selectedDB, selectedTable);
107
- // eslint-disable-next-line react-hooks/exhaustive-deps
108
- }, [selectedDB, selectedTable, dispatch]);
109
-
110
- const refresh = useCallback(() => {
111
- if (!client) return;
112
- if (selectedDB && selectedTable) {
113
- pendingRef.current = 'data';
114
- dispatch({ type: 'LOAD_DATA_START' });
115
- fetchData(client, selectedDB, selectedTable);
116
- } else if (selectedDB) {
117
- pendingRef.current = 'tables';
118
- dispatch({ type: 'LOAD_TABLES_START' });
119
- fetchTables(client, selectedDB);
120
- }
121
- }, [client, selectedDB, selectedTable, dispatch]);
122
-
123
- const clearTable = useCallback(() => {
124
- if (!client || !selectedDB || !selectedTable) return;
125
- pendingRef.current = 'clear';
126
- dispatch({ type: 'LOAD_DATA_START' });
127
- client.send(EVENTS.SQL_EXECUTE, { dbName: selectedDB, query: `DELETE FROM "${selectedTable}"` });
128
- }, [client, selectedDB, selectedTable, dispatch]);
129
-
130
- const runCustomQuery = useCallback(
131
- (sql: string): Promise<{ rows: RowData[]; columns: string[]; error?: string }> =>
132
- new Promise((resolve) => {
133
- const db = selectedDBRef.current;
134
- if (!client || !db) {
135
- resolve({ rows: [], columns: [], error: 'No database selected' });
136
- return;
137
- }
138
- pendingRef.current = 'query';
139
- customQueryResolverRef.current = resolve;
140
- client.send(EVENTS.SQL_EXECUTE, { dbName: db, query: sql });
141
- }),
142
- [client],
143
- );
144
-
145
- return { refresh, clearTable, runCustomQuery };
146
- }
@@ -1,160 +0,0 @@
1
- import { useReducer, useCallback } from 'react';
2
- import type { RowData } from '../theme';
3
- import { useBridgeSync } from './useBridgeSync';
4
-
5
- export type ExplorerStatus = 'connecting' | 'idle' | 'loadingTables' | 'loadingData' | 'error';
6
-
7
- export interface ExplorerState {
8
- databases: string[];
9
- selectedDB: string | null;
10
- tables: string[];
11
- selectedTable: string | null;
12
- rows: RowData[];
13
- columns: string[];
14
- selectedRowIndex: number | null;
15
- status: ExplorerStatus;
16
- error: string | null;
17
- }
18
-
19
- export type Action =
20
- | { type: 'RESET' }
21
- | { type: 'SELECT_DB'; db: string }
22
- | { type: 'SELECT_TABLE'; table: string }
23
- | { type: 'SET_DATABASES'; databases: string[] }
24
- | { type: 'LOAD_TABLES_START' }
25
- | { type: 'SET_TABLES'; tables: string[] }
26
- | { type: 'LOAD_DATA_START' }
27
- | { type: 'SET_DATA'; rows: RowData[]; columns: string[] }
28
- | { type: 'SET_ERROR'; error: string }
29
- | { type: 'SELECT_ROW'; index: number }
30
- | { type: 'CLOSE_ROW' }
31
- | { type: 'SAVE_ROW'; updated: RowData }
32
- | { type: 'DELETE_ROW' };
33
-
34
- const initial: ExplorerState = {
35
- databases: [],
36
- selectedDB: null,
37
- tables: [],
38
- selectedTable: null,
39
- rows: [],
40
- columns: [],
41
- selectedRowIndex: null,
42
- status: 'connecting',
43
- error: null,
44
- };
45
-
46
- function reducer(state: ExplorerState, action: Action): ExplorerState {
47
- switch (action.type) {
48
- case 'RESET':
49
- return { ...initial };
50
-
51
- case 'SELECT_DB':
52
- return {
53
- ...state,
54
- selectedDB: action.db,
55
- tables: [],
56
- selectedTable: null,
57
- rows: [],
58
- columns: [],
59
- selectedRowIndex: null,
60
- error: null,
61
- };
62
-
63
- case 'SELECT_TABLE':
64
- return { ...state, selectedTable: action.table, selectedRowIndex: null, error: null };
65
-
66
- case 'SET_DATABASES':
67
- return {
68
- ...state,
69
- status: 'idle',
70
- databases: action.databases,
71
- selectedDB: action.databases[0] ?? null,
72
- error: null,
73
- };
74
-
75
- case 'LOAD_TABLES_START':
76
- return {
77
- ...state,
78
- status: 'loadingTables',
79
- tables: [],
80
- selectedTable: null,
81
- rows: [],
82
- columns: [],
83
- error: null,
84
- };
85
-
86
- case 'SET_TABLES': {
87
- const { tables } = action;
88
- const selectedTable = tables.includes(state.selectedTable ?? '')
89
- ? state.selectedTable
90
- : (tables[0] ?? null);
91
- return { ...state, status: 'idle', tables, selectedTable };
92
- }
93
-
94
- case 'LOAD_DATA_START':
95
- return {
96
- ...state,
97
- status: 'loadingData',
98
- rows: [],
99
- columns: [],
100
- selectedRowIndex: null,
101
- error: null,
102
- };
103
-
104
- case 'SET_DATA':
105
- return {
106
- ...state,
107
- status: 'idle',
108
- rows: action.rows,
109
- columns: action.columns,
110
- selectedRowIndex: null,
111
- };
112
-
113
- case 'SET_ERROR':
114
- return { ...state, status: 'error', error: action.error };
115
-
116
- case 'SELECT_ROW':
117
- return {
118
- ...state,
119
- selectedRowIndex: state.selectedRowIndex === action.index ? null : action.index,
120
- };
121
-
122
- case 'CLOSE_ROW':
123
- return { ...state, selectedRowIndex: null };
124
-
125
- case 'SAVE_ROW':
126
- if (state.selectedRowIndex === null) return state;
127
- return {
128
- ...state,
129
- rows: state.rows.map((r, i) => (i === state.selectedRowIndex ? action.updated : r)),
130
- };
131
-
132
- case 'DELETE_ROW':
133
- if (state.selectedRowIndex === null) return state;
134
- return {
135
- ...state,
136
- rows: state.rows.filter((_, i) => i !== state.selectedRowIndex),
137
- selectedRowIndex: null,
138
- };
139
-
140
- default:
141
- return state;
142
- }
143
- }
144
-
145
- export function useExplorerState() {
146
- const [state, dispatch] = useReducer(reducer, initial);
147
- const { selectedDB, selectedTable } = state;
148
-
149
- const { refresh, clearTable, runCustomQuery } = useBridgeSync(dispatch, selectedDB, selectedTable);
150
-
151
- const selectDB = useCallback((db: string) => dispatch({ type: 'SELECT_DB', db }), []);
152
- const selectTable = useCallback((table: string) => dispatch({ type: 'SELECT_TABLE', table }), []);
153
- const selectRow = useCallback((index: number) => dispatch({ type: 'SELECT_ROW', index }), []);
154
- const closeRow = useCallback(() => dispatch({ type: 'CLOSE_ROW' }), []);
155
- const saveRow = useCallback((updated: RowData) => dispatch({ type: 'SAVE_ROW', updated }), []);
156
- const deleteRow = useCallback(() => dispatch({ type: 'DELETE_ROW' }), []);
157
-
158
- return { state, selectDB, selectTable, selectRow, closeRow, saveRow, deleteRow, refresh, clearTable, runCustomQuery };
159
- }
160
-