mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-08-24 20:42:27 +00:00
Diallows naming duplication when duplicating a space, and adds a proper suffix to the name in case of another spaces having a name match.
This commit is contained in:
@ -68,4 +68,20 @@ abstract final class SpacesRecursiveHelper {
|
||||
return space;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
static SpaceModel? findParent(
|
||||
List<SpaceModel> spaces,
|
||||
String targetUuid,
|
||||
) {
|
||||
for (final space in spaces) {
|
||||
if (space.children.any((child) => child.uuid == targetUuid)) {
|
||||
return space;
|
||||
}
|
||||
final parent = findParent(space.children, targetUuid);
|
||||
if (parent != null) {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -49,8 +49,8 @@ class CommunityStructureHeaderActionButtonsComposer extends StatelessWidget {
|
||||
context: context,
|
||||
builder: (_) => DuplicateSpaceDialog(
|
||||
initialName: space.spaceName,
|
||||
selectedSpaceUuid: space.uuid,
|
||||
selectedCommunityUuid: selectedCommunity.uuid,
|
||||
selectedSpace: space,
|
||||
selectedCommunity: selectedCommunity,
|
||||
onSuccess: (spaces) {
|
||||
final updatedCommunity = selectedCommunity.copyWith(
|
||||
spaces: spaces,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/common/widgets/app_loading_indicator.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/community_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/communities/domain/models/space_model.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/data/services/remote_duplicate_space_service.dart';
|
||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.dart';
|
||||
@ -12,15 +13,15 @@ class DuplicateSpaceDialog extends StatelessWidget {
|
||||
const DuplicateSpaceDialog({
|
||||
required this.initialName,
|
||||
required this.onSuccess,
|
||||
required this.selectedSpaceUuid,
|
||||
required this.selectedCommunityUuid,
|
||||
required this.selectedSpace,
|
||||
required this.selectedCommunity,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String initialName;
|
||||
final void Function(List<SpaceModel> spaces) onSuccess;
|
||||
final String selectedSpaceUuid;
|
||||
final String selectedCommunityUuid;
|
||||
final SpaceModel selectedSpace;
|
||||
final CommunityModel selectedCommunity;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -31,10 +32,10 @@ class DuplicateSpaceDialog extends StatelessWidget {
|
||||
child: BlocListener<DuplicateSpaceBloc, DuplicateSpaceState>(
|
||||
listener: _listener,
|
||||
child: DuplicateSpaceDialogForm(
|
||||
initialNameSuffix: '(1)',
|
||||
initialName: initialName,
|
||||
selectedSpaceUuid: selectedSpaceUuid,
|
||||
selectedCommunityUuid: selectedCommunityUuid,
|
||||
initialName: _getInitialName(),
|
||||
selectedSpaceUuid: selectedSpace.uuid,
|
||||
selectedCommunityUuid: selectedCommunity.uuid,
|
||||
siblingNames: _getSiblingNames(),
|
||||
),
|
||||
),
|
||||
);
|
||||
@ -67,4 +68,69 @@ class DuplicateSpaceDialog extends StatelessWidget {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
List<SpaceModel> _findSiblings(
|
||||
List<SpaceModel> spaces,
|
||||
String targetUuid,
|
||||
) {
|
||||
final parent = _findParent(spaces, targetUuid);
|
||||
|
||||
if (parent != null) {
|
||||
return parent.children.where((s) => s.uuid != targetUuid).toList();
|
||||
} else {
|
||||
if (spaces.any((s) => s.uuid == targetUuid)) {
|
||||
return spaces.where((s) => s.uuid != targetUuid).toList();
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
SpaceModel? _findParent(List<SpaceModel> allSpaces, String childUuid) {
|
||||
for (final space in allSpaces) {
|
||||
if (space.children.any((child) => child.uuid == childUuid)) {
|
||||
return space;
|
||||
}
|
||||
final parent = _findParent(space.children, childUuid);
|
||||
if (parent != null) {
|
||||
return parent;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
List<String> _getSiblingNames() {
|
||||
final siblings = _findSiblings(selectedCommunity.spaces, selectedSpace.uuid);
|
||||
final names = siblings.map((s) => s.spaceName).toList();
|
||||
names.add(initialName);
|
||||
return names;
|
||||
}
|
||||
|
||||
String _getInitialName() {
|
||||
final allRelevantNames = _getSiblingNames();
|
||||
final nameRegex = RegExp(r'^(.*?) ?\((\d+)\)$');
|
||||
|
||||
final baseNameMatch = nameRegex.firstMatch(initialName);
|
||||
final baseName = baseNameMatch?.group(1)?.trim() ?? initialName;
|
||||
|
||||
var maxSuffix = 0;
|
||||
|
||||
for (final name in allRelevantNames) {
|
||||
if (name == baseName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
final match = nameRegex.firstMatch(name);
|
||||
if (match != null) {
|
||||
final currentBaseName = match.group(1)!.trim();
|
||||
if (currentBaseName == baseName) {
|
||||
final suffix = int.parse(match.group(2)!);
|
||||
if (suffix > maxSuffix) {
|
||||
maxSuffix = suffix;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return '$baseName(${maxSuffix + 1})';
|
||||
}
|
||||
}
|
||||
|
@ -11,14 +11,14 @@ class DuplicateSpaceDialogForm extends StatefulWidget {
|
||||
required this.initialName,
|
||||
required this.selectedSpaceUuid,
|
||||
required this.selectedCommunityUuid,
|
||||
required this.initialNameSuffix,
|
||||
required this.siblingNames,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final String initialName;
|
||||
final String selectedSpaceUuid;
|
||||
final String selectedCommunityUuid;
|
||||
final String initialNameSuffix;
|
||||
final List<String> siblingNames;
|
||||
|
||||
@override
|
||||
State<DuplicateSpaceDialogForm> createState() => _DuplicateSpaceDialogFormState();
|
||||
@ -26,20 +26,33 @@ class DuplicateSpaceDialogForm extends StatefulWidget {
|
||||
|
||||
class _DuplicateSpaceDialogFormState extends State<DuplicateSpaceDialogForm> {
|
||||
late final TextEditingController _nameController;
|
||||
bool _isNameValid = true;
|
||||
String? _errorText;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_nameController = TextEditingController(
|
||||
text: '${widget.initialName}${widget.initialNameSuffix}',
|
||||
text: widget.initialName,
|
||||
);
|
||||
_nameController.addListener(_validateName);
|
||||
}
|
||||
|
||||
void _validateName() => setState(
|
||||
() => _isNameValid = _nameController.text.trim() != widget.initialName,
|
||||
);
|
||||
void _validateName() {
|
||||
final name = _nameController.text.trim();
|
||||
if (name.isEmpty) {
|
||||
setState(() {
|
||||
_errorText = 'Name cannot be empty';
|
||||
});
|
||||
} else if (widget.siblingNames.contains(name)) {
|
||||
setState(() {
|
||||
_errorText = 'Name must be unique';
|
||||
});
|
||||
} else {
|
||||
setState(() {
|
||||
_errorText = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@ -66,15 +79,15 @@ class _DuplicateSpaceDialogFormState extends State<DuplicateSpaceDialogForm> {
|
||||
const SelectableText('Enter a new name for the duplicated space:'),
|
||||
DuplicateSpaceTextField(
|
||||
nameController: _nameController,
|
||||
isNameValid: _isNameValid,
|
||||
initialName: widget.initialName,
|
||||
isNameValid: _errorText == null,
|
||||
errorText: _errorText,
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
SpaceDetailsActionButtons(
|
||||
spacerFlex: 2,
|
||||
onSave: _isNameValid ? () => _submit(context) : null,
|
||||
onSave: _errorText == null ? () => _submit(context) : null,
|
||||
onCancel: Navigator.of(context).pop,
|
||||
saveButtonLabel: 'Duplicate',
|
||||
),
|
||||
|
@ -6,15 +6,13 @@ class DuplicateSpaceTextField extends StatelessWidget {
|
||||
const DuplicateSpaceTextField({
|
||||
required this.nameController,
|
||||
required this.isNameValid,
|
||||
required this.initialName,
|
||||
this.errorText,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final TextEditingController nameController;
|
||||
final bool isNameValid;
|
||||
final String initialName;
|
||||
|
||||
String get _errorText => 'Name must be different from "$initialName"';
|
||||
final String? errorText;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -35,7 +33,7 @@ class DuplicateSpaceTextField extends StatelessWidget {
|
||||
color: context.theme.colorScheme.error,
|
||||
fontSize: 8,
|
||||
),
|
||||
errorText: isNameValid ? null : _errorText,
|
||||
errorText: isNameValid ? null : errorText,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user