mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-09 22:57:21 +00:00
@ -301,11 +301,18 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
|
SpaceTemplateModel? spaceModel, List<SubspaceModel>? subspaces, List<Tag>? tags) {
|
||||||
setState(() {
|
setState(() {
|
||||||
// Set the first space in the center or use passed position
|
// Set the first space in the center or use passed position
|
||||||
Offset centerPosition = position ?? ConnectionHelper.getCenterPosition(screenSize);
|
Offset newPosition;
|
||||||
|
if (parentIndex != null) {
|
||||||
|
newPosition =
|
||||||
|
getBalancedChildPosition(spaces[parentIndex]); // Ensure balanced position
|
||||||
|
} else {
|
||||||
|
newPosition = position ?? ConnectionHelper.getCenterPosition(screenSize);
|
||||||
|
}
|
||||||
|
|
||||||
SpaceModel newSpace = SpaceModel(
|
SpaceModel newSpace = SpaceModel(
|
||||||
name: name,
|
name: name,
|
||||||
icon: icon,
|
icon: icon,
|
||||||
position: centerPosition,
|
position: newPosition,
|
||||||
isPrivate: false,
|
isPrivate: false,
|
||||||
children: [],
|
children: [],
|
||||||
status: SpaceStatus.newSpace,
|
status: SpaceStatus.newSpace,
|
||||||
@ -425,7 +432,7 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
Connection(
|
Connection(
|
||||||
startSpace: parent,
|
startSpace: parent,
|
||||||
endSpace: child,
|
endSpace: child,
|
||||||
direction: child.incomingConnection?.direction ?? "down",
|
direction: "down",
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -522,6 +529,38 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Offset getBalancedChildPosition(SpaceModel parent) {
|
||||||
|
int totalSiblings = parent.children.length + 1;
|
||||||
|
double totalWidth = (totalSiblings - 1) * 250; // Horizontal spacing
|
||||||
|
double startX = parent.position.dx - (totalWidth / 2);
|
||||||
|
|
||||||
|
Offset position = Offset(startX + (parent.children.length * 250), parent.position.dy + 180);
|
||||||
|
|
||||||
|
// Check for overlaps & adjust
|
||||||
|
while (spaces.any((s) => (s.position - position).distance < 250)) {
|
||||||
|
position = Offset(position.dx + 250, position.dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
void realignTree() {
|
||||||
|
void updatePositions(SpaceModel node, double x, double y) {
|
||||||
|
node.position = Offset(x, y);
|
||||||
|
|
||||||
|
int numChildren = node.children.length;
|
||||||
|
double childStartX = x - ((numChildren - 1) * 250) / 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < numChildren; i++) {
|
||||||
|
updatePositions(node.children[i], childStartX + (i * 250), y + 180);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spaces.isNotEmpty) {
|
||||||
|
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void _onDuplicate(BuildContext parentContext) {
|
void _onDuplicate(BuildContext parentContext) {
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
@ -604,29 +643,57 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
|
|
||||||
void _duplicateSpace(SpaceModel space) {
|
void _duplicateSpace(SpaceModel space) {
|
||||||
final Map<SpaceModel, SpaceModel> originalToDuplicate = {};
|
final Map<SpaceModel, SpaceModel> originalToDuplicate = {};
|
||||||
const double horizontalGap = 200.0;
|
double horizontalGap = 250.0; // Increased spacing
|
||||||
const double verticalGap = 100.0;
|
double verticalGap = 180.0; // Adjusted for better visualization
|
||||||
|
|
||||||
SpaceModel duplicateRecursive(
|
print("🟢 Duplicating: ${space.name}");
|
||||||
SpaceModel original, Offset parentPosition, SpaceModel? duplicatedParent) {
|
|
||||||
Offset newPosition = Offset(parentPosition.dx + horizontalGap, original.position.dy);
|
|
||||||
|
|
||||||
while (spaces.any((s) =>
|
/// **Find a new position ensuring no overlap**
|
||||||
(s.position - newPosition).distance < horizontalGap && s.status != SpaceStatus.deleted)) {
|
Offset getBalancedChildPosition(SpaceModel parent) {
|
||||||
newPosition += Offset(horizontalGap, 0);
|
int totalSiblings = parent.children.length + 1;
|
||||||
|
double totalWidth = (totalSiblings - 1) * horizontalGap;
|
||||||
|
double startX = parent.position.dx - (totalWidth / 2);
|
||||||
|
Offset position = Offset(
|
||||||
|
startX + (parent.children.length * horizontalGap), parent.position.dy + verticalGap);
|
||||||
|
|
||||||
|
// **Check for overlaps & adjust**
|
||||||
|
while (spaces.any((s) => (s.position - position).distance < horizontalGap)) {
|
||||||
|
position = Offset(position.dx + horizontalGap, position.dy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print("🔹 New position for ${parent.name}: (${position.dx}, ${position.dy})");
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// **Realign the entire tree after duplication**
|
||||||
|
void realignTree() {
|
||||||
|
void updatePositions(SpaceModel node, double x, double y) {
|
||||||
|
node.position = Offset(x, y);
|
||||||
|
print("✅ Adjusted ${node.name} to (${x}, ${y})");
|
||||||
|
|
||||||
|
int numChildren = node.children.length;
|
||||||
|
double childStartX = x - ((numChildren - 1) * horizontalGap) / 2;
|
||||||
|
|
||||||
|
for (int i = 0; i < numChildren; i++) {
|
||||||
|
updatePositions(node.children[i], childStartX + (i * horizontalGap), y + verticalGap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spaces.isNotEmpty) {
|
||||||
|
print("🔄 Realigning tree...");
|
||||||
|
updatePositions(spaces.first, spaces.first.position.dx, spaces.first.position.dy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// **Recursive duplication logic**
|
||||||
|
SpaceModel duplicateRecursive(SpaceModel original, SpaceModel? duplicatedParent) {
|
||||||
|
Offset newPosition = duplicatedParent == null
|
||||||
|
? Offset(original.position.dx + horizontalGap, original.position.dy)
|
||||||
|
: getBalancedChildPosition(duplicatedParent);
|
||||||
|
|
||||||
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
|
final duplicatedName = SpaceHelper.generateUniqueSpaceName(original.name, spaces);
|
||||||
|
print(
|
||||||
final List<SubspaceModel>? duplicatedSubspaces;
|
"🟡 Duplicating ${original.name} → ${duplicatedName} at (${newPosition.dx}, ${newPosition.dy})");
|
||||||
final List<Tag>? duplicatedTags;
|
|
||||||
if (original.spaceModel != null) {
|
|
||||||
duplicatedTags = [];
|
|
||||||
duplicatedSubspaces = [];
|
|
||||||
} else {
|
|
||||||
duplicatedTags = original.tags;
|
|
||||||
duplicatedSubspaces = original.subspaces;
|
|
||||||
}
|
|
||||||
|
|
||||||
final duplicated = SpaceModel(
|
final duplicated = SpaceModel(
|
||||||
name: duplicatedName,
|
name: duplicatedName,
|
||||||
@ -637,12 +704,10 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
status: SpaceStatus.newSpace,
|
status: SpaceStatus.newSpace,
|
||||||
parent: duplicatedParent,
|
parent: duplicatedParent,
|
||||||
spaceModel: original.spaceModel,
|
spaceModel: original.spaceModel,
|
||||||
subspaces: duplicatedSubspaces,
|
subspaces: original.subspaces,
|
||||||
tags: duplicatedTags,
|
tags: original.tags,
|
||||||
);
|
);
|
||||||
|
|
||||||
originalToDuplicate[original] = duplicated;
|
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
spaces.add(duplicated);
|
spaces.add(duplicated);
|
||||||
_updateNodePosition(duplicated, duplicated.position);
|
_updateNodePosition(duplicated, duplicated.position);
|
||||||
@ -651,60 +716,42 @@ class _CommunityStructureAreaState extends State<CommunityStructureArea> {
|
|||||||
final newConnection = Connection(
|
final newConnection = Connection(
|
||||||
startSpace: duplicatedParent,
|
startSpace: duplicatedParent,
|
||||||
endSpace: duplicated,
|
endSpace: duplicated,
|
||||||
direction: original.incomingConnection?.direction ?? 'down',
|
direction: "down",
|
||||||
);
|
);
|
||||||
connections.add(newConnection);
|
connections.add(newConnection);
|
||||||
duplicated.incomingConnection = newConnection;
|
duplicated.incomingConnection = newConnection;
|
||||||
duplicatedParent.addOutgoingConnection(newConnection);
|
duplicatedParent.addOutgoingConnection(newConnection);
|
||||||
|
duplicatedParent.children.add(duplicated);
|
||||||
|
print("🔗 Created connection: ${duplicatedParent.name} → ${duplicated.name}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (original.parent != null && duplicatedParent == null) {
|
// **Recalculate the whole tree to avoid overlaps**
|
||||||
final originalParent = original.parent!;
|
realignTree();
|
||||||
final duplicatedParent = originalToDuplicate[originalParent] ?? originalParent;
|
|
||||||
|
|
||||||
final parentConnection = Connection(
|
|
||||||
startSpace: duplicatedParent,
|
|
||||||
endSpace: duplicated,
|
|
||||||
direction: original.incomingConnection?.direction ?? "down",
|
|
||||||
);
|
|
||||||
|
|
||||||
connections.add(parentConnection);
|
|
||||||
duplicated.incomingConnection = parentConnection;
|
|
||||||
duplicatedParent.addOutgoingConnection(parentConnection);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final childrenWithDownDirection = original.children
|
// Recursively duplicate children
|
||||||
.where((child) =>
|
for (var child in original.children) {
|
||||||
child.incomingConnection?.direction == "down" && child.status != SpaceStatus.deleted)
|
duplicateRecursive(child, duplicated);
|
||||||
.toList();
|
|
||||||
|
|
||||||
Offset childStartPosition = childrenWithDownDirection.length == 1
|
|
||||||
? duplicated.position
|
|
||||||
: newPosition + Offset(0, verticalGap);
|
|
||||||
|
|
||||||
for (final child in original.children) {
|
|
||||||
final isDownDirection = child.incomingConnection?.direction == "down" ?? false;
|
|
||||||
|
|
||||||
if (isDownDirection && childrenWithDownDirection.length == 1) {
|
|
||||||
childStartPosition = duplicated.position + Offset(0, verticalGap);
|
|
||||||
} else if (!isDownDirection) {
|
|
||||||
childStartPosition = duplicated.position + Offset(horizontalGap, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
final duplicatedChild = duplicateRecursive(child, childStartPosition, duplicated);
|
|
||||||
duplicated.children.add(duplicatedChild);
|
|
||||||
childStartPosition += Offset(0, verticalGap);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return duplicated;
|
return duplicated;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// **Handle root duplication**
|
||||||
if (space.parent == null) {
|
if (space.parent == null) {
|
||||||
duplicateRecursive(space, space.position, null);
|
print("🟠 Duplicating root node: ${space.name}");
|
||||||
|
SpaceModel duplicatedRoot = duplicateRecursive(space, null);
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
spaces.add(duplicatedRoot);
|
||||||
|
realignTree();
|
||||||
|
});
|
||||||
|
|
||||||
|
print("✅ Root duplication successful: ${duplicatedRoot.name}");
|
||||||
} else {
|
} else {
|
||||||
final duplicatedParent = originalToDuplicate[space.parent!] ?? space.parent!;
|
duplicateRecursive(space, space.parent);
|
||||||
duplicateRecursive(space, space.position, duplicatedParent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
print("🟢 Finished duplication process for: ${space.name}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,18 +47,6 @@ class SpaceCardWidget extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
buildSpaceContainer(index), // Build the space container
|
buildSpaceContainer(index), // Build the space container
|
||||||
if (isHovered) ...[
|
if (isHovered) ...[
|
||||||
PlusButtonWidget(
|
|
||||||
index: index,
|
|
||||||
direction: 'left',
|
|
||||||
offset: const Offset(-21, 20),
|
|
||||||
onButtonTap: onButtonTap,
|
|
||||||
),
|
|
||||||
PlusButtonWidget(
|
|
||||||
index: index,
|
|
||||||
direction: 'right',
|
|
||||||
offset: const Offset(140, 20),
|
|
||||||
onButtonTap: onButtonTap,
|
|
||||||
),
|
|
||||||
PlusButtonWidget(
|
PlusButtonWidget(
|
||||||
index: index,
|
index: index,
|
||||||
direction: 'down',
|
direction: 'down',
|
||||||
|
@ -2,8 +2,7 @@ import 'package:syncrow_web/pages/spaces_management/all_spaces/model/community_m
|
|||||||
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
import 'package:syncrow_web/pages/spaces_management/all_spaces/model/space_model.dart';
|
||||||
|
|
||||||
class SpaceHelper {
|
class SpaceHelper {
|
||||||
static SpaceModel? findSpaceByUuid(
|
static SpaceModel? findSpaceByUuid(String? uuid, List<CommunityModel> communities) {
|
||||||
String? uuid, List<CommunityModel> communities) {
|
|
||||||
for (var community in communities) {
|
for (var community in communities) {
|
||||||
for (var space in community.spaces) {
|
for (var space in community.spaces) {
|
||||||
if (space.uuid == uuid) return space;
|
if (space.uuid == uuid) return space;
|
||||||
@ -12,8 +11,7 @@ class SpaceHelper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static SpaceModel? findSpaceByInternalId(
|
static SpaceModel? findSpaceByInternalId(String? internalId, List<SpaceModel> spaces) {
|
||||||
String? internalId, List<SpaceModel> spaces) {
|
|
||||||
if (internalId != null) {
|
if (internalId != null) {
|
||||||
for (var space in spaces) {
|
for (var space in spaces) {
|
||||||
if (space.internalId == internalId) return space;
|
if (space.internalId == internalId) return space;
|
||||||
@ -23,8 +21,7 @@ class SpaceHelper {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String generateUniqueSpaceName(
|
static String generateUniqueSpaceName(String originalName, List<SpaceModel> spaces) {
|
||||||
String originalName, List<SpaceModel> spaces) {
|
|
||||||
final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim();
|
final baseName = originalName.replaceAll(RegExp(r'\(\d+\)$'), '').trim();
|
||||||
int maxNumber = 0;
|
int maxNumber = 0;
|
||||||
|
|
||||||
@ -54,13 +51,10 @@ class SpaceHelper {
|
|||||||
|
|
||||||
return space == selectedSpace ||
|
return space == selectedSpace ||
|
||||||
selectedSpace.parent?.internalId == space.internalId ||
|
selectedSpace.parent?.internalId == space.internalId ||
|
||||||
selectedSpace.children
|
selectedSpace.children?.any((child) => child.internalId == space.internalId) == true;
|
||||||
?.any((child) => child.internalId == space.internalId) ==
|
|
||||||
true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool isNameConflict(
|
static bool isNameConflict(String value, SpaceModel? parentSpace, SpaceModel? editSpace) {
|
||||||
String value, SpaceModel? parentSpace, SpaceModel? editSpace) {
|
|
||||||
final siblings = parentSpace?.children
|
final siblings = parentSpace?.children
|
||||||
.where((child) => child.internalId != editSpace?.internalId)
|
.where((child) => child.internalId != editSpace?.internalId)
|
||||||
.toList() ??
|
.toList() ??
|
||||||
@ -71,19 +65,17 @@ class SpaceHelper {
|
|||||||
.toList() ??
|
.toList() ??
|
||||||
[];
|
[];
|
||||||
|
|
||||||
final editSiblingConflict =
|
final editSiblingConflict = editSiblings.any((child) => child.name == value);
|
||||||
editSiblings.any((child) => child.name == value);
|
|
||||||
|
|
||||||
final siblingConflict = siblings.any((child) => child.name == value);
|
final siblingConflict = siblings.any((child) => child.name == value);
|
||||||
|
|
||||||
final parentConflict = parentSpace?.name == value &&
|
final parentConflict =
|
||||||
parentSpace?.internalId != editSpace?.internalId;
|
parentSpace?.name == value && parentSpace?.internalId != editSpace?.internalId;
|
||||||
|
|
||||||
final parentOfEditSpaceConflict = editSpace?.parent?.name == value &&
|
final parentOfEditSpaceConflict =
|
||||||
editSpace?.parent?.internalId != editSpace?.internalId;
|
editSpace?.parent?.name == value && editSpace?.parent?.internalId != editSpace?.internalId;
|
||||||
|
|
||||||
final childConflict =
|
final childConflict = editSpace?.children.any((child) => child.name == value) ?? false;
|
||||||
editSpace?.children.any((child) => child.name == value) ?? false;
|
|
||||||
|
|
||||||
return siblingConflict ||
|
return siblingConflict ||
|
||||||
parentConflict ||
|
parentConflict ||
|
||||||
|
Reference in New Issue
Block a user