diff --git a/lib/config_tree.dart b/lib/config_tree.dart index 8d2635f..2c0c78a 100644 --- a/lib/config_tree.dart +++ b/lib/config_tree.dart @@ -7,6 +7,8 @@ import "package:path/path.dart" as p; import "config_tile.dart"; import "dual_pane.dart"; import "main.dart"; +import "map_tile.dart"; +import "new_map_button.dart"; class ConfigTree extends ConsumerStatefulWidget { const ConfigTree({super.key}); @@ -45,6 +47,20 @@ class _ConfigTreeState extends ConsumerState { maps.add(map); } } + + //watch for changes to files in the maps directory + entity + .watch(events: FileSystemEvent.create | FileSystemEvent.delete) + .listen((FileSystemEvent event) { + switch (event.type) { + case FileSystemEvent.create: + addMap(File(event.path)); + break; + case FileSystemEvent.delete: + removeMap(File(event.path)); + break; + } + }); } } } @@ -52,6 +68,24 @@ class _ConfigTreeState extends ConsumerState { //sort all lists alphabetically configs.sort((a, b) => a.path.compareTo(b.path)); storages.sort((a, b) => a.path.compareTo(b.path)); + sortMaps(); + } + + void addMap(File newMap) { + setState(() { + maps.add(newMap); + sortMaps(); + }); + } + + void removeMap(File toRemoveMap) { + setState(() { + maps.removeWhere((File map) => p.equals(map.path, toRemoveMap.path)); + sortMaps(); + }); + } + + void sortMaps() { //TODO: Sort maps based on internal `sorting` property maps.sort((a, b) => a.path.compareTo(b.path)); } @@ -66,7 +100,8 @@ class _ConfigTreeState extends ConsumerState { const Text("Storages"), for (final File storage in storages) ConfigTile(storage), const Text("Maps"), - for (final File map in maps) ConfigTile(map), + for (final File map in maps) MapTile(map), + const NewMapButton(), ], ); } diff --git a/lib/map_tile.dart b/lib/map_tile.dart new file mode 100644 index 0000000..e822e75 --- /dev/null +++ b/lib/map_tile.dart @@ -0,0 +1,47 @@ +import "dart:io"; + +import "package:flutter/material.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:path/path.dart" as p; + +import "dual_pane.dart"; + +class MapTile extends ConsumerWidget { + final File configFile; + + const MapTile(this.configFile, {super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final File? openConfig = ref.watch(openConfigProvider); + + return ListTile( + trailing: IconButton( + icon: const Icon(Icons.delete), + onPressed: () { + //TODO: Show confirmation dialog + + //close the editor if it's open on that file + if (openConfig != null && p.equals(openConfig.path, configFile.path)) { + ref.read(openConfigProvider.notifier).close(); + } + //delete the file next frame, to ensure the editor is closed + WidgetsBinding.instance.addPostFrameCallback((_) { + configFile.delete(); + }); + }, + ), + title: Text(_toHuman(configFile)), + onTap: () { + ref.read(openConfigProvider.notifier).open(configFile); + }, + selected: openConfig != null && p.equals(openConfig.path, configFile.path), + ); + } + + static String _toHuman(File file) { + final String name = p.basename(file.path).replaceAll(".conf", ""); + if (name == "Sql") return "SQL"; + return name; + } +} diff --git a/lib/new_map_button.dart b/lib/new_map_button.dart new file mode 100644 index 0000000..36ae3df --- /dev/null +++ b/lib/new_map_button.dart @@ -0,0 +1,53 @@ +import "dart:io"; +import "dart:math"; + +import "package:flutter/material.dart"; +import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:path/path.dart" as p; + +import "main.dart"; + +class NewMapButton extends ConsumerWidget { + const NewMapButton({super.key}); + + @override + Widget build(BuildContext context, WidgetRef ref) { + return Padding( + padding: const EdgeInsets.all(8), + child: ListTile( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(80), + ), + tileColor: Theme.of(context).colorScheme.secondary, + title: const Row( + children: [ + Icon(Icons.add), + SizedBox(width: 8), + Text("New map"), + ], + ), + onTap: () { + Directory? projectDirectory = ref.read(projectDirectoryProvider); + if (projectDirectory == null) return; + + File templateConfig = File( + p.join(projectDirectory.path, "config", "map-templates", "overworld.conf"), + ); + + File newConfig = File( + p.join(projectDirectory.path, "config", "maps", + "new-map-${Random().nextInt(999)}.conf"), + ); + + templateConfig.copySync(newConfig.path); + + //TODO: Show options dialog + // - which template? (overworld, nether, end) + // - map name? (gets turned into a map ID and compared against existing map IDs) + // other options won't be in this initial dialog, but in the actual config screen + // which will get opened after this dialog + }, + ), + ); + } +} diff --git a/lib/path_picker_button.dart b/lib/path_picker_button.dart index 24d034e..b04a5e6 100644 --- a/lib/path_picker_button.dart +++ b/lib/path_picker_button.dart @@ -95,6 +95,12 @@ class _PathPickerButtonState extends ConsumerState { throw Exception("BlueMap CLI JAR failed to run!"); } + // == Turn default maps directory into templates directory == + final Directory mapsDir = + Directory(p.join(projectDirectory.path, "config", "maps")); + mapsDir.renameSync(p.join(projectDirectory.path, "config", "map-templates")); + mapsDir.createSync(); //recreate maps dir (now empty) + Prefs.instance.projectPath = projectDirectory.path; ref.invalidate(projectDirectoryProvider); },