fixed operations on properties
added operations on link(s)
collapse children when collapsing parent
MODIFY
packages/isar/lib/src/isar_connect.dart
packages/isar/lib/src/isar_connect.dart
@@ -234,17 +234,11 @@ abstract class _IsarConnect {
final objects = await query.exportJson();
- if (objects.isEmpty || !objects[0].containsKey(cEdit.property)) {
+ if (objects.isEmpty ||
+ objects[0].setByPath(cEdit.path, cEdit.value) == null) {
throw IsarError('Cant get object or property is wrong for edit');
}
- if (cEdit.index == null) {
- objects[0][cEdit.property] = cEdit.value;
- } else {
- //ignore: avoid_dynamic_calls
- objects[0][cEdit.property][cEdit.index] = cEdit.value;
- }
-
await collection.isar.writeTxn(() async => collection.importJson(objects));
}
@@ -258,18 +252,17 @@ abstract class _IsarConnect {
);
final objects = await query.exportJson();
+ dynamic list;
if (objects.isEmpty ||
- !objects[0].containsKey(cEdit.property) ||
- objects[0][cEdit.property] is! List) {
+ ((list = objects[0].getByPath(cEdit.path)) == null) ||
+ list is! List) {
throw IsarError('Cant get object or property is wrong for add');
}
- if (cEdit.index == null) {
- (objects[0][cEdit.property] as List).add(cEdit.value);
- } else {
- (objects[0][cEdit.property] as List).insert(cEdit.index!, cEdit.value);
- }
+ cEdit.listIndex == null
+ ? list.add(cEdit.value)
+ : list.insert(cEdit.listIndex!, cEdit.value);
await collection.isar.writeTxn(() async => collection.importJson(objects));
}
@@ -284,14 +277,15 @@ abstract class _IsarConnect {
);
final objects = await query.exportJson();
+ dynamic list;
if (objects.isEmpty ||
- !objects[0].containsKey(cEdit.property) ||
- objects[0][cEdit.property] is! List) {
+ ((list = objects[0].getByPath(cEdit.path)) == null) ||
+ list is! List) {
throw IsarError('Cant get object or property is wrong for remove');
}
- (objects[0][cEdit.property] as List).removeAt(cEdit.index!);
+ list.removeAt(cEdit.listIndex!);
await collection.isar.writeTxn(() async => collection.importJson(objects));
}
@@ -331,3 +325,37 @@ abstract class _IsarConnect {
);
}
}
+
+extension _MapPath on Map<String, dynamic> {
+ Map<String, dynamic>? setByPath(String path, dynamic value) {
+ final keys = path.split('.');
+ dynamic subData = this;
+
+ try {
+ for (final key in keys.take(keys.length - 1)) {
+ //ignore: avoid_dynamic_calls
+ subData = subData[subData is List ? int.parse(key) : key];
+ }
+ //ignore: avoid_dynamic_calls
+ subData[subData is List ? int.parse(keys.last) : keys.last] = value;
+ } catch (_) {
+ return null;
+ }
+ return this;
+ }
+
+ dynamic getByPath(String path) {
+ final keys = path.split('.');
+ dynamic subData = this;
+
+ try {
+ for (final key in keys) {
+ //ignore: avoid_dynamic_calls
+ subData = subData[subData is List ? int.parse(key) : key];
+ }
+ return subData;
+ } catch (_) {
+ return null;
+ }
+ }
+}

MODIFY
packages/isar/lib/src/isar_connect_api.dart
packages/isar/lib/src/isar_connect_api.dart
@@ -147,8 +147,8 @@ class ConnectEdit {
required this.instance,
required this.collection,
required this.id,
- required this.property,
- this.index,
+ required this.path,
+ this.listIndex,
required this.value,
});
@@ -157,8 +157,8 @@ class ConnectEdit {
instance: json['instance'] as String,
collection: json['collection'] as String,
id: json['id'] as Id,
- index: json['index'] as int?,
- property: json['property'] as String,
+ path: json['path'] as String,
+ listIndex: json['listIndex'] as int?,
value: json['value'],
);
}
@@ -166,8 +166,8 @@ class ConnectEdit {
final String instance;
final String collection;
final Id id;
- final String property;
- final int? index;
+ final String path;
+ final int? listIndex;
final dynamic value;
Map<String, dynamic> toJson() {
@@ -175,11 +175,22 @@ class ConnectEdit {
'instance': instance,
'collection': collection,
'id': id,
- 'index': index,
- 'property': property,
+ 'path': path,
+ 'listIndex': listIndex,
'value': value,
};
}
+
+ ConnectEdit copyWith({String? instance, String? collection}) {
+ return ConnectEdit(
+ instance: instance ?? this.instance,
+ collection: collection ?? this.collection,
+ id: id,
+ path: path,
+ listIndex: listIndex,
+ value: value,
+ );
+ }
}
class ConnectCollectionInfo {

MODIFY
packages/isar_inspector/lib/query_table.dart
packages/isar_inspector/lib/query_table.dart
@@ -20,14 +20,7 @@ const _boolColor = Color(0xFFCC7832);
const _dateColor = Color(0xFFFFC66D);
const _disableColor = Colors.grey;
-typedef Editor = void Function(
- int id,
- String property,
- int? index,
- dynamic value,
- EditorType editing,
-);
-
+typedef Editor = void Function(ConnectEdit edit, EditorType type);
typedef Aggregate = Future<num?> Function(String property, AggregationOp op);
enum EditorType { add, edit, remove }
@@ -65,18 +58,15 @@ class QueryTable extends ConsumerWidget {
),
collection: collection,
object: objects[index],
- editor: (id, property, index, value, editing) {
- final edit = ConnectEdit(
- instance: ref.read(selectedInstancePod).value!,
- collection: collection.name,
- id: id,
- property: property,
- index: index,
- value: value,
+ editor: (ConnectEdit edit, type) {
+ edit = edit.copyWith(
+ instance: ref.read(selectedInstancePod).value,
+ collection:
+ edit.collection == '' ? collection.name : null,
);
final pod = ref.read(isarConnectPod.notifier);
- switch (editing) {
+ switch (type) {
case EditorType.add:
pod.addInList(edit);
break;
@@ -192,9 +182,11 @@ class _TableBlockState extends State<TableBlock> {
nodeBuilder: (context, node) {
return TableItem(
data: node.data as TreeViewHelper,
+ nodeKey: node.key,
editor: widget.editor,
objectId: _id,
aggregate: widget.aggregate,
+ treeViewController: _treeViewController,
);
},
onExpansionChanged: (key, expanded) {
@@ -213,7 +205,12 @@ class _TableBlockState extends State<TableBlock> {
_treeViewController = _treeViewController.copyWith(
children: _treeViewController.updateNode(
node.key,
- node.copyWith(expanded: expanded),
+ node.copyWith(
+ expanded: expanded,
+ children: !expanded
+ ? _treeViewController.collapseAll(parent: node)
+ : null,
+ ),
),
);
});
@@ -237,57 +234,67 @@ class _TableBlockState extends State<TableBlock> {
List<Node<TreeViewHelper>> _createProperties({
required List<IProperty> properties,
required Map<String, dynamic> data,
- String prefixKey = '',
- bool subLink = false,
+ String prefixPath = '',
+ String? realPrefixPath,
+ String linkKey = '',
List<IObject>? embeddedObjects,
}) {
embeddedObjects = embeddedObjects ?? widget.collection.objects;
+ prefixPath = prefixPath.isNotEmpty ? '$prefixPath.' : '';
+ realPrefixPath = realPrefixPath == null
+ ? prefixPath
+ : (realPrefixPath.isNotEmpty ? '$realPrefixPath.' : '');
+
return properties.map((property) {
+ final path = '$realPrefixPath${property.name}';
+ final key = '$prefixPath${property.name}';
final children = <Node<TreeViewHelper>>[];
final value = data[property.name];
if (property.type == IsarType.object) {
return Node<TreeViewHelper>(
- key: '$prefixKey${property.name}',
+ key: key,
label: '',
- expanded: _isExpanded('$prefixKey${property.name}'),
+ expanded: _isExpanded(key),
children: _createProperties(
properties: embeddedObjects!
.firstWhere((e) => e.name == property.target)
.properties,
data: value as Map<String, dynamic>,
- prefixKey: '${property.name}_',
- subLink: subLink,
+ linkKey: linkKey,
embeddedObjects: embeddedObjects,
+ prefixPath: path,
),
data: PropertyHelper(
property: property,
value: value,
- subLink: subLink,
+ linkKey: linkKey,
+ path: path,
),
);
}
if (property.type == IsarType.objectList) {
final list = value as List<dynamic>;
+
return Node<TreeViewHelper>(
- key: '$prefixKey${property.name}',
+ key: key,
label: '',
- expanded: _isExpanded('$prefixKey${property.name}'),
+ expanded: _isExpanded(key),
children: [
for (var index = 0; index < list.length; ++index)
Node<TreeViewHelper>(
- key: '$prefixKey${property.name}_$index',
+ key: '$key.$index',
label: '',
- expanded: _isExpanded('$prefixKey${property.name}_$index'),
+ expanded: _isExpanded('$key.$index'),
children: _createProperties(
properties: embeddedObjects!
.firstWhere((e) => e.name == property.target)
.properties,
data: list[index] as Map<String, dynamic>,
- prefixKey: '$prefixKey${property.name}_$index',
- subLink: subLink,
+ linkKey: linkKey,
embeddedObjects: embeddedObjects,
+ prefixPath: '$path.$index',
),
data: PropertyHelper(
property: IProperty(
@@ -296,15 +303,17 @@ class _TableBlockState extends State<TableBlock> {
target: property.target,
),
value: list[index],
- subLink: subLink,
+ linkKey: linkKey,
index: index,
+ path: '$path.$index',
),
)
],
data: PropertyHelper(
property: property,
value: list,
- subLink: subLink,
+ linkKey: linkKey,
+ path: path,
),
);
}
@@ -315,9 +324,9 @@ class _TableBlockState extends State<TableBlock> {
for (var index = 0; index < list.length; index++) {
children.add(
Node(
- key: '$prefixKey${property.name}_$index',
+ key: '$key.$index',
label: '',
- expanded: _isExpanded('$prefixKey${property.name}_$index'),
+ expanded: _isExpanded('$key.$index'),
data: PropertyHelper(
property: IProperty(
name: property.name,
@@ -325,7 +334,8 @@ class _TableBlockState extends State<TableBlock> {
),
value: list[index],
index: index,
- subLink: subLink,
+ linkKey: linkKey,
+ path: '$path.$index',
),
),
);
@@ -333,14 +343,15 @@ class _TableBlockState extends State<TableBlock> {
}
return Node<TreeViewHelper>(
- key: '$prefixKey${property.name}',
+ key: key,
label: '',
- expanded: _isExpanded('$prefixKey${property.name}'),
+ expanded: _isExpanded(key),
children: children,
data: PropertyHelper(
property: property,
value: value,
- subLink: subLink,
+ linkKey: linkKey,
+ path: path,
),
);
}).toList();
@@ -357,8 +368,9 @@ class _TableBlockState extends State<TableBlock> {
_createProperties(
properties: link.target.allProperties,
data: value as Map<String, dynamic>,
- prefixKey: '${link.name}_',
- subLink: true,
+ prefixPath: link.name,
+ realPrefixPath: '',
+ linkKey: link.name,
embeddedObjects: link.target.objects,
),
);
@@ -369,14 +381,15 @@ class _TableBlockState extends State<TableBlock> {
for (var index = 0; index < list.length; index++) {
children.add(
Node<TreeViewHelper>(
- key: '${link.name}_$index',
+ key: '${link.name}.$index',
label: '',
- expanded: _isExpanded('${link.name}_$index'),
+ expanded: _isExpanded('${link.name}.$index'),
children: _createProperties(
properties: link.target.allProperties,
data: list[index] as Map<String, dynamic>,
- prefixKey: '${link.name}_${index}_',
- subLink: true,
+ prefixPath: '${link.name}.$index',
+ realPrefixPath: '',
+ linkKey: '${link.name}.$index',
embeddedObjects: link.target.objects,
),
data: LinkHelper(
@@ -411,15 +424,19 @@ class TableItem extends StatefulWidget {
const TableItem({
super.key,
required this.data,
+ required this.nodeKey,
required this.editor,
required this.objectId,
required this.aggregate,
+ required this.treeViewController,
});
final TreeViewHelper data;
+ final String nodeKey;
final int objectId;
final Editor editor;
final Aggregate aggregate;
+ final TreeViewController treeViewController;
@override
State<TableItem> createState() => _TableItemState();
@@ -428,7 +445,7 @@ class TableItem extends StatefulWidget {
class _TableItemState extends State<TableItem> {
bool _entered = false;
- late final _options = <PopupMenuEntry<dynamic>>[
+ List<PopupMenuEntry<dynamic>> get _options => [
if (widget.data is LinkHelper && widget.data.value != null)
PopupMenuItem<dynamic>(
onTap: () => _copy(true),
@@ -445,9 +462,9 @@ class _TableItemState extends State<TableItem> {
),
),
if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
!(widget.data as PropertyHelper).property.isId &&
- !(widget.data as PropertyHelper).property.type.isList)
+ !(widget.data as PropertyHelper).property.type.isList &&
+ (widget.data as PropertyHelper).property.type != IsarType.object)
PopupMenuItem<dynamic>(
onTap: () {
WidgetsBinding.instance.addPostFrameCallback(
@@ -464,9 +481,7 @@ class _TableItemState extends State<TableItem> {
: 'Edit property',
),
),
- if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
- widget.data.index != null)
+ if (widget.data is PropertyHelper && widget.data.index != null)
PopupMenuItem<dynamic>(
onTap: _removeListItem,
child: PopUpMenuRow(
@@ -478,12 +493,14 @@ class _TableItemState extends State<TableItem> {
),
),
if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
(widget.data as PropertyHelper).property.type.isList &&
- widget.data.value != null)
+ widget.data.value != null &&
+ (widget.data as PropertyHelper).property.type !=
+ IsarType.objectList)
PopupMenuItem<dynamic>(
onTap: () {
- WidgetsBinding.instance.addPostFrameCallback((_) => _addInList(null));
+ WidgetsBinding.instance
+ .addPostFrameCallback((_) => _addInList(null));
},
child: const PopUpMenuRow(
icon: Icon(Icons.add, size: PopUpMenuRow.iconSize),
@@ -491,8 +508,9 @@ class _TableItemState extends State<TableItem> {
),
),
if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
- widget.data.index != null) ...[
+ widget.data.index != null &&
+ (widget.data as PropertyHelper).property.type !=
+ IsarType.object) ...[
PopupMenuItem<dynamic>(
onTap: () {
WidgetsBinding.instance.addPostFrameCallback(
@@ -504,7 +522,7 @@ class _TableItemState extends State<TableItem> {
Icons.add,
size: PopUpMenuRow.iconSize,
),
- text: 'Add item before ${widget.data.index!}',
+ text: 'Add item before ${widget.data.index}',
),
),
PopupMenuItem<dynamic>(
@@ -518,21 +536,19 @@ class _TableItemState extends State<TableItem> {
Icons.add,
size: PopUpMenuRow.iconSize,
),
- text: 'Add item after ${widget.data.index!}',
+ text: 'Add item after ${widget.data.index}',
),
),
],
if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
(widget.data as PropertyHelper).property.type.isList)
PopupMenuItem<dynamic>(
onTap: () => _setValue(<dynamic>[]),
child: const PopUpMenuRow(
- text: 'Clear list',
+ text: 'Empty list',
),
),
if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
!(widget.data as PropertyHelper).property.isId &&
widget.data.value != null)
PopupMenuItem<dynamic>(
@@ -542,7 +558,7 @@ class _TableItemState extends State<TableItem> {
),
),
if (widget.data is PropertyHelper &&
- !(widget.data as PropertyHelper).subLink &&
+ (widget.data as PropertyHelper).linkKey.isEmpty &&
(widget.data as PropertyHelper).index == null &&
(widget.data as PropertyHelper).property.type.isNum)
PopupMenuItem(
@@ -620,50 +636,6 @@ class _TableItemState extends State<TableItem> {
);
}
- Future<void> _edit() async {
- final result = await showDialog<Map<String, dynamic>?>(
- context: context,
- builder: (context) {
- return AlertDialog(
- content: EditPopup(
- type: (widget.data as PropertyHelper).property.type,
- value: widget.data.value,
- ),
- );
- },
- );
-
- if (result != null) {
- await _setValue(result['value']);
- }
- }
-
- Future<void> _addInList(int? index) async {
- final property = (widget.data as PropertyHelper).property;
- final result = await showDialog<Map<String, dynamic>?>(
- context: context,
- builder: (context) {
- return AlertDialog(
- content: EditPopup(
- type:
- property.type.isList ? property.type.scalarType : property.type,
- value: null,
- ),
- );
- },
- );
-
- if (result != null) {
- widget.editor(
- widget.objectId,
- property.name,
- index,
- result['value'],
- EditorType.add,
- );
- }
- }
-
Future<void> _copy([bool linkId = false]) async {
ScaffoldMessenger.of(context).removeCurrentSnackBar();
@@ -708,22 +680,95 @@ class _TableItemState extends State<TableItem> {
}
}
+ Future<void> _edit() async {
+ final result = await showDialog<Map<String, dynamic>?>(
+ context: context,
+ builder: (context) {
+ return AlertDialog(
+ content: EditPopup(
+ type: (widget.data as PropertyHelper).property.type,
+ value: widget.data.value,
+ ),
+ );
+ },
+ );
+
+ if (result != null) {
+ await _setValue(result['value']);
+ }
+ }
+
+ Future<void> _addInList(int? index) async {
+ final helper = widget.data as PropertyHelper;
+ final result = await showDialog<Map<String, dynamic>?>(
+ context: context,
+ builder: (context) {
+ return AlertDialog(
+ content: EditPopup(
+ type: helper.property.type.isList
+ ? helper.property.type.scalarType
+ : helper.property.type,
+ value: null,
+ ),
+ );
+ },
+ );
+
+ if (result != null) {
+ final objectInfo = _objectInfo(helper);
+ if (objectInfo == null) {
+ return;
+ }
+
+ widget.editor(
+ ConnectEdit(
+ instance: '',
+ collection: objectInfo['collectionName'] as String,
+ id: objectInfo['objectId'] as int,
+ path: index == null ? helper.path : _parentPath(),
+ listIndex: index,
+ value: result['value'],
+ ),
+ EditorType.add,
+ );
+ }
+ }
+
Future<void> _setValue(dynamic value) async {
+ final helper = widget.data as PropertyHelper;
+ final objectInfo = _objectInfo(helper);
+ if (objectInfo == null) {
+ return;
+ }
+
widget.editor(
- widget.objectId,
- (widget.data as PropertyHelper).property.name,
- widget.data.index,
- value,
+ ConnectEdit(
+ instance: '',
+ collection: objectInfo['collectionName'] as String,
+ id: objectInfo['objectId'] as int,
+ path: helper.path,
+ listIndex: widget.data.index,
+ value: value,
+ ),
EditorType.edit,
);
}
Future<void> _removeListItem() async {
+ final objectInfo = _objectInfo();
+ if (objectInfo == null) {
+ return;
+ }
+
widget.editor(
- widget.objectId,
- (widget.data as PropertyHelper).property.name,
- widget.data.index,
- null,
+ ConnectEdit(
+ instance: '',
+ collection: objectInfo['collectionName'] as String,
+ id: objectInfo['objectId'] as int,
+ path: _parentPath(),
+ listIndex: widget.data.index,
+ value: null,
+ ),
EditorType.remove,
);
}
@@ -760,6 +805,31 @@ class _TableItemState extends State<TableItem> {
}
}
+ Map<String, dynamic>? _objectInfo([PropertyHelper? helper]) {
+ helper = helper ?? widget.data as PropertyHelper;
+
+ if (helper.linkKey.isNotEmpty) {
+ final linkHelper = widget.treeViewController
+ .getNode<TreeViewHelper>(helper.linkKey)
+ ?.data as LinkHelper?;
+ if (linkHelper == null) {
+ return null;
+ }
+ return {
+ 'collectionName': linkHelper.link.target.name,
+ 'objectId':
+ (linkHelper.value as Map)[linkHelper.link.target.idName] as int,
+ };
+ }
+
+ return {'collectionName': '', 'objectId': widget.objectId};
+ }
+
+ String _parentPath() {
+ final parsedPath = (widget.data as PropertyHelper).path.split('.');
+ return parsedPath.take(parsedPath.length - 1).join('.');
+ }
+
Widget _createText() {
var value = '';
var property = '';
@@ -836,19 +906,16 @@ class _TableItemState extends State<TableItem> {
case IsarType.stringList:
case IsarType.boolList:
case IsarType.dateTimeList:
- value = 'List<${prop.type.scalarType.name[0].toUpperCase()}'
- '${prop.type.scalarType.name.substring(1)}> [';
- value += widget.data.value == null
- ? 'null]'
- : '${(widget.data.value as List).length.toString()}]';
- color = _disableColor;
- break;
-
case IsarType.objectList:
- value = 'List<${prop.target}> [';
+ if (prop.type == IsarType.objectList) {
+ value = 'List<${prop.target}> ';
+ } else {
+ value = 'List<${prop.type.scalarType.name[0].toUpperCase()}'
+ '${prop.type.scalarType.name.substring(1)}> ';
+ }
value += widget.data.value == null
- ? 'null]'
- : '${(widget.data.value as List).length.toString()}]';
+ ? '[null]'
+ : '[${(widget.data.value as List).length}]';
color = _disableColor;
break;
}
@@ -928,21 +995,23 @@ abstract class TreeViewHelper {
class PropertyHelper extends TreeViewHelper {
const PropertyHelper({
- required this.property,
required super.value,
super.index,
- required this.subLink,
+ required this.property,
+ required this.linkKey,
+ required this.path,
});
final IProperty property;
- final bool subLink;
+ final String linkKey;
+ final String path;
}
class LinkHelper extends TreeViewHelper {
const LinkHelper({
- required this.link,
required super.value,
super.index,
+ required this.link,
});
final ILink link;

fixed operations on properties

added operations on link(s)
collapse children when collapsing parent