added property editor
fixed properties with null value
better presentation for multi line strings
MODIFY
packages/isar/lib/src/isar_connect.dart
packages/isar/lib/src/isar_connect.dart
@@ -13,9 +13,11 @@ abstract class _IsarConnect {
ConnectAction.executeQuery: _executeQuery,
ConnectAction.removeQuery: _removeQuery,
ConnectAction.exportJson: _exportJson,
+ ConnectAction.editProperty: _editProperty,
};
static bool _initialized = false;
+
// ignore: cancel_subscriptions
static StreamSubscription<void>? _querySubscription;
static final List<StreamSubscription<void>> _collectionSubscriptions =
@@ -167,6 +169,34 @@ abstract class _IsarConnect {
return query.exportJson();
}
+ static Future<bool> _editProperty(
+ Map<String, dynamic> params,
+ ) async {
+ final cEdit = ConnectEdit.fromJson(params);
+ final collection = Isar.getInstance(cEdit.instance)!
+ .getCollectionByNameInternal(cEdit.collection)!;
+
+ final query = collection.buildQuery<dynamic>(
+ whereClauses: [IdWhereClause.equalTo(value: cEdit.id)],
+ );
+
+ final objects = await query.exportJson();
+
+ if (objects.isEmpty || !objects[0].containsKey(cEdit.property)) {
+ 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));
+ return true;
+ }
+
static Query<dynamic> _getQuery(Map<String, dynamic> params) {
final query = ConnectQuery.fromJson(params);
final collection = Isar.getInstance(query.instance)!

MODIFY
packages/isar/lib/src/isar_connect_api.dart
packages/isar/lib/src/isar_connect_api.dart
@@ -10,7 +10,8 @@ enum ConnectAction {
executeQuery('ext.isar.executeQuery'),
removeQuery('ext.isar.removeQuery'),
exportQuery('ext.isar.exportQuery'),
- exportJson('ext.isar.exportJson');
+ exportJson('ext.isar.exportJson'),
+ editProperty('ext.isar.editProperty');
const ConnectAction(this.method);
@@ -123,6 +124,46 @@ class ConnectQuery {
}
}
+class ConnectEdit {
+ ConnectEdit({
+ required this.instance,
+ required this.collection,
+ required this.id,
+ required this.property,
+ this.index,
+ required this.value,
+ });
+
+ factory ConnectEdit.fromJson(Map<String, dynamic> json) {
+ return ConnectEdit(
+ instance: json['instance'] as String,
+ collection: json['collection'] as String,
+ id: json['id'] as int,
+ index: json['index'] as int?,
+ property: json['property'] as String,
+ value: json['value'],
+ );
+ }
+
+ final String instance;
+ final String collection;
+ final int id;
+ final String property;
+ final int? index;
+ final dynamic value;
+
+ Map<String, dynamic> toJson() {
+ return {
+ 'instance': instance,
+ 'collection': collection,
+ 'id': id,
+ 'index': index,
+ 'property': property,
+ 'value': value,
+ };
+ }
+}
+
class ConnectCollectionInfo {
ConnectCollectionInfo({
required this.instance,

ADD
packages/isar_inspector/lib/edit_popup.dart
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:isar/isar.dart';
+import 'package:isar_inspector/schema.dart';
+
+class EditPopup extends StatefulWidget {
+ const EditPopup({
+ super.key,
+ required this.type,
+ required this.value,
+ required this.enableNull,
+ });
+
+ final IsarType type;
+ final dynamic value;
+ final bool enableNull;
+
+ @override
+ State<EditPopup> createState() => _EditPopupState();
+}
+
+class _EditPopupState extends State<EditPopup> {
+ final _formKey = GlobalKey<FormState>();
+ final _controller = TextEditingController();
+ final _focus = FocusNode();
+ RegExp? _re;
+
+ bool? _boolValue;
+ bool _null = false;
+
+ @override
+ void initState() {
+ if (widget.enableNull && widget.value == null) {
+ _null = true;
+ }
+
+ if (widget.type == IsarType.Bool) {
+ _boolValue = _null || widget.value as bool;
+ } else {
+ _controller.text = _null ? '' : widget.value.toString();
+ _controller.selection = TextSelection(
+ baseOffset: 0,
+ extentOffset: _controller.text.length,
+ );
+
+ if ([IsarType.Float, IsarType.Double].contains(widget.type)) {
+ _re = RegExp(r'^(\d+)?\.?\d*$');
+ } else if ([IsarType.Int, IsarType.Long].contains(widget.type)) {
+ _re = RegExp(r'^\d*$');
+ } else if (widget.type == IsarType.Byte) {
+ _re = RegExp(r'^\d{0,3}$');
+ } else if (widget.type == IsarType.String) {
+ _re = null;
+ } else {
+ throw IsarError('new IsarType (${widget.type.name}), rule not defined');
+ }
+
+ if (!_null) {
+ _focus.requestFocus();
+ }
+ }
+
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return SizedBox(
+ width: widget.type == IsarType.String ? 500 : 300,
+ //height: 210,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (widget.enableNull) ...[
+ CheckboxListTile(
+ value: _null,
+ title: const Text('NULL'),
+ onChanged: (value) {
+ if (value != null) {
+ setState(() {
+ _null = value;
+ });
+ }
+ },
+ ),
+ const SizedBox(height: 15),
+ ],
+ if (widget.type == IsarType.Bool) ...[
+ DropdownButtonHideUnderline(
+ child: DropdownButton<bool>(
+ value: _boolValue,
+ items: const [
+ DropdownMenuItem(
+ value: true,
+ child: Text('TRUE'),
+ ),
+ DropdownMenuItem(
+ value: false,
+ child: Text('FALSE'),
+ ),
+ ],
+ onChanged: _null
+ ? null
+ : (value) {
+ if (value != null) {
+ setState(() {
+ _boolValue = value;
+ });
+ }
+ },
+ ),
+ ),
+ const SizedBox(height: 15),
+ ],
+ if (widget.type != IsarType.Bool) ...[
+ Form(
+ key: _formKey,
+ child: TextFormField(
+ controller: _controller,
+ focusNode: _focus,
+ enabled: !_null,
+ decoration: InputDecoration(
+ enabledBorder: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(20),
+ borderSide: const BorderSide(color: Colors.white),
+ ),
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(20),
+ ),
+ contentPadding: const EdgeInsets.all(20),
+ ),
+ inputFormatters: [
+ if (_re != null) FilteringTextInputFormatter.allow(_re!)
+ ],
+ maxLines: widget.type == IsarType.String ? 3 : 1,
+ validator: (value) {
+ if (widget.type == IsarType.Byte) {
+ final val = int.parse(value!);
+ if (val < 0 || val > 255) {
+ return 'Byte values must between 0-255';
+ }
+ }
+ return null;
+ },
+ onFieldSubmitted: (_) => _save(),
+ ),
+ ),
+ const SizedBox(height: 15),
+ ],
+ Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+ children: [
+ ElevatedButton(onPressed: _save, child: const Text('Save')),
+ ElevatedButton(
+ onPressed: () => Navigator.pop(context),
+ child: const Text('Cancel'),
+ ),
+ ],
+ )
+ ],
+ ),
+ );
+ }
+
+ void _save() {
+ dynamic value;
+
+ if (_null) {
+ value = null;
+ } else {
+ if (widget.type == IsarType.Bool) {
+ value = _boolValue;
+ } else {
+ if (_formKey.currentState!.validate()) {
+ //ignore: missing_enum_constant_in_switch
+ switch (widget.type) {
+ case IsarType.Float:
+ case IsarType.Double:
+ value = double.tryParse(_controller.text) ?? 0.0;
+ break;
+
+ case IsarType.Byte:
+ case IsarType.Int:
+ case IsarType.Long:
+ value = int.tryParse(_controller.text) ?? 0;
+ break;
+ }
+ value ??= _controller.text;
+ }
+ }
+ }
+
+ Navigator.pop(context, {'value': value});
+ }
+}

MODIFY
packages/isar_inspector/lib/schema.dart
packages/isar_inspector/lib/schema.dart
import 'dart:core';
+import 'package:isar/isar.dart';
import 'package:json_annotation/json_annotation.dart';
part 'schema.g.dart';
@@ -64,6 +65,7 @@ enum IsarType {
Int,
Float,
Long,
+ Byte,
Double,
String,
ByteList,
@@ -73,4 +75,43 @@ enum IsarType {
DoubleList,
StringList,
BoolList;
+
+ bool get isList {
+ return const [
+ ByteList,
+ IntList,
+ FloatList,
+ LongList,
+ DoubleList,
+ StringList,
+ BoolList
+ ].contains(this);
+ }
+
+ IsarType get childType {
+ //ignore: missing_enum_constant_in_switch
+ switch (this) {
+ case ByteList:
+ return Byte;
+
+ case IntList:
+ return Int;
+
+ case FloatList:
+ return Float;
+
+ case LongList:
+ return Long;
+
+ case DoubleList:
+ return Double;
+
+ case StringList:
+ return String;
+
+ case BoolList:
+ return Bool;
+ }
+ throw IsarError('new IsarType ($name), rule not defined');
+ }
}

MODIFY
packages/isar_inspector/lib/state/isar_connect_state_notifier.dart
packages/isar_inspector/lib/state/isar_connect_state_notifier.dart
@@ -174,6 +174,14 @@ class IsarConnectStateNotifier
return data.cast();
}
+ Future<void> editProperty(ConnectEdit edit) async {
+ await _call<dynamic>(
+ ConnectAction.editProperty,
+ args: edit.toJson(),
+ timeout: kLongTimeout,
+ );
+ }
+
Future<void> disconnect() async {
await state.value?.vmService.dispose();
}

added property editor

fixed properties with null value
better presentation for multi line strings