Skip to content
Merged
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
85 changes: 84 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ After installing this library, you need to configure your React Native app for D

```typescript
import React from 'react';
import { DittoProvider, PeersList, DiskUsage, SystemSettings } from '@dittolive/ditto-react-native-tools';
import { DittoProvider, PeersList, DiskUsage, SystemSettings, QueryEditor } from '@dittolive/ditto-react-native-tools';
import { Ditto } from '@dittolive/ditto';

// Initialize your Ditto instance
Expand Down Expand Up @@ -221,6 +221,89 @@ The component includes a built-in search feature that:
/>
```

### QueryEditor

Execute DQL (Document Query Language) queries against your Ditto store with a comprehensive interface for query input, execution, and results display.

```typescript
import { QueryEditor } from '@dittolive/ditto-react-native-tools';

<QueryEditor
ditto={ditto}
style={{ flex: 1 }}
/>
```

**Props:**
- `ditto` (required): Your Ditto instance
- `style?: ViewStyle` - Custom styling for the main container

**Features:**
- **Multi-line Query Input**: 3-line TextInput with word wrap and scrollable content for complex DQL queries
- **Smart Execution**: Run button is disabled when query is empty, with loading state during execution
- **Results Display**: Supports both SELECT queries (data results) and mutating queries (INSERT/UPDATE/DELETE with affected count)
- **Performance Optimized**: Handles large result sets (20,000+ records) with FlatList virtualization
- **Expandable JSON View**: Click any result row to view full JSON details in expandable format
- **Export Functionality**: Share query results as formatted JSON using React Native's Share API
- **Cross-platform Compatible**: Works on both iOS and Android with native share dialogs

**Supported Query Types:**
- **SELECT queries**: Display results in a scrollable list with expandable JSON details
- **INSERT/UPDATE/DELETE**: Show affected record count and operation success status
- **SHOW statements**: Display system information and configuration

**Style Customization:**
The QueryEditor component consists of three sub-components, each accepting custom styles:

```typescript
interface QueryEditorStyles {
container?: ViewStyle; // Main container
editorView?: ViewStyle; // Query input section
headerView?: ViewStyle; // Buttons section
resultsView?: ViewStyle; // Results display section
}

<QueryEditor
ditto={ditto}
style={{
container: { flex: 1, backgroundColor: '#f8f9fa' },
editorView: { backgroundColor: 'white', padding: 16 },
headerView: { backgroundColor: '#e9ecef', borderBottomWidth: 1 },
resultsView: { flex: 1, backgroundColor: 'white' }
}}
/>
```

**Performance Features:**
- **Virtual Scrolling**: Uses FlatList with `getItemLayout` for optimal performance with large datasets
- **Memory Management**: Results are dematerialized to prevent memory leaks
- **Efficient Rendering**: Only visible rows are rendered, supporting 20,000+ records smoothly
- **Smart Loading States**: Execution and sharing buttons show activity indicators during operations

**Usage Examples:**

```typescript
// Basic usage for query testing
<QueryEditor ditto={ditto} />

// With custom container styling
<QueryEditor
ditto={ditto}
style={{
flex: 1,
backgroundColor: '#f5f5f5',
margin: 10
}}
/>

// Example queries to try:
// SELECT * FROM myCollection
// INSERT INTO myCollection (name, value) VALUES ('test', 123)
// UPDATE myCollection SET value = 456 WHERE name = 'test'
// DELETE FROM myCollection WHERE name = 'test'
// SHOW ALL
```

## Example App

This repository includes a fully functional example app demonstrating all features. See the [example directory](./example) for setup instructions and implementation details.
Expand Down
5 changes: 3 additions & 2 deletions example/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,10 @@ const config = {
// Add custom resolver to handle the library module and ensure react-native consistency
resolveRequest: (context, moduleName, platform) => {
if (moduleName === '@dittolive/ditto-react-native-tools') {
// Resolve to the library's source directly
// Use the same entry point that external apps will use (built module)
// This ensures consistency between example app and external usage
return {
filePath: path.resolve(libraryPath, 'src/index.ts'),
filePath: path.resolve(libraryPath, 'lib/module/index.js'),
type: 'sourceFile',
};
}
Expand Down
80 changes: 58 additions & 22 deletions example/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion example/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@
"dependencies": {
"@dittolive/ditto": "4.11.4",
"@dittolive/ditto-react-native-tools": "file:..",
"@dr.pogodin/react-native-fs": "^2.35.1",
"@react-navigation/native": "^6.1.18",
"@react-navigation/stack": "^6.4.1",
"react": "18.3.1",
"react-native": "0.77.1",
"@dr.pogodin/react-native-fs": "^2.35.1",
"react-native-gesture-handler": "^2.28.0",
"react-native-safe-area-context": "^4.14.1",
"react-native-screens": "^3.37.0",
Expand Down
10 changes: 10 additions & 0 deletions example/src/navigation/AppNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SyncStatusScreen } from '../screens/SyncStatusScreen';
import { PermissionsScreen } from '../screens/PermissionsScreen';
import { DiskUsageScreen } from '../screens/DiskUsageScreen';
import { SystemSettingsScreen } from '../screens/SystemSettingsScreen';
import { QueryEditorScreen } from '../screens/QueryEditorScreen';

const Stack = createStackNavigator<RootStackParamList>();

Expand Down Expand Up @@ -89,6 +90,15 @@ export const AppNavigator: React.FC = () => {
headerBackTitle: 'Back',
}}
/>
<Stack.Screen
name="QueryEditor"
component={QueryEditorScreen}
options={{
title: 'Query Editor',
headerShown: true,
headerBackTitle: 'Back',
}}
/>
</Stack.Navigator>
</NavigationContainer>
);
Expand Down
22 changes: 8 additions & 14 deletions example/src/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ import { MenuListItem } from '../components/MenuListItem';
import { navigate } from '../services/NavigationService';
import { colors, typography, spacing } from '../styles';

// Simple icon components using Unicode symbols
const PeersIcon = () => <Text style={styles.icon}>📱</Text>;
const DiskIcon = () => <Text style={styles.icon}>💾</Text>;
const SettingsIcon = () => <Text style={styles.icon}>⚙️</Text>;

export const HomeScreen: React.FC = () => {
return (
<SafeAreaContainer>
Expand All @@ -22,8 +17,6 @@ export const HomeScreen: React.FC = () => {
<MenuSection title="Network">
<MenuListItem
title="Peers List"
iconColor={colors.networkIcon}
iconComponent={<PeersIcon />}
onPress={() => navigate('PeersList')}
isFirst
/>
Expand All @@ -32,19 +25,23 @@ export const HomeScreen: React.FC = () => {
<MenuSection title="System">
<MenuListItem
title="Disk Usage"
iconColor={colors.diskIcon}
iconComponent={<DiskIcon />}
onPress={() => navigate('DiskUsage')}
isFirst
/>
<MenuListItem
title="System Settings"
iconColor={colors.settingsIcon}
iconComponent={<SettingsIcon />}
onPress={() => navigate('SystemSettings')}
isLast
/>
</MenuSection>

<MenuSection title="Ditto Store">
<MenuListItem
title="Query Editor"
onPress={() => navigate('QueryEditor')}
isFirst
/>
</MenuSection>
</ScrollView>
</SafeAreaContainer>
);
Expand All @@ -63,7 +60,4 @@ const styles = StyleSheet.create({
fontWeight: typography.weights.bold,
color: colors.text,
},
icon: {
fontSize: 16,
},
});
47 changes: 47 additions & 0 deletions example/src/screens/QueryEditorScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { useContext } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import { SafeAreaContainer } from '../components/SafeAreaContainer';
import DittoContext from '../providers/DittoContext';
import { QueryEditor } from '@dittolive/ditto-react-native-tools';
import { colors, typography, spacing } from '../styles';

export const QueryEditorScreen: React.FC = () => {
const context = useContext(DittoContext);
if (!context) {
throw new Error('QueryEditorScreen must be used within a DittoProvider');
}
const { dittoService } = context;
const ditto = dittoService.getDitto();

if (!ditto) {
return (
<SafeAreaContainer>
<View style={styles.loadingState}>
<Text style={styles.loadingText}>Loading...</Text>
</View>
</SafeAreaContainer>
);
}

return (
<SafeAreaContainer>
<QueryEditor ditto={ditto} />
</SafeAreaContainer>
);
};

const styles = StyleSheet.create({
container: {
flex: 1,
},
loadingState: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
loadingText: {
fontSize: typography.sizes.body,
color: colors.textSecondary,
marginTop: spacing.md,
},
});
Loading