11package redxax .oxy .remotely .explorer ;
22
3- import net .minecraft .client .MinecraftClient ;
3+ import com .google .common .reflect .TypeToken ;
4+ import com .google .gson .Gson ;
45import net .minecraft .client .gui .screen .Screen ;
56import net .minecraft .text .Text ;
67import org .lwjgl .glfw .GLFW ;
1617import redxax .oxy .remotely .util .Sound ;
1718
1819import java .awt .image .BufferedImage ;
20+ import java .io .IOException ;
21+ import java .nio .file .Files ;
1922import java .nio .file .Path ;
2023import java .nio .file .Paths ;
2124import java .util .*;
2730import static redxax .oxy .remotely .util .searchUtils .isFuzzyMatch ;
2831
2932public class FileExplorerScreen extends ReScreen {
30- private final MinecraftClient minecraftClient ;
3133 private final Screen parent ;
3234 private final ServerInfo serverInfo ;
3335 private final RemotelyCoreAPI fileAPI ;
@@ -37,7 +39,7 @@ public class FileExplorerScreen extends ReScreen {
3739 private final List <FileEntryWidget > allWidgets = new CopyOnWriteArrayList <>();
3840 private final boolean importMode ;
3941 private ContextMenuWidget itemsContextMenu ;
40-
42+ private Map < Container , Path > containerPaths = new HashMap <>();
4143 public static BufferedImage fileIcon ;
4244 public static BufferedImage folderIcon ;
4345 public static BufferedImage pinIcon ;
@@ -50,13 +52,12 @@ public class FileExplorerScreen extends ReScreen {
5052 ".bash" , ".fish" , ".toml" , ".mcfunction" , ".nbt"
5153 );
5254
53- public FileExplorerScreen (MinecraftClient mc , Screen parent , ServerInfo info ) {
54- this (mc , parent , info , false );
55+ public FileExplorerScreen (Screen parent , ServerInfo info ) {
56+ this (parent , info , false );
5557 }
5658
57- public FileExplorerScreen (MinecraftClient mc , Screen parent , ServerInfo info , boolean importMode ) {
59+ public FileExplorerScreen (Screen parent , ServerInfo info , boolean importMode ) {
5860 super (Text .literal ("File Explorer" ));
59- this .minecraftClient = mc ;
6061 this .parent = parent ;
6162 this .serverInfo = info ;
6263 this .importMode = importMode ;
@@ -94,13 +95,13 @@ protected void init() {
9495 .addLeft ("/assets/remotely/icons/goforward.png" , this ::navigateBack , "Go Up" )
9596 .addLeft ("/assets/remotely/icons/newFile.png" , this ::createNewFile , "Create New" )
9697 .addLeft ("/assets/remotely/icons/external.png" , this ::openExternally , "Open Externally" )
97- .addRight ("/assets/remotely/icons/close.png" , () -> minecraftClient .setScreen (parent ), "Close" )
98+ .addRight ("/assets/remotely/icons/close.png" , () -> client .setScreen (parent ), "Close" )
9899 .addRight ("/assets/remotely/icons/paste.png" , this ::paste , "Paste" )
99100 .addRight ("/assets/remotely/icons/copy.png" , this ::copy , "Copy" )
100101 .addRight ("/assets/remotely/icons/favorite.png" , this ::toggleFavorites , "Toggle Favorites" )
101102 .setSearchMode (searchMode , true )
102103 .build ();
103- tabs ().builder ().allowReorder (true ).allowAdd (true ).position (5 , 36 ).size (width - 5 , 18 ).onTabClosed (this ::onTabClosed ).onPlusButtonClicked (this ::onNewTab ).build ();
104+ tabs ().builder ().allowReorder (true ).allowAdd (true ).position (5 , 36 ).size (width - 5 , 18 ).onTabClosed (this ::onTabClosed ).onPlusButtonClicked (this ::onNewTab ).onTabsReordered ( this :: onTabReordered ). onTabSelected ( this :: onTabSelected ). build ();
104105
105106 TabsManager .Tab initialTab = tabs ().addTab (getTabName (currentPath ), explorerContainer );
106107 tabs ().setActiveTab (0 );
@@ -149,14 +150,15 @@ protected void init() {
149150 }, "" )
150151 .addIconItem ("Copy Path" , "snippets.png" , () -> {
151152 FileEntryWidget firstWidget = (FileEntryWidget ) currentSelectedWidgets .getFirst ();
152- minecraftClient .keyboard .setClipboard (firstWidget .getFileEntry ().path .toString ());
153+ client .keyboard .setClipboard (firstWidget .getFileEntry ().path .toString ());
153154 }, "" )
154155 .addIconItem ("Undo" , "goback.png" , this ::undo , "" )
155156 .addIconItem ("Refresh" , "reload.png" , () -> {
156157 playSound (Sound .CLICK );
157158 loadDirectory (currentPath );
158159 }, "" ).build ();
159160 addDrawableChild (itemsContextMenu );
161+ loadExplorerTabs ();
160162 }
161163
162164 private void loadIcons () {
@@ -188,23 +190,19 @@ private void loadIcons() {
188190 private void loadDirectory (Path path ) {
189191 loading = true ;
190192 currentPath = path ;
193+ containerPaths .put (activeContainer , currentPath );
191194 activeContainer .clearWidgets ();
192195 allWidgets .clear ();
193196 currentSelectedWidgets .clear ();
194-
195- fileAPI .listDirectory (path ).thenAccept (entries -> {
196- minecraftClient .execute (() -> {
197- for (RemotelyCoreAPI .FileEntry entry : entries ) {
198- FileEntryWidget widget = new FileEntryWidget .Builder (entry , fileAPI , serverInfo .isRemote , favoritePaths , favoritePathsLock ).size (0 , 20 )
199- .onClick (this ::onFileDoubleClick ).onRightClick (this ::onFileRightClick ).build ();
200-
201- activeContainer .addWidget (widget );
202- allWidgets .add (widget );
203- }
204- loading = false ;
205- });
206- }).exceptionally (e -> {
207- minecraftClient .execute (() -> {
197+ fileAPI .listDirectory (path ).thenAccept (entries -> client .execute (() -> {
198+ for (RemotelyCoreAPI .FileEntry entry : entries ) {
199+ FileEntryWidget widget = new FileEntryWidget .Builder (entry , fileAPI , serverInfo .isRemote , favoritePaths , favoritePathsLock ).size (0 , 20 ).onClick (this ::onFileDoubleClick ).onRightClick (this ::onFileRightClick ).build ();
200+ activeContainer .addWidget (widget );
201+ allWidgets .add (widget );
202+ }
203+ loading = false ;
204+ })).exceptionally (e -> {
205+ client .execute (() -> {
208206 loading = false ;
209207 new Notification ("Failed to load directory: " , e .getMessage (), Notification .Type .ERROR );
210208 });
@@ -219,15 +217,14 @@ private void onFileDoubleClick(FileEntryWidget widget) {
219217 navigateTo (entry .path );
220218 } else {
221219 if (importMode && entry .path .getFileName ().toString ().equalsIgnoreCase ("server.jar" )) {
222- minecraftClient .setScreen (parent );
220+ client .setScreen (parent );
223221 return ;
224222 }
225223 if (isSupportedFile (entry .path )) {
226- minecraftClient .setScreen (new FileEditorScreen (minecraftClient , this , entry .path , serverInfo ));
224+ client .setScreen (new FileEditorScreen (client , this , entry .path , serverInfo ));
227225 } else {
228226 openExternally (entry .path );
229227 }
230-
231228 }
232229 }
233230
@@ -238,16 +235,37 @@ private void onFileRightClick(FileEntryWidget widget) {
238235
239236 private void onTabClosed (TabsManager .Tab tab ) {
240237 if (tabs ().getTabs ().isEmpty ()) {
241- minecraftClient .setScreen (parent );
238+ client .setScreen (parent );
239+ }
240+ saveExplorerTabs ();
241+ }
242+
243+ private void onTabReordered (List <TabsManager .Tab > tabs ) {
244+ saveExplorerTabs ();
245+ }
246+
247+ private void onTabSelected (TabsManager .Tab tab ) {
248+ if (tab .getContainer ().getWidgets ().isEmpty ()) {
249+ Path path = containerPaths .get (tab .getContainer ());
250+ if (path != null ) {
251+ loadDirectory (path );
252+ }
242253 }
243254 }
244255
245256 public void onNewTab () {
246257 Path homePath = serverInfo .isRemote ? Paths .get ("/" ) : Paths .get (System .getProperty ("user.home" )).toAbsolutePath ().normalize ();
247- createTab (homePath );
258+ createTab (homePath , false );
248259 }
249260
250- public void createTab (Path newPath ) {
261+ public void createTab (Path newPath , boolean allowDuplicate ) {
262+ for (TabsManager .Tab tab : tabs ().getTabs ()) {
263+ if (containerPaths .get (tab .getContainer ()).equals (newPath ) && !allowDuplicate ) {
264+ tabs ().setActiveTab (tabs ().getTabs ().indexOf (tab ));
265+ return ;
266+ }
267+ }
268+ playSound (Sound .CREATE );
251269 Container newContainer = createContainer (5 , 60 , width - 10 , height - 5 );
252270 newContainer .columns (1 ).padding (2 ).enableSelecting (true ).layoutStyle (Container .LayoutStyle .RESTRICTED );
253271 TabsManager .Tab newTab = tabs ().addTab (getTabName (newPath ), newContainer );
@@ -288,15 +306,17 @@ private void navigateTo(Path path) {
288306 if (activeTab != null ) {
289307 activeTab .setName (getTabName (path ));
290308 }
309+ saveExplorerTabs ();
291310 }
292311
293312 private void navigateUp () {
294313 Path parentPath = currentPath .getParent ();
295314 if (parentPath != null ) {
296315 navigateTo (parentPath );
297316 } else {
298- minecraftClient .setScreen (parent );
317+ client .setScreen (parent );
299318 }
319+ saveExplorerTabs ();
300320 }
301321
302322 private void navigateBack () {
@@ -356,15 +376,66 @@ private void toggleFavorites() {
356376 }
357377 }
358378
359- private void updateButtonStates () {
360- boolean hasSelection = !currentSelectedWidgets .isEmpty ();
361- // Update button visibility based on selection
362- }
363-
364379 private String getTabName (Path path ) {
365380 return path .getFileName () != null ? path .getFileName ().toString () : path .toString ();
366381 }
367382
383+ public void saveExplorerTabs () {
384+ try {
385+ Path tabsFile = Paths .get (String .valueOf (remotelyDir ), "data" , "file_explorer_tabs.json" );
386+ if (!Files .exists (tabsFile .getParent ())) {
387+ Files .createDirectories (tabsFile .getParent ());
388+ }
389+ Map <String , Object > data = new HashMap <>();
390+ List <Map <String , Object >> tabList = new ArrayList <>();
391+ List <TabsManager .Tab > tabs = tabs ().getTabs ();
392+ for (TabsManager .Tab tab : tabs ) {
393+ Map <String , Object > tabMap = new HashMap <>();
394+ Path tabPath = containerPaths .get (tab .getContainer ());
395+ tabMap .put ("path" , tabPath .toString ());
396+ tabMap .put ("isRemote" , serverInfo .isRemote );
397+ tabMap .put ("scrollOffset" , 0 );
398+ if (serverInfo .isRemote && serverInfo .remoteHost != null ) {
399+ Map <String , Object > hostMap = new HashMap <>();
400+ hostMap .put ("user" , serverInfo .remoteHost .getUser ());
401+ hostMap .put ("ip" , serverInfo .remoteHost .getIp ());
402+ hostMap .put ("port" , serverInfo .remoteHost .getPort ());
403+ hostMap .put ("password" , serverInfo .remoteHost .getPassword ());
404+ tabMap .put ("remoteHostInfo" , hostMap );
405+ }
406+ tabList .add (tabMap );
407+ }
408+ data .put ("tabs" , tabList );
409+ data .put ("currentTabIndex" , tabs ().getActiveTabIndex ());
410+ String json = new Gson ().toJson (data );
411+ Files .write (tabsFile , json .getBytes ());
412+ } catch (IOException e ) {
413+ throw new RuntimeException (e );
414+ }
415+ }
416+
417+ public void loadExplorerTabs () {
418+ try {
419+ Path tabsFile = Paths .get (String .valueOf (remotelyDir ), "data" , "file_explorer_tabs.json" );
420+ if (Files .exists (tabsFile )) {
421+ String json = new String (Files .readAllBytes (tabsFile ));
422+ Map <String , Object > data = new Gson ().fromJson (json , new TypeToken <Map <String , Object >>() {}.getType ());
423+ List <Map <String , Object >> tabList = (List <Map <String , Object >>) data .get ("tabs" );
424+ int activeTabIndex = ((Number ) data .getOrDefault ("currentTabIndex" , 0 )).intValue ();
425+ for (Map <String , Object > tabMap : tabList ) {
426+ String pathStr = (String ) tabMap .get ("path" );
427+ Path path = Paths .get (pathStr );
428+ createTab (path , false );
429+ loadDirectory (path );
430+ System .out .println ("Loaded tab for path: " + path );
431+ }
432+ tabs ().setActiveTab (activeTabIndex );
433+ }
434+ } catch (IOException e ) {
435+ throw new RuntimeException (e );
436+ }
437+ }
438+
368439 public static BufferedImage getIconForFile (Path file ) {
369440 String fileName = file .getFileName ().toString ().toLowerCase ();
370441 if (fileName .endsWith (".exe" )) return appsIcon ;
@@ -401,6 +472,13 @@ public boolean mouseClicked(double mouseX, double mouseY, int button) {
401472 }
402473 if (button == 1 ) {
403474 itemsContextMenu .show ((int ) mouseX , (int ) mouseY );
475+ if (currentSelectedWidgets .isEmpty ()) {
476+ for (AnimatedWidget widget : activeContainer .getWidgets ()) {
477+ if (widget .isMouseOver (mouseX , mouseY )) {
478+ activeContainer .addSelectedWidget (widget );
479+ }
480+ }
481+ }
404482 return true ;
405483 }
406484 return super .mouseClicked (mouseX , mouseY , button );
@@ -434,7 +512,10 @@ public boolean keyPressed(int keyCode, int scanCode, int modifiers) {
434512 }
435513 }
436514 if (keyCode == GLFW .GLFW_KEY_ENTER ) {
437-
515+ for (FileEntryWidget widget : allWidgets ) {
516+ if (widget .isFocused ()) onFileDoubleClick (widget );
517+ }
518+ return true ;
438519 }
439520 if (keyCode == GLFW .GLFW_KEY_DELETE ) {
440521 deleteSelected ();
@@ -478,6 +559,16 @@ private void undo() {
478559 }
479560 }
480561
562+ @ Override
563+ public void setActiveContainer (Container container ) {
564+ super .setActiveContainer (container );
565+ if (containerPaths .containsKey (container )) {
566+ currentPath = containerPaths .get (container );
567+ } else {
568+ containerPaths .put (container , currentPath );
569+ }
570+ }
571+
481572 @ Override
482573 public void onDisplayed () {
483574 playSound (Sound .FILEEXPLORER );
0 commit comments