Skip to content

Commit 7a5990e

Browse files
committed
feat: import workspace from json files
1 parent 6bb7545 commit 7a5990e

File tree

5 files changed

+172
-28
lines changed

5 files changed

+172
-28
lines changed

lib/core/helpers/file_helper.dart

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import 'dart:convert';
2-
import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
32
import 'package:cookethflow/core/helpers/platform_file_helper.dart';
3+
import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
44
import 'package:flutter/foundation.dart';
55
import 'package:universal_html/html.dart' as html;
66

77
class FileServices {
8-
final FileSelectorPlatform fileSelector = FileSelectorPlatform.instance;
9-
108
Future<String> exportFile({
119
required String defaultName,
1210
required String jsonString,
@@ -35,7 +33,7 @@ class FileServices {
3533
mimeTypes: ['application/json'],
3634
);
3735

38-
final String? path = await fileSelector.getSavePath(
36+
final String? path = await PlatformFileService.getSavePath(
3937
acceptedTypeGroups: [typeGroup],
4038
suggestedName: sanitizedName,
4139
);
@@ -89,7 +87,7 @@ class FileServices {
8987
mimeTypes: ['image/png'],
9088
);
9189

92-
final String? path = await fileSelector.getSavePath(
90+
final String? path = await PlatformFileService.getSavePath(
9391
acceptedTypeGroups: [typeGroup],
9492
suggestedName: sanitizedName,
9593
);
@@ -144,7 +142,7 @@ class FileServices {
144142
mimeTypes: ['image/svg+xml'],
145143
);
146144

147-
final String? path = await fileSelector.getSavePath(
145+
final String? path = await PlatformFileService.getSavePath(
148146
acceptedTypeGroups: [typeGroup],
149147
suggestedName: sanitizedName,
150148
);
@@ -188,7 +186,7 @@ class FileServices {
188186

189187
try {
190188
final XFile? file =
191-
await fileSelector.openFile(acceptedTypeGroups: [typeGroup]);
189+
await PlatformFileService.openFile(acceptedTypeGroups: [typeGroup]);
192190

193191
if (file != null) {
194192
print('Selected file: ${file.path}');
@@ -201,22 +199,35 @@ class FileServices {
201199
}
202200

203201
Future<XFile?> importJsonFiles() async {
204-
const XTypeGroup typeGroup = XTypeGroup(
205-
label: 'JSON',
206-
mimeTypes: ['application/json'],
207-
extensions: ['json'],
208-
);
209-
210202
try {
211-
final XFile? file =
212-
await fileSelector.openFile(acceptedTypeGroups: [typeGroup]);
213-
if (file != null) {
214-
print('Selected file: ${file.path}');
203+
final result = await PlatformFileService.pickJSONFile();
204+
if (result == null) {
205+
return null; // User cancelled the operation
215206
}
216-
return file;
207+
final fileName = result['name'] as String;
208+
final bytes = result['bytes'] as Uint8List;
209+
return XFile.fromData(
210+
bytes,
211+
mimeType: 'application/json',
212+
name: fileName,
213+
);
217214
} catch (e) {
218215
print('Error in selecting file: $e');
219216
return null;
220217
}
221218
}
219+
220+
Future<Map<String, dynamic>?> importJsonFileFromUser() async {
221+
try {
222+
final file = await importJsonFiles();
223+
if (file == null) {
224+
return null; // User cancelled the operation
225+
}
226+
final bytes = await file.readAsBytes();
227+
return PlatformFileService.parseJSONFile(bytes);
228+
} catch (e) {
229+
print('Error importing JSON file: $e');
230+
return null;
231+
}
232+
}
222233
}

lib/core/helpers/platform_file_helper.dart

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import 'dart:convert';
22
import 'dart:io';
3+
import 'package:file_selector_platform_interface/file_selector_platform_interface.dart';
34
import 'package:flutter/foundation.dart';
45
import 'package:flutter_document_picker/flutter_document_picker.dart';
56
import 'package:universal_html/html.dart' as html;
@@ -80,4 +81,48 @@ class PlatformFileService {
8081
return null;
8182
}
8283
}
84+
85+
// Added methods to replace file_selector usage
86+
static Future<String?> getSavePath({
87+
required List<XTypeGroup> acceptedTypeGroups,
88+
required String suggestedName,
89+
}) async {
90+
try {
91+
if (kIsWeb) {
92+
// Web platform does not need a save path; handled via Blob
93+
return null;
94+
} else {
95+
final path = await FlutterDocumentPicker.openDocument(
96+
params: FlutterDocumentPickerParams(
97+
allowedFileExtensions:
98+
acceptedTypeGroups.first.extensions ?? ['json'],
99+
invalidFileNameSymbols: ['/'],
100+
),
101+
);
102+
return path;
103+
}
104+
} catch (e) {
105+
print("Error getting save path: $e");
106+
return null;
107+
}
108+
}
109+
110+
static Future<XFile?> openFile({
111+
required List<XTypeGroup> acceptedTypeGroups,
112+
}) async {
113+
try {
114+
final result = await pickJSONFile();
115+
if (result == null) return null;
116+
final fileName = result['name'] as String;
117+
final bytes = result['bytes'] as Uint8List;
118+
return XFile.fromData(
119+
bytes,
120+
mimeType: acceptedTypeGroups.first.mimeTypes?.first ?? 'application/json',
121+
name: fileName,
122+
);
123+
} catch (e) {
124+
print("Error opening file: $e");
125+
return null;
126+
}
127+
}
83128
}

lib/features/dashboard/providers/dashboard_provider.dart

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@ import 'package:flutter/material.dart';
55
import 'package:phosphor_flutter/phosphor_flutter.dart';
66
import 'package:supabase_flutter/supabase_flutter.dart';
77
import 'package:uuid/uuid.dart';
8+
import 'package:cookethflow/core/helpers/file_helper.dart';
89

910
class DashboardProvider extends StateHandler {
1011
late SupabaseClient? supabase;
1112
late SupabaseService supabaseService;
13+
final FileServices _fileServices = FileServices();
14+
1215
DashboardProvider(this.supabase, this.supabaseService) : super() {
1316
initialize();
1417
}
@@ -151,7 +154,7 @@ class DashboardProvider extends StateHandler {
151154
}
152155
}
153156

154-
Future<String> createNewProject(BuildContext context) async {
157+
Future<String> createNewProject() async {
155158
_isLoading = true;
156159
try {
157160
var res = supabase?.auth.currentUser;
@@ -188,7 +191,87 @@ class DashboardProvider extends StateHandler {
188191
}
189192
}
190193

191-
void importExistingProject(BuildContext context) {}
194+
Future<String> importExistingProject() async {
195+
_isLoading = true;
196+
notifyListeners();
197+
try {
198+
final jsonContent = await _fileServices.importJsonFileFromUser();
199+
if (jsonContent == null) {
200+
return 'Import operation cancelled or failed.';
201+
}
202+
203+
if (jsonContent['workspace'] == null || jsonContent['canvasObjects'] == null) {
204+
return 'Invalid file format. Missing "workspace" or "canvasObjects" data.';
205+
}
206+
207+
final currentUser = supabase?.auth.currentUser;
208+
if (currentUser == null) {
209+
return 'User not authenticated.';
210+
}
211+
212+
final oldWorkspaceId = jsonContent['workspace']['id'];
213+
final newWorkspaceId = Uuid().v4();
214+
215+
final newWorkspace = Map<String, dynamic>.from(jsonContent['workspace']);
216+
newWorkspace['id'] = newWorkspaceId;
217+
newWorkspace['owner'] = currentUser.id;
218+
newWorkspace['name'] = '${newWorkspace['name']} (Imported)';
219+
newWorkspace.remove('created_at');
220+
newWorkspace.remove('lastEdited');
221+
222+
final oldToNewIdMap = <String, String>{};
223+
final newCanvasObjects = <Map<String, dynamic>>[];
224+
225+
for (var obj in (jsonContent['canvasObjects'] as List)) {
226+
final newId = Uuid().v4();
227+
final oldId = obj['id'];
228+
oldToNewIdMap[oldId] = newId;
229+
230+
final newObj = Map<String, dynamic>.from(obj);
231+
newObj['id'] = newId;
232+
newCanvasObjects.add(newObj);
233+
}
234+
235+
for (var obj in newCanvasObjects) {
236+
if (obj['object_type'] == 'connector') {
237+
final sourceId = obj['source_id'];
238+
final targetId = obj['target_id'];
239+
if (sourceId != null && oldToNewIdMap.containsKey(sourceId)) {
240+
obj['source_id'] = oldToNewIdMap[sourceId];
241+
}
242+
if (targetId != null && oldToNewIdMap.containsKey(targetId)) {
243+
obj['target_id'] = oldToNewIdMap[targetId];
244+
}
245+
}
246+
}
247+
248+
await supabase!.from('workspace').insert(newWorkspace);
249+
250+
if (newCanvasObjects.isNotEmpty) {
251+
final objectsToInsert = newCanvasObjects.map((obj) {
252+
return {
253+
'id': obj['id'],
254+
'object': obj,
255+
'workspace_id': newWorkspaceId,
256+
};
257+
}).toList();
258+
await supabase!.from('canvas_objects').insert(objectsToInsert);
259+
}
260+
261+
await refreshDashboard();
262+
return 'Workspace imported successfully!';
263+
} catch (e) {
264+
print("Error importing project: $e");
265+
String output = 'An error occurred during import.';
266+
if (e.toString().contains('maximum limit of 10 workspaces')) {
267+
output = 'Maximum limit of workspaces reached. Upgrade your plan for more!';
268+
}
269+
return output;
270+
} finally {
271+
_isLoading = false;
272+
notifyListeners();
273+
}
274+
}
192275

193276
Future<void> syncWithDb() async {
194277
if (supabase == null) {
@@ -249,4 +332,4 @@ class DashboardProvider extends StateHandler {
249332
await refreshDashboard();
250333
}
251334
}
252-
}
335+
}

lib/features/dashboard/widgets/add_project_dialogue.dart

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class AddProject extends StatelessWidget {
3333
icon: PhosphorIconsRegular.plus,
3434
label: 'Start Blank Project',
3535
onTap: () async {
36-
String output = await provider.createNewProject(context);
36+
String output = await provider.createNewProject();
3737
if (output != 'Workspace created successfully!!') {
3838
ScaffoldMessenger.of(context).showSnackBar(
3939
SnackBar(
@@ -54,7 +54,14 @@ class AddProject extends StatelessWidget {
5454
label: 'Import Existing Project',
5555
onTap: () async {
5656
context.pop();
57-
provider.importExistingProject(context);
57+
String output = await provider.importExistingProject();
58+
ScaffoldMessenger.of(context).showSnackBar(
59+
SnackBar(
60+
content: Text(output),
61+
backgroundColor: Colors.lightGreen,
62+
duration: const Duration(seconds: 3),
63+
),
64+
);
5865
},
5966
txtColor: suprovider.isDark ? Colors.white : Colors.black,
6067
borderColor: suprovider.isDark ? Colors.white : Colors.black,
@@ -65,4 +72,4 @@ class AddProject extends StatelessWidget {
6572
},
6673
);
6774
}
68-
}
75+
}

lib/features/workspace/providers/workspace_provider.dart

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -127,11 +127,9 @@ class WorkspaceProvider extends StateHandler {
127127
final canvasObjectsData =
128128
_canvasObjects.values.map((obj) => obj.toJson()).toList();
129129

130+
// ** FIX: Ensure the full workspace object is exported **
130131
final exportData = {
131-
'workspace': {
132-
'name': workspaceData["name"],
133-
'data': workspaceData["data"],
134-
},
132+
'workspace': workspaceData,
135133
'canvasObjects': canvasObjectsData,
136134
};
137135

0 commit comments

Comments
 (0)