new isar inspector
MODIFY
packages/isar_inspector/lib/common.dart
packages/isar_inspector/lib/common.dart
@@ -7,6 +7,7 @@ class IsarCard extends StatelessWidget {
this.color,
this.onTap,
this.onLongPress,
+ this.padding = EdgeInsets.zero,
BorderRadius? radius,
}) : radius = radius ?? BorderRadius.circular(20);
final Widget child;
@@ -14,6 +15,7 @@ class IsarCard extends StatelessWidget {
final VoidCallback? onTap;
final VoidCallback? onLongPress;
final BorderRadius radius;
+ final EdgeInsets padding;
@override
Widget build(BuildContext context) {
@@ -25,11 +27,14 @@ class IsarCard extends StatelessWidget {
),
clipBehavior: Clip.antiAlias,
elevation: 0,
+ child: Padding(
+ padding: padding,
child: InkWell(
onTap: onTap,
onLongPress: onLongPress,
child: child,
),
+ ),
);
}
}

MODIFY
packages/isar_inspector/lib/connected_layout.dart
packages/isar_inspector/lib/connected_layout.dart
@@ -37,9 +37,9 @@ class _ConnectedLayoutState extends ConsumerState<ConnectedLayout> {
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
- children: const [
- FilterField(),
- SizedBox(height: 25),
+ children: [
+ const FilterField(),
+ const SizedBox(height: 25),
Expanded(
child: QueryTable(),
),

MODIFY
packages/isar_inspector/lib/query_table.dart
packages/isar_inspector/lib/query_table.dart
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:font_awesome_flutter/font_awesome_flutter.dart';
+import 'package:flutter_treeview/flutter_treeview.dart';
import 'package:isar/isar.dart';
import 'package:isar_inspector/common.dart';
@@ -11,10 +12,15 @@ import 'package:isar_inspector/state/instances_state.dart';
import 'package:isar_inspector/state/isar_connect_state_notifier.dart';
import 'package:isar_inspector/state/query_state.dart';
-const double _deleteColWidth = 60;
+const _stringColor = Color(0xFF6A8759);
+const _numberColor = Color(0xFF6897BB);
+const _boolColor = Color(0xFFCC7832);
+const _disableColor = Colors.grey;
class QueryTable extends ConsumerWidget {
- const QueryTable({super.key});
+ QueryTable({super.key});
+
+ final _controller = ScrollController();
@override
Widget build(BuildContext context, WidgetRef ref) {
@@ -25,35 +31,60 @@ class QueryTable extends ConsumerWidget {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Expanded(
- child: SingleChildScrollView(
- scrollDirection: Axis.horizontal,
- child: Column(
- children: [
- IsarCard(
- child: Row(
- mainAxisSize: MainAxisSize.min,
+ child: RawScrollbar(
+ controller: _controller,
+ thumbColor: Colors.grey,
+ child: ListView.separated(
+ controller: _controller,
+ itemCount: objects.length,
+ itemBuilder: (context, index) {
+ return Stack(
children: [
- for (var property in collection.allProperties)
- HeaderProperty(property: property),
- const SizedBox(width: _deleteColWidth),
- ],
+ TableBlock(
+ key: Key(objects[index].hashCode.toString()),
+ collection: collection,
+ object: objects[index],
),
+ Align(
+ alignment: Alignment.topLeft,
+ child: Padding(
+ padding: const EdgeInsets.only(top: 10, right: 10),
+ child: Tooltip(
+ message: 'Delete object',
+ child: IconButton(
+ icon: const Icon(Icons.delete),
+ splashRadius: 25,
+ onPressed: () {
+ final collection =
+ ref.read(selectedCollectionPod).valueOrNull;
+ if (collection == null) {
+ return;
+ }
+
+ final query = ConnectQuery(
+ instance: ref.read(selectedInstancePod).value!,
+ collection: collection.name,
+ filter: FilterCondition.equalTo(
+ property: collection.idName,
+ value: objects[index].getValue(
+ collection.idName,
),
- Expanded(
- child: SingleChildScrollView(
- child: Column(
- children: [
- for (int i = 0; i < objects.length; i++)
- TableRow(
- collection: collection,
- object: objects[i],
- index: i,
),
- ],
+ );
+ ref
+ .read(isarConnectPod.notifier)
+ .removeQuery(query);
+ },
+ ),
),
),
),
],
+ );
+ },
+ separatorBuilder: (BuildContext context, int index) {
+ return const SizedBox(height: 15);
+ },
),
),
),
@@ -64,147 +95,246 @@ class QueryTable extends ConsumerWidget {
}
}
-class HeaderProperty extends ConsumerWidget {
- const HeaderProperty({super.key, required this.property});
- final IProperty property;
+class TableBlock extends StatefulWidget {
+ const TableBlock({
+ super.key,
+ required this.collection,
+ required this.object,
+ });
+
+ final ICollection collection;
+ final QueryObject object;
@override
- Widget build(BuildContext context, WidgetRef ref) {
- final theme = Theme.of(context);
- final sortProperty = ref.watch(querySortPod);
+ State<TableBlock> createState() => _TableBlockState();
+}
- return IsarCard(
- onTap: () {
- SortProperty? newSortProperty;
- if (sortProperty?.property == property.name) {
- if (sortProperty!.sort == Sort.asc) {
- newSortProperty = SortProperty(
- property: property.name,
- sort: Sort.desc,
+class _TableBlockState extends State<TableBlock> {
+ late TreeViewController _treeViewController = TreeViewController(
+ children: widget.collection.allProperties.map((property) {
+ final children = <Node<TreeViewHelper>>[];
+ final rawValue = widget.object.getValue(property.name);
+ String value;
+ Color color;
+
+ switch (property.type) {
+ case IsarType.Bool:
+ value = rawValue.toString();
+ color = _boolColor;
+ break;
+
+ case IsarType.Int:
+ case IsarType.Float:
+ case IsarType.Long:
+ case IsarType.Double:
+ value = rawValue.toString();
+ color = _numberColor;
+ break;
+
+ case IsarType.String:
+ value = '"$rawValue"';
+ color = _stringColor;
+ break;
+
+ case IsarType.ByteList:
+ case IsarType.IntList:
+ case IsarType.FloatList:
+ case IsarType.BoolList:
+ case IsarType.LongList:
+ case IsarType.DoubleList:
+ case IsarType.StringList:
+ final list = rawValue as List<dynamic>;
+
+ value = '${property.type.toString().replaceAll('IsarType.', '')}'
+ ' [${list.length}]';
+ color = _disableColor;
+
+ for (var index = 0; index < list.length; index++) {
+ children.add(
+ Node(
+ key: '${property.name}_$index',
+ label: '',
+ data: TreeViewHelper(
+ name: index.toString(),
+ value: property.type == IsarType.StringList
+ ? '"${list[index]}"'
+ : list[index].toString(),
+ rawValue: list[index],
+ valueColor: property.type == IsarType.StringList
+ ? _stringColor
+ : property.type == IsarType.BoolList
+ ? _boolColor
+ : _numberColor,
+ ),
+ ),
);
}
- } else if (property.type.sortable) {
- newSortProperty = SortProperty(
- property: property.name,
- sort: sortProperty == null && property.isId ? Sort.desc : Sort.asc,
- );
+ break;
}
- ref.read(querySortPod.state).state = newSortProperty;
- },
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 15),
- child: SizedBox(
- width: property.type.width,
- child: Row(
- children: [
- Expanded(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- property.name,
- style: const TextStyle(
- fontWeight: FontWeight.bold,
- fontSize: 16,
- ),
+
+ return Node<TreeViewHelper>(
+ key: property.name,
+ label: '',
+ children: children,
+ data: TreeViewHelper(
+ name: property.name,
+ value: value,
+ rawValue: rawValue,
+ valueColor: color,
),
- const SizedBox(height: 5),
- Text(
- property.isId ? 'Id' : property.type.name,
- style: TextStyle(
- color: theme.primaryColor,
- fontSize: 12,
+ );
+ }).toList(),
+ );
+
+ @override
+ Widget build(BuildContext context) {
+ return IsarCard(
+ color: const Color(0xFF1F2128),
+ padding: const EdgeInsets.fromLTRB(25, 15, 15, 15),
+ radius: BorderRadius.circular(5),
+ child: TreeView(
+ theme: const TreeViewTheme(
+ expanderTheme: ExpanderThemeData(color: Colors.white),
),
+ controller: _treeViewController,
+ shrinkWrap: true,
+ nodeBuilder: (context, node) {
+ final data = node.data as TreeViewHelper;
+
+ return Padding(
+ padding: const EdgeInsets.symmetric(vertical: 5),
+ child: TableItem(data: data),
+ );
+ },
+ onExpansionChanged: (key, expanded) {
+ _expanding(_treeViewController.getNode(key)!, expanded);
+ },
+ onNodeTap: (key) {
+ final node = _treeViewController.getNode<TreeViewHelper>(key)!;
+ _expanding(node, !node.expanded);
+ },
),
- ],
+ );
+ }
+
+ void _expanding(Node<TreeViewHelper> node, bool expanded) {
+ setState(() {
+ _treeViewController = _treeViewController.copyWith(
+ children: _treeViewController.updateNode(
+ node.key,
+ node.copyWith(expanded: expanded),
),
+ );
+ });
+ }
+}
+
+class TableItem extends StatefulWidget {
+ const TableItem({super.key, required this.data});
+
+ final TreeViewHelper data;
+
+ @override
+ State<TableItem> createState() => _TableItemState();
+}
+
+class _TableItemState extends State<TableItem> {
+ bool _entered = false;
+
+ @override
+ Widget build(BuildContext context) {
+ final richText = RichText(
+ overflow: TextOverflow.ellipsis,
+ text: TextSpan(
+ text: '${widget.data.name}: ',
+ style: const TextStyle(
+ fontSize: 15,
+ fontWeight: FontWeight.bold,
+ color: Colors.white,
),
- if (sortProperty?.property == property.name)
- Icon(
- sortProperty!.sort == Sort.asc
- ? FontAwesomeIcons.caretUp
- : FontAwesomeIcons.caretDown,
- size: 20,
+ children: [
+ TextSpan(
+ text: widget.data.value,
+ style: TextStyle(color: widget.data.valueColor),
)
- else if (sortProperty == null && property.isId)
- const Icon(
- FontAwesomeIcons.caretUp,
- size: 20,
- ),
],
),
- ),
- ),
);
- }
-}
-class TableRow extends ConsumerWidget {
- const TableRow({
- super.key,
- required this.collection,
- required this.index,
- required this.object,
+ return MouseRegion(
+ onEnter: (_) {
+ setState(() {
+ _entered = true;
});
- final ICollection collection;
- final int index;
- final QueryObject object;
-
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- return IsarCard(
- color: index.isEven ? Colors.transparent : null,
- radius: BorderRadius.circular(15),
- onTap: () {},
+ },
+ onExit: (e) {
+ setState(() {
+ _entered = false;
+ });
+ },
+ child: Padding(
+ padding: const EdgeInsets.symmetric(vertical: 5),
child: Row(
- mainAxisSize: MainAxisSize.min,
children: [
- for (IProperty property in collection.allProperties)
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 25, vertical: 15),
- child: SizedBox(
- width: property.type.width,
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- object.getValue(property.name),
- style: TextStyle(color: Colors.grey[500]),
- ),
- ],
+ Expanded(
+ child: widget.data.rawValue is String
+ ? Tooltip(
+ message: widget.data.value,
+ child: richText,
+ )
+ : richText,
),
+ if (_entered)
+ InkWell(
+ onTap: () async {
+ ScaffoldMessenger.of(context).removeCurrentSnackBar();
+
+ await Clipboard.setData(
+ ClipboardData(text: widget.data.rawValue.toString()),
+ );
+
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ duration: Duration(seconds: 1),
+ width: 200,
+ behavior: SnackBarBehavior.floating,
+ content: Text(
+ 'Copied To Clipboard',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Colors.white,
+ fontWeight: FontWeight.bold,
),
),
- SizedBox(
- width: _deleteColWidth,
- child: Center(
- child: IconButton(
- icon: const Icon(Icons.delete),
- splashRadius: 25,
- onPressed: () {
- final collection =
- ref.read(selectedCollectionPod).valueOrNull;
- if (collection == null) {
- return;
- }
-
- final query = ConnectQuery(
- instance: ref.read(selectedInstancePod).value!,
- collection: collection.name,
- filter: FilterCondition.equalTo(
- property: collection.idName,
- value: int.parse(object.getValue(collection.idName)),
),
);
- ref.read(isarConnectPod.notifier).removeQuery(query);
+ }
},
+ child: const Center(
+ child: Tooltip(
+ message: 'Copy to clipboard',
+ child: Icon(Icons.copy, size: 18),
),
),
),
],
),
+ ),
);
}
}
+
+class TreeViewHelper {
+ const TreeViewHelper({
+ required this.name,
+ required this.value,
+ required this.rawValue,
+ required this.valueColor,
+ });
+
+ final String name;
+ final String value;
+ final dynamic rawValue;
+ final Color valueColor;
+}

MODIFY
packages/isar_inspector/lib/schema.dart
packages/isar_inspector/lib/schema.dart
+import 'dart:core';
+
import 'package:json_annotation/json_annotation.dart';
part 'schema.g.dart';
@@ -58,23 +60,17 @@ class ILink {
// ignore_for_file: constant_identifier_names
enum IsarType {
- Bool(true, 80),
- Int(true, 80),
- Float(true, 80),
- Long(true, 80),
- Double(true, 80),
- String(true, 200),
- Bytes(false, 200),
- IntList(false, 200),
- FloatList(false, 200),
- LongList(false, 200),
- DoubleList(false, 200),
- StringList(false, 200);
-
- final bool sortable;
- final double width;
-
- // False warning
- // ignore: sort_constructors_first
- const IsarType(this.sortable, this.width);
+ Bool,
+ Int,
+ Float,
+ Long,
+ Double,
+ String,
+ ByteList,
+ IntList,
+ FloatList,
+ LongList,
+ DoubleList,
+ StringList,
+ BoolList;
}

MODIFY
packages/isar_inspector/lib/schema.g.dart
packages/isar_inspector/lib/schema.g.dart
@@ -44,12 +44,13 @@ const _$IsarTypeEnumMap = {
IsarType.Long: 'Long',
IsarType.Double: 'Double',
IsarType.String: 'String',
- IsarType.Bytes: 'Bytes',
+ IsarType.ByteList: 'ByteList',
IsarType.IntList: 'IntList',
IsarType.FloatList: 'FloatList',
IsarType.LongList: 'LongList',
IsarType.DoubleList: 'DoubleList',
IsarType.StringList: 'StringList',
+ IsarType.BoolList: 'BoolList',
};
ILink _$ILinkFromJson(Map<String, dynamic> json) => ILink(

MODIFY
packages/isar_inspector/lib/state/query_state.dart
packages/isar_inspector/lib/state/query_state.dart
@@ -13,9 +13,7 @@ class QueryObject {
const QueryObject(this.data);
final Map<String, dynamic> data;
- String getValue(String propertyName) {
- return data[propertyName]?.toString() ?? '';
- }
+ dynamic getValue(String propertyName) => data[propertyName];
}
final queryPagePod = StateProvider((ref) => 0);

MODIFY
packages/isar_inspector/pubspec.yaml
packages/isar_inspector/pubspec.yaml
@@ -11,6 +11,7 @@ dependencies:
sdk: flutter
flutter_riverpod: ^2.0.0-dev.7
flutter_svg: any
+ flutter_treeview: ^1.0.7+1
font_awesome_flutter: any
go_router: ^4.1.0
google_fonts: ^3.0.1

new isar inspector