implemented container site assignements & improved routing

This commit is contained in:
Fabian Freund
2025-11-09 07:25:54 +01:00
parent da232bf8aa
commit 107b2a9aa6
45 changed files with 1104 additions and 264 deletions
@@ -17,6 +17,7 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import 'dart:convert';
import 'dart:ui';
import 'package:drift/drift.dart';
@@ -24,6 +25,7 @@ import 'package:weblibre/features/geckoview/features/tabs/data/database/daos/con
import 'package:weblibre/features/geckoview/features/tabs/data/database/database.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/database/definitions.drift.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart';
@DriftAccessor()
class ContainerDao extends DatabaseAccessor<TabDatabase>
@@ -115,7 +117,7 @@ class ContainerDao extends DatabaseAccessor<TabDatabase>
);
}
SingleSelectable<String> generateOrderKeyAfterTabId(
SingleOrNullSelectable<String> generateOrderKeyAfterTabId(
String? containerId,
String tabId,
) {
@@ -134,4 +136,26 @@ class ContainerDao extends DatabaseAccessor<TabDatabase>
tabId: tabId,
);
}
SingleSelectable<bool> isSiteAssignedToContainer(Uri uri) {
return db.definitionsDrift.isSiteAssignedToContainer(uri: uri.origin);
}
SingleSelectable<bool> areSitesAvailable(
Iterable<Uri> origins,
String ignoredContainerId,
) {
return db.definitionsDrift.areSitesAvailable(
ignoreContainerId: ignoredContainerId,
uriList: jsonEncode(origins.map((value) => value.origin).toList()),
);
}
SingleSelectable<String> siteAssignedContainerId(Uri uri) {
return db.definitionsDrift.siteAssignedContainerId(uri: uri.origin);
}
Selectable<SiteAssignment> allAssignedSites() {
return db.definitionsDrift.allAssignedSites();
}
}
@@ -62,11 +62,14 @@ class TabDao extends DatabaseAccessor<TabDatabase> with $TabDaoMixin {
Future<String> _generateOrderKey({
required Value<String?> parentId,
required Value<String?> containerId,
}) {
}) async {
if (parentId.value.isNotEmpty) {
return db.containerDao
.generateOrderKeyAfterTabId(containerId.value, parentId.value!)
.getSingle();
return await db.containerDao
.generateOrderKeyAfterTabId(containerId.value, parentId.value!)
.getSingleOrNull() ??
await db.containerDao
.generateLeadingOrderKey(containerId.value)
.getSingle();
} else {
return db.containerDao
.generateLeadingOrderKey(containerId.value)
@@ -1,6 +1,7 @@
import 'package:weblibre/data/database/converters/color.dart';
import 'package:weblibre/data/database/converters/uri.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/container_data.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/models/tab_query_result.dart';
import 'package:weblibre/features/geckoview/features/tabs/data/database/converters/container_metadata_converter.dart';
@@ -273,3 +274,41 @@ nextTabByOrderKey(:tab_id AS TEXT, :container_id AS TEXT OR NULL, :skip_containe
SELECT next_tab_id
FROM ranked_tabs
WHERE id = :tab_id;
isSiteAssignedToContainer:
SELECT EXISTS (
SELECT 1
FROM container
CROSS JOIN json_each(container.metadata, '$.assignedSites')
WHERE json_each.value = :uri
) AS existing;
areSitesAvailable:
SELECT NOT EXISTS (
SELECT 1
FROM container
CROSS JOIN json_each(container.metadata, '$.assignedSites')
WHERE json_each.value IN (
SELECT value
FROM json_each(:uriList)
) AND
container.id IS NOT :ignore_container_id
) AS existing;
siteAssignedContainerId:
SELECT id
FROM container
WHERE EXISTS (
SELECT 1
FROM json_each(container.metadata, '$.assignedSites')
WHERE value = :uri
);
allAssignedSites WITH SiteAssignment:
SELECT
container.id,
COALESCE(container.metadata ->> '$.contextualIdentity', 'general') AS contextualIdentity,
value AS assigned_site
FROM container
CROSS JOIN json_each(container.metadata, '$.assignedSites')
WHERE value IS NOT NULL;
@@ -13,6 +13,8 @@ import 'package:weblibre/data/database/converters/uri.dart' as i6;
import 'package:drift/internal/modular.dart' as i7;
import 'package:weblibre/features/geckoview/features/tabs/data/models/tab_query_result.dart'
as i8;
import 'package:weblibre/features/geckoview/features/tabs/data/models/site_assignment.dart'
as i9;
typedef $ContainerCreateCompanionBuilder =
i3.ContainerCompanion Function({
@@ -2277,6 +2279,50 @@ class DefinitionsDrift extends i7.ModularAccessor {
).map((i0.QueryRow row) => row.readNullable<String>('next_tab_id'));
}
i0.Selectable<bool> isSiteAssignedToContainer({String? uri}) {
return customSelect(
'SELECT EXISTS (SELECT 1 AS _c0 FROM container CROSS JOIN json_each(container.metadata, \'\$.assignedSites\')WHERE json_each.value = ?1) AS existing',
variables: [i0.Variable<String>(uri)],
readsFrom: {container},
).map((i0.QueryRow row) => row.read<bool>('existing'));
}
i0.Selectable<bool> areSitesAvailable({
required String uriList,
required String ignoreContainerId,
}) {
return customSelect(
'SELECT NOT EXISTS (SELECT 1 AS _c0 FROM container CROSS JOIN json_each(container.metadata, \'\$.assignedSites\')WHERE json_each.value IN (SELECT value FROM json_each(?1)) AND container.id IS NOT ?2) AS existing',
variables: [
i0.Variable<String>(uriList),
i0.Variable<String>(ignoreContainerId),
],
readsFrom: {container},
).map((i0.QueryRow row) => row.read<bool>('existing'));
}
i0.Selectable<String> siteAssignedContainerId({String? uri}) {
return customSelect(
'SELECT id FROM container WHERE EXISTS (SELECT 1 AS _c0 FROM json_each(container.metadata, \'\$.assignedSites\')WHERE value = ?1)',
variables: [i0.Variable<String>(uri)],
readsFrom: {container},
).map((i0.QueryRow row) => row.read<String>('id'));
}
i0.Selectable<i9.SiteAssignment> allAssignedSites() {
return customSelect(
'SELECT container.id, COALESCE(container.metadata ->> \'\$.contextualIdentity\', \'general\') AS contextualIdentity, value AS assigned_site FROM container CROSS JOIN json_each(container.metadata, \'\$.assignedSites\')WHERE value IS NOT NULL',
variables: [],
readsFrom: {container},
).map(
(i0.QueryRow row) => i9.SiteAssignment(
id: row.read<String>('id'),
contextualIdentity: row.read<String>('contextualIdentity'),
assignedSite: row.readNullable<String>('assigned_site'),
),
);
}
i3.TabFts get tabFts => i7.ReadDatabaseContainer(
attachedDatabase,
).resultSet<i3.TabFts>('tab_fts');
@@ -21,6 +21,7 @@ import 'package:copy_with_extension/copy_with_extension.dart';
import 'package:fast_equatable/fast_equatable.dart';
import 'package:flutter/widgets.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:weblibre/data/database/converters/color.dart';
import 'package:weblibre/data/database/converters/icon_data.dart';
part 'container_data.g.dart';
@@ -72,11 +73,14 @@ class ContainerMetadata with FastEquatable {
@JsonKey(defaultValue: false)
final bool useProxy;
final List<Uri>? assignedSites;
ContainerMetadata({
required this.iconData,
required this.contextualIdentity,
required this.authSettings,
required this.useProxy,
required this.assignedSites,
});
ContainerMetadata.withDefaults({
@@ -84,11 +88,13 @@ class ContainerMetadata with FastEquatable {
String? contextualIdentity,
ContainerAuthSettings? authSettings,
bool? useProxy,
List<Uri>? assignedSites,
}) : this(
iconData: iconData,
contextualIdentity: contextualIdentity,
authSettings: authSettings ?? ContainerAuthSettings.withDefaults(),
useProxy: useProxy ?? false,
assignedSites: assignedSites,
);
factory ContainerMetadata.fromJson(Map<String, dynamic> json) =>
@@ -102,13 +108,16 @@ class ContainerMetadata with FastEquatable {
contextualIdentity,
authSettings,
useProxy,
assignedSites,
];
}
@JsonSerializable()
@CopyWith()
class ContainerData with FastEquatable {
final String id;
final String? name;
@ColorJsonConverter()
final Color color;
final ContainerMetadata metadata;
@@ -119,10 +128,16 @@ class ContainerData with FastEquatable {
ContainerMetadata? metadata,
}) : metadata = metadata ?? ContainerMetadata.withDefaults();
factory ContainerData.fromJson(Map<String, dynamic> json) =>
_$ContainerDataFromJson(json);
Map<String, dynamic> toJson() => _$ContainerDataToJson(this);
@override
List<Object?> get hashParameters => [id, name, color, metadata];
}
@JsonSerializable()
class ContainerDataWithCount extends ContainerData {
final int? tabCount;
@@ -134,6 +149,12 @@ class ContainerDataWithCount extends ContainerData {
required this.tabCount,
});
factory ContainerDataWithCount.fromJson(Map<String, dynamic> json) =>
_$ContainerDataWithCountFromJson(json);
@override
Map<String, dynamic> toJson() => _$ContainerDataWithCountToJson(this);
@override
List<Object?> get hashParameters => [...super.hashParameters, tabCount];
}
@@ -98,6 +98,8 @@ abstract class _$ContainerMetadataCWProxy {
ContainerMetadata useProxy(bool useProxy);
ContainerMetadata assignedSites(List<Uri>? assignedSites);
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `ContainerMetadata(...).copyWith.fieldName(value)`.
///
@@ -110,6 +112,7 @@ abstract class _$ContainerMetadataCWProxy {
String? contextualIdentity,
ContainerAuthSettings authSettings,
bool useProxy,
List<Uri>? assignedSites,
});
}
@@ -134,6 +137,10 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
@override
ContainerMetadata useProxy(bool useProxy) => call(useProxy: useProxy);
@override
ContainerMetadata assignedSites(List<Uri>? assignedSites) =>
call(assignedSites: assignedSites);
@override
/// Creates a new instance with the provided field values.
/// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `ContainerMetadata(...).copyWith.fieldName(value)`.
@@ -147,6 +154,7 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
Object? contextualIdentity = const $CopyWithPlaceholder(),
Object? authSettings = const $CopyWithPlaceholder(),
Object? useProxy = const $CopyWithPlaceholder(),
Object? assignedSites = const $CopyWithPlaceholder(),
}) {
return ContainerMetadata(
iconData: iconData == const $CopyWithPlaceholder()
@@ -166,6 +174,10 @@ class _$ContainerMetadataCWProxyImpl implements _$ContainerMetadataCWProxy {
? _value.useProxy
// ignore: cast_nullable_to_non_nullable
: useProxy as bool,
assignedSites: assignedSites == const $CopyWithPlaceholder()
? _value.assignedSites
// ignore: cast_nullable_to_non_nullable
: assignedSites as List<Uri>?,
);
}
}
@@ -299,18 +311,23 @@ ContainerMetadata _$ContainerMetadataFromJson(Map<String, dynamic> json) =>
json['authSettings'] as Map<String, dynamic>,
),
useProxy: json['useProxy'] as bool? ?? false,
assignedSites: (json['assignedSites'] as List<dynamic>?)
?.map((e) => Uri.parse(e as String))
.toList(),
);
Map<String, dynamic> _$ContainerMetadataToJson(ContainerMetadata instance) =>
<String, dynamic>{
'iconData': _$JsonConverterToJson<Map<String, dynamic>, IconData>(
instance.iconData,
const IconDataJsonConverter().toJson,
),
'contextualIdentity': instance.contextualIdentity,
'authSettings': instance.authSettings.toJson(),
'useProxy': instance.useProxy,
};
Map<String, dynamic> _$ContainerMetadataToJson(
ContainerMetadata instance,
) => <String, dynamic>{
'iconData': _$JsonConverterToJson<Map<String, dynamic>, IconData>(
instance.iconData,
const IconDataJsonConverter().toJson,
),
'contextualIdentity': instance.contextualIdentity,
'authSettings': instance.authSettings.toJson(),
'useProxy': instance.useProxy,
'assignedSites': instance.assignedSites?.map((e) => e.toString()).toList(),
};
Value? _$JsonConverterFromJson<Json, Value>(
Object? json,
@@ -321,3 +338,44 @@ Json? _$JsonConverterToJson<Json, Value>(
Value? value,
Json? Function(Value value) toJson,
) => value == null ? null : toJson(value);
ContainerData _$ContainerDataFromJson(
Map<String, dynamic> json,
) => ContainerData(
id: json['id'] as String,
name: json['name'] as String?,
color: const ColorJsonConverter().fromJson((json['color'] as num).toInt()),
metadata: json['metadata'] == null
? null
: ContainerMetadata.fromJson(json['metadata'] as Map<String, dynamic>),
);
Map<String, dynamic> _$ContainerDataToJson(ContainerData instance) =>
<String, dynamic>{
'id': instance.id,
'name': instance.name,
'color': const ColorJsonConverter().toJson(instance.color),
'metadata': instance.metadata.toJson(),
};
ContainerDataWithCount _$ContainerDataWithCountFromJson(
Map<String, dynamic> json,
) => ContainerDataWithCount(
id: json['id'] as String,
name: json['name'] as String?,
color: const ColorJsonConverter().fromJson((json['color'] as num).toInt()),
metadata: json['metadata'] == null
? null
: ContainerMetadata.fromJson(json['metadata'] as Map<String, dynamic>),
tabCount: (json['tabCount'] as num?)?.toInt(),
);
Map<String, dynamic> _$ContainerDataWithCountToJson(
ContainerDataWithCount instance,
) => <String, dynamic>{
'id': instance.id,
'name': instance.name,
'color': const ColorJsonConverter().toJson(instance.color),
'metadata': instance.metadata.toJson(),
'tabCount': instance.tabCount,
};
@@ -0,0 +1,17 @@
import 'package:fast_equatable/fast_equatable.dart';
class SiteAssignment with FastEquatable {
final String id;
final String? contextualIdentity;
final Uri assignedSite;
SiteAssignment({
required this.id,
required this.contextualIdentity,
//Drift type casting issue should be non null
required String? assignedSite,
}) : assignedSite = Uri.parse(assignedSite!);
@override
List<Object?> get hashParameters => [id, contextualIdentity, assignedSite];
}