mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-08-24 23:42:28 +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;
|
return space;
|
||||||
}).toList();
|
}).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,
|
context: context,
|
||||||
builder: (_) => DuplicateSpaceDialog(
|
builder: (_) => DuplicateSpaceDialog(
|
||||||
initialName: space.spaceName,
|
initialName: space.spaceName,
|
||||||
selectedSpaceUuid: space.uuid,
|
selectedSpace: space,
|
||||||
selectedCommunityUuid: selectedCommunity.uuid,
|
selectedCommunity: selectedCommunity,
|
||||||
onSuccess: (spaces) {
|
onSuccess: (spaces) {
|
||||||
final updatedCommunity = selectedCommunity.copyWith(
|
final updatedCommunity = selectedCommunity.copyWith(
|
||||||
spaces: spaces,
|
spaces: spaces,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/common/widgets/app_loading_indicator.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/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/data/services/remote_duplicate_space_service.dart';
|
||||||
import 'package:syncrow_web/pages/space_management_v2/modules/duplicate_space/presentation/bloc/duplicate_space_bloc.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({
|
const DuplicateSpaceDialog({
|
||||||
required this.initialName,
|
required this.initialName,
|
||||||
required this.onSuccess,
|
required this.onSuccess,
|
||||||
required this.selectedSpaceUuid,
|
required this.selectedSpace,
|
||||||
required this.selectedCommunityUuid,
|
required this.selectedCommunity,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String initialName;
|
final String initialName;
|
||||||
final void Function(List<SpaceModel> spaces) onSuccess;
|
final void Function(List<SpaceModel> spaces) onSuccess;
|
||||||
final String selectedSpaceUuid;
|
final SpaceModel selectedSpace;
|
||||||
final String selectedCommunityUuid;
|
final CommunityModel selectedCommunity;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -31,10 +32,10 @@ class DuplicateSpaceDialog extends StatelessWidget {
|
|||||||
child: BlocListener<DuplicateSpaceBloc, DuplicateSpaceState>(
|
child: BlocListener<DuplicateSpaceBloc, DuplicateSpaceState>(
|
||||||
listener: _listener,
|
listener: _listener,
|
||||||
child: DuplicateSpaceDialogForm(
|
child: DuplicateSpaceDialogForm(
|
||||||
initialNameSuffix: '(1)',
|
initialName: _getInitialName(),
|
||||||
initialName: initialName,
|
selectedSpaceUuid: selectedSpace.uuid,
|
||||||
selectedSpaceUuid: selectedSpaceUuid,
|
selectedCommunityUuid: selectedCommunity.uuid,
|
||||||
selectedCommunityUuid: selectedCommunityUuid,
|
siblingNames: _getSiblingNames(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -67,4 +68,69 @@ class DuplicateSpaceDialog extends StatelessWidget {
|
|||||||
break;
|
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.initialName,
|
||||||
required this.selectedSpaceUuid,
|
required this.selectedSpaceUuid,
|
||||||
required this.selectedCommunityUuid,
|
required this.selectedCommunityUuid,
|
||||||
required this.initialNameSuffix,
|
required this.siblingNames,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String initialName;
|
final String initialName;
|
||||||
final String selectedSpaceUuid;
|
final String selectedSpaceUuid;
|
||||||
final String selectedCommunityUuid;
|
final String selectedCommunityUuid;
|
||||||
final String initialNameSuffix;
|
final List<String> siblingNames;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DuplicateSpaceDialogForm> createState() => _DuplicateSpaceDialogFormState();
|
State<DuplicateSpaceDialogForm> createState() => _DuplicateSpaceDialogFormState();
|
||||||
@ -26,20 +26,33 @@ class DuplicateSpaceDialogForm extends StatefulWidget {
|
|||||||
|
|
||||||
class _DuplicateSpaceDialogFormState extends State<DuplicateSpaceDialogForm> {
|
class _DuplicateSpaceDialogFormState extends State<DuplicateSpaceDialogForm> {
|
||||||
late final TextEditingController _nameController;
|
late final TextEditingController _nameController;
|
||||||
bool _isNameValid = true;
|
String? _errorText;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_nameController = TextEditingController(
|
_nameController = TextEditingController(
|
||||||
text: '${widget.initialName}${widget.initialNameSuffix}',
|
text: widget.initialName,
|
||||||
);
|
);
|
||||||
_nameController.addListener(_validateName);
|
_nameController.addListener(_validateName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _validateName() => setState(
|
void _validateName() {
|
||||||
() => _isNameValid = _nameController.text.trim() != widget.initialName,
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
@ -66,15 +79,15 @@ class _DuplicateSpaceDialogFormState extends State<DuplicateSpaceDialogForm> {
|
|||||||
const SelectableText('Enter a new name for the duplicated space:'),
|
const SelectableText('Enter a new name for the duplicated space:'),
|
||||||
DuplicateSpaceTextField(
|
DuplicateSpaceTextField(
|
||||||
nameController: _nameController,
|
nameController: _nameController,
|
||||||
isNameValid: _isNameValid,
|
isNameValid: _errorText == null,
|
||||||
initialName: widget.initialName,
|
errorText: _errorText,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
SpaceDetailsActionButtons(
|
SpaceDetailsActionButtons(
|
||||||
spacerFlex: 2,
|
spacerFlex: 2,
|
||||||
onSave: _isNameValid ? () => _submit(context) : null,
|
onSave: _errorText == null ? () => _submit(context) : null,
|
||||||
onCancel: Navigator.of(context).pop,
|
onCancel: Navigator.of(context).pop,
|
||||||
saveButtonLabel: 'Duplicate',
|
saveButtonLabel: 'Duplicate',
|
||||||
),
|
),
|
||||||
|
@ -6,15 +6,13 @@ class DuplicateSpaceTextField extends StatelessWidget {
|
|||||||
const DuplicateSpaceTextField({
|
const DuplicateSpaceTextField({
|
||||||
required this.nameController,
|
required this.nameController,
|
||||||
required this.isNameValid,
|
required this.isNameValid,
|
||||||
required this.initialName,
|
this.errorText,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final TextEditingController nameController;
|
final TextEditingController nameController;
|
||||||
final bool isNameValid;
|
final bool isNameValid;
|
||||||
final String initialName;
|
final String? errorText;
|
||||||
|
|
||||||
String get _errorText => 'Name must be different from "$initialName"';
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -35,7 +33,7 @@ class DuplicateSpaceTextField extends StatelessWidget {
|
|||||||
color: context.theme.colorScheme.error,
|
color: context.theme.colorScheme.error,
|
||||||
fontSize: 8,
|
fontSize: 8,
|
||||||
),
|
),
|
||||||
errorText: isNameValid ? null : _errorText,
|
errorText: isNameValid ? null : errorText,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user