From 5868fcfd337828f1133590a51cd22be37fe404af Mon Sep 17 00:00:00 2001 From: crschnick Date: Sat, 16 Nov 2024 07:13:24 +0000 Subject: [PATCH] Squash merge xpipe 13 feature branches into master --- CONTRIBUTING.md | 6 +- app/build.gradle | 8 +- .../app/beacon/impl/AskpassExchangeImpl.java | 22 + .../impl/ConnectionBrowseExchangeImpl.java | 6 +- .../impl/ConnectionRefreshExchangeImpl.java | 4 +- .../impl/ConnectionTerminalExchangeImpl.java | 9 +- .../beacon/impl/DaemonOpenExchangeImpl.java | 2 +- .../beacon/impl/ShellStartExchangeImpl.java | 6 +- .../beacon/impl/SshLaunchExchangeImpl.java | 2 +- .../impl/TerminalLaunchExchangeImpl.java | 2 +- .../beacon/impl/TerminalWaitExchangeImpl.java | 6 +- .../BrowserAbstractSessionModel.java | 8 +- ...ava => BrowserFileChooserSessionComp.java} | 42 +- ...va => BrowserFileChooserSessionModel.java} | 24 +- ...nComp.java => BrowserFullSessionComp.java} | 186 +++-- .../app/browser/BrowserFullSessionModel.java | 222 ++++++ .../xpipe/app/browser/BrowserSessionTab.java | 41 + .../{session => }/BrowserSessionTabsComp.java | 226 ++++-- .../app/browser/BrowserStoreSessionTab.java | 38 + .../app/browser/action/BrowserAction.java | 28 +- ...java => BrowserApplicationPathAction.java} | 8 +- ...chAction.java => BrowserBranchAction.java} | 8 +- ...LeafAction.java => BrowserLeafAction.java} | 24 +- .../{ => file}/BrowserBreadcrumbBar.java | 11 +- .../browser/{ => file}/BrowserClipboard.java | 7 +- .../BrowserConnectionListComp.java} | 14 +- .../BrowserConnectionListFilterComp.java} | 10 +- .../app/browser/file/BrowserContextMenu.java | 5 +- .../app/browser/file/BrowserFileListComp.java | 255 ++---- .../file/BrowserFileListCompEntry.java | 10 +- .../BrowserFileListFilterComp.java} | 21 +- .../browser/file/BrowserFileListModel.java | 8 +- .../browser/file/BrowserFileListNameCell.java | 202 +++++ .../browser/{ => file}/BrowserFileOpener.java | 11 +- .../browser/file/BrowserFileOverviewComp.java | 11 +- .../BrowserFileSelectionListComp.java} | 19 +- .../BrowserFileSystemCache.java} | 8 +- ...lper.java => BrowserFileSystemHelper.java} | 11 +- .../BrowserFileSystemHistory.java} | 4 +- .../BrowserFileSystemSavedState.java} | 39 +- .../BrowserFileSystemTabComp.java} | 32 +- .../BrowserFileSystemTabModel.java} | 127 +-- .../file/BrowserFileTransferOperation.java | 3 +- .../{ => file}/BrowserGreetingComp.java | 6 +- .../BrowserHistorySavedState.java} | 4 +- .../BrowserHistorySavedStateImpl.java} | 30 +- .../BrowserHistoryTabComp.java} | 41 +- .../browser/file/BrowserHistoryTabModel.java | 46 ++ ...ystem.java => BrowserLocalFileSystem.java} | 2 +- .../BrowserNavBarComp.java} | 117 +-- .../{ => file}/BrowserOverviewComp.java | 19 +- .../file/BrowserQuickAccessButtonComp.java | 9 +- .../file/BrowserQuickAccessContextMenu.java | 12 +- .../{ => file}/BrowserStatusBarComp.java | 21 +- .../file/BrowserTerminalDockTabModel.java | 164 ++++ .../{ => file}/BrowserTransferComp.java | 168 ++-- .../{ => file}/BrowserTransferModel.java | 43 +- .../{ => file}/BrowserTransferProgress.java | 2 +- .../icon/BrowserIconDirectoryType.java | 6 +- .../app/browser/icon/BrowserIconFileType.java | 4 +- ...onManager.java => BrowserIconManager.java} | 2 +- ...onVariant.java => BrowserIconVariant.java} | 6 +- .../xpipe/app/browser/icon/BrowserIcons.java | 6 +- .../browser/session/BrowserSessionModel.java | 106 --- .../browser/session/BrowserSessionTab.java | 36 - .../io/xpipe/app/{fxcomps => comp}/Comp.java | 16 +- .../app/{fxcomps => comp}/CompStructure.java | 2 +- .../io/xpipe/app/comp/DeveloperTabComp.java | 59 -- .../io/xpipe/app/{fxcomps => comp}/README.md | 0 .../app/{fxcomps => comp}/SimpleComp.java | 2 +- .../SimpleCompStructure.java | 2 +- .../{fxcomps => comp}/augment/Augment.java | 6 +- .../augment/ContextMenuAugment.java | 4 +- .../augment/GrowAugment.java | 4 +- .../impl => comp/base}/AnchorComp.java | 8 +- .../app/comp/{ => base}/AppLayoutComp.java | 12 +- .../app/comp/base/BackgroundImageComp.java | 62 -- .../io/xpipe/app/comp/base/BigIconButton.java | 66 -- .../io/xpipe/app/comp/base/ButtonComp.java | 8 +- .../impl => comp/base}/ChoiceComp.java | 10 +- .../impl => comp/base}/ChoicePaneComp.java | 10 +- .../base}/ComboTextFieldComp.java | 10 +- .../ContextualFileReferenceChoiceComp.java | 13 +- .../io/xpipe/app/comp/base/CountComp.java | 8 +- .../io/xpipe/app/comp/base/DialogComp.java | 6 +- .../io/xpipe/app/comp/base/DropdownComp.java | 8 +- .../xpipe/app/comp/base/ErrorOverlayComp.java | 6 +- .../app/comp/base/FileDropOverlayComp.java | 4 +- .../impl => comp/base}/FilterComp.java | 11 +- .../io/xpipe/app/comp/base/FontIconComp.java | 6 +- .../impl => comp/base}/GrowPaneComp.java | 8 +- .../impl => comp/base}/HorizontalComp.java | 8 +- .../impl => comp/base}/IconButtonComp.java | 12 +- .../impl => comp/base}/IntComboFieldComp.java | 10 +- .../impl => comp/base}/IntFieldComp.java | 10 +- .../app/comp/base/IntegratedTextAreaComp.java | 15 +- .../impl => comp/base}/LabelComp.java | 10 +- .../app/comp/base/LazyTextFieldComp.java | 8 +- ...itPaneComp.java => LeftSplitPaneComp.java} | 12 +- .../xpipe/app/comp/base/ListBoxViewComp.java | 10 +- .../xpipe/app/comp/base/ListSelectorComp.java | 2 +- .../app/comp/base/LoadingOverlayComp.java | 8 +- .../io/xpipe/app/comp/base/MarkdownComp.java | 8 +- .../app/comp/base/MarkdownEditorComp.java | 5 +- .../xpipe/app/comp/base/ModalOverlayComp.java | 6 +- .../xpipe/app/comp/base/MultiContentComp.java | 6 +- .../impl => comp/base}/OptionsComp.java | 11 +- .../app/comp/base/PopupMenuButtonComp.java | 4 +- .../impl => comp/base}/PrettyImageComp.java | 6 +- .../impl => comp/base}/PrettyImageHelper.java | 6 +- .../impl => comp/base}/PrettySvgComp.java | 6 +- .../impl => comp/base}/ScrollComp.java | 8 +- .../impl => comp/base}/SecretFieldComp.java | 9 +- .../xpipe/app/comp/base/SideMenuBarComp.java | 50 +- .../app/comp/base/SimpleTitledPaneComp.java | 6 +- .../impl => comp/base}/StackComp.java | 8 +- .../impl => comp/base}/SvgHelper.java | 2 +- .../{fxcomps/impl => comp/base}/SvgView.java | 6 +- .../impl => comp/base}/TextAreaComp.java | 8 +- .../impl => comp/base}/TextFieldComp.java | 10 +- .../xpipe/app/comp/base/TileButtonComp.java | 8 +- .../xpipe/app/comp/base/TitledPaneComp.java | 6 +- .../impl => comp/base}/ToggleGroupComp.java | 10 +- .../xpipe/app/comp/base/ToggleSwitchComp.java | 6 +- .../impl => comp/base}/TooltipAugment.java | 8 +- .../io/xpipe/app/comp/base/VBoxViewComp.java | 8 +- .../impl => comp/base}/VerticalComp.java | 10 +- .../app/comp/store/DenseStoreEntryComp.java | 15 +- .../app/comp/{base => store}/OsLogoComp.java | 11 +- .../comp/store/StandardStoreEntryComp.java | 20 +- .../xpipe/app/comp/store/StoreActiveComp.java | 38 + .../store}/StoreCategoryComp.java | 19 +- .../store}/StoreCategoryListComp.java | 6 +- .../app/comp/store/StoreCategoryWrapper.java | 2 +- .../store/StoreChoiceComp.java} | 26 +- .../app/comp/store/StoreCreationComp.java | 58 +- .../app/comp/store/StoreCreationMenu.java | 10 +- .../xpipe/app/comp/store/StoreEntryComp.java | 27 +- .../app/comp/store/StoreEntryListComp.java | 63 +- .../store/StoreEntryListOverviewComp.java | 33 +- .../app/comp/store/StoreEntryWrapper.java | 20 +- .../app/comp/store/StoreIconChoiceComp.java | 4 +- .../comp/store/StoreIconChoiceDialogComp.java | 8 +- .../xpipe/app/comp/store/StoreIconComp.java | 6 +- .../xpipe/app/comp/store/StoreIntroComp.java | 6 +- .../xpipe/app/comp/store/StoreLayoutComp.java | 6 +- .../store/StoreListChoiceComp.java} | 17 +- .../app/comp/store/StoreNotFoundComp.java | 2 +- .../xpipe/app/comp/store/StoreNotesComp.java | 8 +- .../comp/store/StoreProviderChoiceComp.java | 11 +- .../store/StoreQuickAccessButtonComp.java | 10 +- .../app/comp/store/StoreScriptsIntroComp.java | 126 +++ .../io/xpipe/app/comp/store/StoreSection.java | 6 +- .../app/comp/store/StoreSectionComp.java | 14 +- .../app/comp/store/StoreSectionMiniComp.java | 13 +- .../app/comp/store/StoreSidebarComp.java | 8 +- .../comp/{base => store}/StoreToggleComp.java | 9 +- .../xpipe/app/comp/store/StoreViewState.java | 6 +- .../comp/{base => store}/SystemStateComp.java | 9 +- app/src/main/java/io/xpipe/app/core/App.java | 4 +- .../xpipe/app/core/AppActionLinkDetector.java | 2 +- .../main/java/io/xpipe/app/core/AppCache.java | 68 +- .../xpipe/app/core/AppDesktopIntegration.java | 2 +- .../xpipe/app/core/AppExtensionManager.java | 2 +- .../java/io/xpipe/app/core/AppGreetings.java | 4 +- .../main/java/io/xpipe/app/core/AppI18n.java | 2 +- .../io/xpipe/app/core/AppLayoutModel.java | 45 +- .../main/java/io/xpipe/app/core/AppLogs.java | 3 +- .../java/io/xpipe/app/core/AppProperties.java | 21 + .../main/java/io/xpipe/app/core/AppState.java | 48 -- .../main/java/io/xpipe/app/core/AppStyle.java | 27 +- .../main/java/io/xpipe/app/core/AppTheme.java | 29 +- .../io/xpipe/app/core/check/AppAvCheck.java | 3 +- .../app/core/check/AppJavaOptionsCheck.java | 2 +- .../app/core/check/AppTestCommandCheck.java | 30 + .../{ => core}/launcher/LauncherCommand.java | 2 +- .../{ => core}/launcher/LauncherInput.java | 6 +- .../launcher/LauncherModeConverter.java | 2 +- .../java/io/xpipe/app/core/mode/BaseMode.java | 9 +- .../java/io/xpipe/app/core/mode/GuiMode.java | 12 +- .../io/xpipe/app/core/mode/OperationMode.java | 11 +- .../io/xpipe/app/core/mode/PlatformMode.java | 4 +- .../java/io/xpipe/app/core/mode/TrayMode.java | 2 +- .../xpipe/app/core/window/AppMainWindow.java | 7 +- .../app/core/window/AppWindowHelper.java | 2 +- .../xpipe/app/core/window/ModifiedStage.java | 7 +- .../core/window/NativeWinWindowControl.java | 83 +- .../io/xpipe/app/ext/DataStoreProvider.java | 6 +- .../app/ext/EnabledParentStoreProvider.java | 4 +- .../main/java/io/xpipe/app/ext/GuiDialog.java | 2 +- .../java/io/xpipe/app/ext/LocalStore.java | 23 +- .../java/io/xpipe/app/ext/PrefsHandler.java | 4 +- .../java/io/xpipe/app/ext/ScanProvider.java | 16 +- .../xpipe/app/ext/ShellControlFunction.java | 8 + .../ext/ShellControlParentStoreFunction.java | 14 + .../java/io/xpipe/app/ext/ShellSession.java | 65 ++ .../java/io/xpipe/app/ext/ShellStore.java | 78 ++ .../ext/SingletonSessionStoreProvider.java | 10 +- .../io/xpipe/app/ext/StubShellControl.java | 13 + .../io/xpipe/app/ext/WrapperShellControl.java | 338 ++++++++ .../augment/DragOverPseudoClassAugment.java | 25 - .../app/fxcomps/augment/DraggableAugment.java | 47 -- .../app/fxcomps/impl/CharChoiceComp.java | 46 -- .../io/xpipe/app/fxcomps/impl/CharComp.java | 35 - .../xpipe/app/fxcomps/impl/CodeSnippet.java | 126 --- .../app/fxcomps/impl/CodeSnippetComp.java | 128 --- .../io/xpipe/app/fxcomps/impl/PaneComp.java | 28 - .../io/xpipe/app/issue/ErrorDetailsComp.java | 4 +- .../io/xpipe/app/issue/ErrorHandlerComp.java | 6 +- .../xpipe/app/issue/GuiErrorHandlerBase.java | 1 - .../xpipe/app/issue/SentryErrorHandler.java | 13 +- .../xpipe/app/issue/TerminalErrorHandler.java | 3 +- .../io/xpipe/app/issue/UserReportComp.java | 4 +- .../io/xpipe/app/prefs/AboutCategory.java | 6 +- .../java/io/xpipe/app/prefs/AppPrefs.java | 241 +++--- .../io/xpipe/app/prefs/AppPrefsCategory.java | 2 +- .../java/io/xpipe/app/prefs/AppPrefsComp.java | 7 +- .../xpipe/app/prefs/AppPrefsSidebarComp.java | 27 +- .../xpipe/app/prefs/AppearanceCategory.java | 24 +- .../xpipe/app/prefs/CloseBehaviourAlert.java | 2 +- ...Category.java => ConnectionsCategory.java} | 18 +- .../io/xpipe/app/prefs/DeveloperCategory.java | 6 +- .../io/xpipe/app/prefs/EditorCategory.java | 12 +- .../xpipe/app/prefs/FileBrowserCategory.java | 27 + .../io/xpipe/app/prefs/HttpApiCategory.java | 8 +- .../xpipe/app/prefs/LocalShellCategory.java | 6 +- .../io/xpipe/app/prefs/LockChangeAlert.java | 4 +- .../io/xpipe/app/prefs/LoggingCategory.java | 46 -- .../app/prefs/PasswordManagerCategory.java | 14 +- .../java/io/xpipe/app/prefs/RdpCategory.java | 6 +- .../io/xpipe/app/prefs/SecurityCategory.java | 22 +- .../java/io/xpipe/app/prefs/SshCategory.java | 4 +- .../java/io/xpipe/app/prefs/SyncCategory.java | 20 +- .../io/xpipe/app/prefs/SystemCategory.java | 12 +- .../io/xpipe/app/prefs/TerminalCategory.java | 50 +- .../prefs/ThirdPartyDependencyListComp.java | 6 +- .../xpipe/app/prefs/TroubleshootCategory.java | 4 +- .../io/xpipe/app/prefs/UpdateCheckComp.java | 8 +- .../io/xpipe/app/prefs/VaultCategory.java | 16 +- .../xpipe/app/prefs/WorkspacesCategory.java | 11 +- .../app/storage/DataStateProviderImpl.java | 9 +- .../io/xpipe/app/storage/DataStorage.java | 30 +- .../io/xpipe/app/storage/DataStoreEntry.java | 37 +- .../app/terminal/AlacrittyTerminalType.java | 16 +- .../xpipe/app/terminal/CmdTerminalType.java | 44 ++ .../terminal/ControllableTerminalSession.java | 48 ++ .../app/terminal/CustomTerminalType.java | 6 +- .../app/terminal/ExternalTerminalType.java | 736 +++--------------- .../xpipe/app/terminal/GnomeTerminalType.java | 65 ++ .../xpipe/app/terminal/KittyTerminalType.java | 35 +- .../app/terminal/MobaXTermTerminalType.java | 76 ++ .../app/terminal/PowerShellTerminalType.java | 60 ++ .../xpipe/app/terminal/PwshTerminalType.java | 47 ++ .../app/terminal/SecureCrtTerminalType.java | 72 ++ .../xpipe/app/terminal/TabbyTerminalType.java | 46 +- .../xpipe/app/terminal/TerminalDockComp.java | 129 +++ .../xpipe/app/terminal/TerminalDockModel.java | 169 ++++ .../TerminalLaunchConfiguration.java} | 124 +-- .../TerminalLaunchRequest.java | 28 +- .../TerminalLaunchResult.java | 2 +- .../xpipe/app/terminal/TerminalLauncher.java | 84 ++ .../TerminalLauncherManager.java | 34 +- .../app/terminal/TerminalOpenFormat.java | 7 + .../io/xpipe/app/terminal/TerminalView.java | 228 ++++++ .../app/terminal/TermiusTerminalType.java | 116 +++ .../app/terminal/TrackableTerminalType.java | 8 + .../xpipe/app/terminal/WarpTerminalType.java | 82 ++ .../xpipe/app/terminal/WezTerminalType.java | 28 +- .../app/terminal/WindowsTerminalSession.java | 97 +++ .../app/terminal/WindowsTerminalType.java | 44 +- .../app/terminal/XShellTerminalType.java | 107 +++ .../java/io/xpipe/app/test/TestModule.java | 17 +- .../io/xpipe/app/update/AppDownloads.java | 57 +- .../io/xpipe/app/update/AppInstaller.java | 6 +- .../io/xpipe/app/update/ChocoUpdater.java | 58 -- .../io/xpipe/app/update/GitHubUpdater.java | 10 +- .../io/xpipe/app/update/HomebrewUpdater.java | 52 -- .../io/xpipe/app/update/PortableUpdater.java | 7 +- .../app/update/UpdateAvailableAlert.java | 2 +- .../io/xpipe/app/update/UpdateHandler.java | 40 +- .../app/update/XPipeDistributionType.java | 9 +- .../java/io/xpipe/app/util/AskpassAlert.java | 2 +- .../java/io/xpipe/app/util/AsktextAlert.java | 2 +- .../{fxcomps => }/util/BindingsHelper.java | 4 +- .../java/io/xpipe/app/util/BooleanScope.java | 4 + .../io/xpipe/app/util/ClipboardHelper.java | 1 - .../io/xpipe/app/util/ContextMenuHelper.java | 2 - .../app/util/DataStoreCategoryChoiceComp.java | 3 +- .../io/xpipe/app/util/DataStoreFormatter.java | 46 -- .../util/DerivedObservableList.java | 2 +- .../io/xpipe/app/util/DesktopShortcuts.java | 7 +- .../xpipe/app/util/DiscreteProgressScope.java | 19 - .../java/io/xpipe/app/util/FileOpener.java | 8 +- .../xpipe/app/util/FixedHierarchyStore.java | 12 +- .../java/io/xpipe/app/util/HostHelper.java | 4 +- .../java/io/xpipe/app/util/Hyperlinks.java | 12 +- .../java/io/xpipe/app/util/JfxHelper.java | 2 +- .../app/{fxcomps => }/util/LabelGraphic.java | 6 +- .../io/xpipe/app/util/LicenseProvider.java | 4 +- .../io/xpipe/app/util/LicensedFeature.java | 20 +- .../io/xpipe/app/util/OptionsBuilder.java | 41 +- .../{fxcomps => }/util/PlatformThread.java | 95 +-- .../java/io/xpipe/app/util/ProgressScope.java | 2 - app/src/main/java/io/xpipe/app/util/Rect.java | 9 + .../java/io/xpipe/app/util/ScanAlert.java | 69 +- .../java/io/xpipe/app/util/ScanDialog.java | 114 +-- .../app/util/SecretRetrievalStrategy.java | 7 +- .../util/SecretRetrievalStrategyHelper.java | 6 +- .../io/xpipe/app/util/ShellStoreFormat.java | 105 +++ .../java/io/xpipe/app/util/XPipeSession.java | 37 - app/src/main/java/module-info.java | 11 +- .../io/xpipe/app/resources/img/logo/logo.png | Bin 2092 -> 2437 bytes .../resources/img/os/assistant-24-dark.png | Bin 0 -> 487 bytes .../app/resources/img/os/assistant-24.png | Bin 0 -> 776 bytes .../resources/img/os/assistant-40-dark.png | Bin 0 -> 823 bytes .../app/resources/img/os/assistant-40.png | Bin 0 -> 1100 bytes .../io/xpipe/app/resources/style/bookmark.css | 10 +- .../io/xpipe/app/resources/style/browser.css | 66 +- .../io/xpipe/app/resources/style/category.css | 6 +- .../xpipe/app/resources/style/color-box.css | 4 +- .../io/xpipe/app/resources/style/frame.css | 62 ++ .../xpipe/app/resources/style/header-bars.css | 35 +- .../io/xpipe/app/resources/style/prefs.css | 22 +- .../app/resources/style/sidebar-comp.css | 6 +- .../app/resources/style/store-entry-comp.css | 9 +- .../io/xpipe/app/resources/style/style.css | 76 +- .../app/resources/theme/cupertinoDark.css | 19 +- .../app/resources/theme/cupertinoLight.css | 16 +- .../io/xpipe/app/resources/theme/dark.css | 17 +- .../io/xpipe/app/resources/theme/dracula.css | 17 +- .../io/xpipe/app/resources/theme/light.css | 14 +- .../io/xpipe/app/resources/theme/mocha.css | 44 +- .../io/xpipe/app/resources/theme/nordDark.css | 15 +- .../xpipe/app/resources/theme/nordLight.css | 14 +- beacon/build.gradle | 4 +- .../io/xpipe/beacon/api/AskpassExchange.java | 2 + .../beacon/api/TerminalWaitExchange.java | 2 + build.gradle | 8 +- core/build.gradle | 4 +- .../io/xpipe/core/process/CommandBuilder.java | 1 - .../io/xpipe/core/process/ProcessControl.java | 2 + .../io/xpipe/core/process/ShellControl.java | 8 +- .../io/xpipe/core/process/ShellDumbMode.java | 2 +- .../io/xpipe/core/store/FileSystemStore.java | 2 +- .../xpipe/core/store/NetworkTunnelStore.java | 6 +- .../java/io/xpipe/core/store/ShellStore.java | 33 - .../core/store/ShellValidationContext.java | 24 - .../core/store/SingletonSessionStore.java | 21 +- .../io/xpipe/core/store/ValidatableStore.java | 6 +- .../xpipe/core/store/ValidationContext.java | 8 - .../java/io/xpipe/core/util/Proxyable.java | 8 - dist/build.gradle | 2 +- dist/changelogs/13.0.md | 47 ++ dist/licenses/jackson.properties | 2 +- .../ext/base/action/BrowseStoreAction.java | 6 +- .../base/action/EditScriptStoreAction.java | 67 ++ .../action/RefreshChildrenStoreAction.java | 2 +- .../ext/base/action/RunScriptActionMenu.java | 28 +- .../ext/base/action/SampleStoreAction.java | 4 +- .../ext/base/action/ScanStoreAction.java | 4 +- .../io/xpipe/ext/base/browser/BackAction.java | 16 +- .../browser/BrowseInNativeManagerAction.java | 12 +- .../xpipe/ext/base/browser/ChgrpAction.java | 26 +- .../xpipe/ext/base/browser/ChmodAction.java | 22 +- .../xpipe/ext/base/browser/ChownAction.java | 26 +- .../io/xpipe/ext/base/browser/CopyAction.java | 14 +- .../ext/base/browser/CopyPathAction.java | 62 +- .../xpipe/ext/base/browser/DeleteAction.java | 16 +- .../ext/base/browser/DeleteLinkAction.java | 18 +- .../ext/base/browser/EditFileAction.java | 18 +- .../browser/ExecuteApplicationAction.java | 12 +- .../ext/base/browser/FileTypeAction.java | 6 +- .../ext/base/browser/FollowLinkAction.java | 14 +- .../xpipe/ext/base/browser/ForwardAction.java | 16 +- .../io/xpipe/ext/base/browser/JarAction.java | 8 +- .../io/xpipe/ext/base/browser/JavaAction.java | 4 +- .../xpipe/ext/base/browser/JavapAction.java | 8 +- .../ext/base/browser/MultiExecuteAction.java | 41 +- .../browser/MultiExecuteSelectionAction.java | 42 +- .../xpipe/ext/base/browser/NewItemAction.java | 47 +- .../ext/base/browser/OpenDirectoryAction.java | 14 +- .../browser/OpenDirectoryInNewTabAction.java | 20 +- .../base/browser/OpenFileDefaultAction.java | 16 +- .../ext/base/browser/OpenFileWithAction.java | 16 +- .../browser/OpenNativeFileDetailsAction.java | 12 +- .../ext/base/browser/OpenTerminalAction.java | 37 +- .../xpipe/ext/base/browser/PasteAction.java | 18 +- .../base/browser/RefreshDirectoryAction.java | 16 +- .../xpipe/ext/base/browser/RenameAction.java | 14 +- .../io/xpipe/ext/base/browser/RunAction.java | 10 +- .../ext/base/browser/ToFileCommandAction.java | 12 +- .../browser/compress/BaseCompressAction.java | 50 +- .../browser/compress/BaseUntarAction.java | 16 +- .../browser/compress/BaseUnzipUnixAction.java | 10 +- .../compress/BaseUnzipWindowsAction.java | 16 +- .../DesktopApplicationStoreProvider.java | 10 +- .../desktop/DesktopCommandStoreProvider.java | 10 +- .../base/desktop/DesktopEnvironmentStore.java | 3 +- .../DesktopEnvironmentStoreProvider.java | 18 +- .../ext/base/script/RunScriptAction.java | 47 +- .../base/script/ScriptGroupStoreProvider.java | 12 +- .../script/SimpleScriptStoreProvider.java | 14 +- .../AbstractServiceGroupStoreProvider.java | 9 +- .../base/service/AbstractServiceStore.java | 7 +- .../service/AbstractServiceStoreProvider.java | 8 +- .../service/CustomServiceStoreProvider.java | 4 +- .../base/service/FixedServiceGroupStore.java | 11 +- .../ext/base/store/ShellStoreProvider.java | 24 +- .../ext/base/store/StoreRestartAction.java | 61 ++ ext/base/src/main/java/module-info.java | 3 + ext/jdbc/build.gradle | 4 - ext/jdbc/src/main/java/module-info.java | 1 - get-xpipe.sh | 2 +- gradle/gradle_scripts/extension.gradle | 2 +- gradle/gradle_scripts/junit.gradle | 6 +- img/os/assistant-dark.svg | 19 + img/os/assistant.svg | 16 + img/proc/virsh_icon-16.png | Bin 0 -> 4916 bytes img/proc/virsh_icon-24.png | Bin 0 -> 5560 bytes img/proc/virsh_icon-40.png | Bin 0 -> 7324 bytes img/proc/virsh_icon-80.png | Bin 0 -> 13983 bytes lang/app/strings/translations_da.properties | 54 +- lang/app/strings/translations_de.properties | 54 +- lang/app/strings/translations_en.properties | 56 +- lang/app/strings/translations_es.properties | 54 +- lang/app/strings/translations_fr.properties | 54 +- lang/app/strings/translations_it.properties | 54 +- lang/app/strings/translations_ja.properties | 54 +- lang/app/strings/translations_nl.properties | 54 +- lang/app/strings/translations_pt.properties | 54 +- lang/app/strings/translations_ru.properties | 54 +- lang/app/strings/translations_tr.properties | 54 +- lang/app/strings/translations_zh.properties | 54 +- lang/base/strings/translations_da.properties | 2 + lang/base/strings/translations_de.properties | 2 + lang/base/strings/translations_en.properties | 6 +- lang/base/strings/translations_es.properties | 2 + lang/base/strings/translations_fr.properties | 2 + lang/base/strings/translations_it.properties | 2 + lang/base/strings/translations_ja.properties | 2 + lang/base/strings/translations_nl.properties | 2 + lang/base/strings/translations_pt.properties | 2 + lang/base/strings/translations_ru.properties | 2 + lang/base/strings/translations_tr.properties | 4 +- lang/base/strings/translations_zh.properties | 2 + lang/proc/strings/translations_da.properties | 34 +- lang/proc/strings/translations_de.properties | 30 +- lang/proc/strings/translations_en.properties | 42 +- lang/proc/strings/translations_es.properties | 32 +- lang/proc/strings/translations_fr.properties | 32 +- lang/proc/strings/translations_it.properties | 38 +- lang/proc/strings/translations_ja.properties | 32 +- lang/proc/strings/translations_nl.properties | 34 +- lang/proc/strings/translations_pt.properties | 36 +- lang/proc/strings/translations_ru.properties | 34 +- lang/proc/strings/translations_tr.properties | 40 +- lang/proc/strings/translations_zh.properties | 34 +- lang/proc/texts/sshKeyVm_da.md | 63 -- lang/proc/texts/sshKeyVm_de.md | 63 -- lang/proc/texts/sshKeyVm_en.md | 63 -- lang/proc/texts/sshKeyVm_es.md | 63 -- lang/proc/texts/sshKeyVm_fr.md | 63 -- lang/proc/texts/sshKeyVm_it.md | 63 -- lang/proc/texts/sshKeyVm_ja.md | 63 -- lang/proc/texts/sshKeyVm_nl.md | 63 -- lang/proc/texts/sshKeyVm_pt.md | 63 -- lang/proc/texts/sshKeyVm_ru.md | 63 -- lang/proc/texts/sshKeyVm_tr.md | 63 -- lang/proc/texts/sshKeyVm_zh.md | 63 -- lang/uacc/strings/translations_da.properties | 2 +- lang/uacc/strings/translations_de.properties | 2 +- lang/uacc/strings/translations_en.properties | 2 +- lang/uacc/strings/translations_es.properties | 2 +- lang/uacc/strings/translations_fr.properties | 2 +- lang/uacc/strings/translations_it.properties | 2 +- lang/uacc/strings/translations_ja.properties | 2 +- lang/uacc/strings/translations_nl.properties | 2 +- lang/uacc/strings/translations_pt.properties | 2 +- lang/uacc/strings/translations_ru.properties | 2 +- lang/uacc/strings/translations_tr.properties | 2 +- lang/uacc/strings/translations_zh.properties | 2 +- version | 2 +- 482 files changed, 7425 insertions(+), 5709 deletions(-) rename app/src/main/java/io/xpipe/app/browser/{session => }/BrowserAbstractSessionModel.java (90%) rename app/src/main/java/io/xpipe/app/browser/{session/BrowserChooserComp.java => BrowserFileChooserSessionComp.java} (83%) rename app/src/main/java/io/xpipe/app/browser/{session/BrowserFileChooserModel.java => BrowserFileChooserSessionModel.java} (77%) rename app/src/main/java/io/xpipe/app/browser/{session/BrowserSessionComp.java => BrowserFullSessionComp.java} (50%) create mode 100644 app/src/main/java/io/xpipe/app/browser/BrowserFullSessionModel.java create mode 100644 app/src/main/java/io/xpipe/app/browser/BrowserSessionTab.java rename app/src/main/java/io/xpipe/app/browser/{session => }/BrowserSessionTabsComp.java (61%) create mode 100644 app/src/main/java/io/xpipe/app/browser/BrowserStoreSessionTab.java rename app/src/main/java/io/xpipe/app/browser/action/{ApplicationPathAction.java => BrowserApplicationPathAction.java} (56%) rename app/src/main/java/io/xpipe/app/browser/action/{BranchAction.java => BrowserBranchAction.java} (76%) rename app/src/main/java/io/xpipe/app/browser/action/{LeafAction.java => BrowserLeafAction.java} (79%) rename app/src/main/java/io/xpipe/app/browser/{ => file}/BrowserBreadcrumbBar.java (91%) rename app/src/main/java/io/xpipe/app/browser/{ => file}/BrowserClipboard.java (94%) rename app/src/main/java/io/xpipe/app/browser/{BrowserBookmarkComp.java => file/BrowserConnectionListComp.java} (92%) rename app/src/main/java/io/xpipe/app/browser/{BrowserBookmarkHeaderComp.java => file/BrowserConnectionListFilterComp.java} (89%) rename app/src/main/java/io/xpipe/app/browser/{BrowserFilterComp.java => file/BrowserFileListFilterComp.java} (88%) create mode 100644 app/src/main/java/io/xpipe/app/browser/file/BrowserFileListNameCell.java rename app/src/main/java/io/xpipe/app/browser/{ => file}/BrowserFileOpener.java (91%) rename app/src/main/java/io/xpipe/app/browser/{BrowserSelectionListComp.java => file/BrowserFileSelectionListComp.java} (83%) rename app/src/main/java/io/xpipe/app/browser/{fs/OpenFileSystemCache.java => file/BrowserFileSystemCache.java} (92%) rename app/src/main/java/io/xpipe/app/browser/file/{FileSystemHelper.java => BrowserFileSystemHelper.java} (89%) rename app/src/main/java/io/xpipe/app/browser/{fs/OpenFileSystemHistory.java => file/BrowserFileSystemHistory.java} (96%) rename app/src/main/java/io/xpipe/app/browser/{fs/OpenFileSystemSavedState.java => file/BrowserFileSystemSavedState.java} (78%) rename app/src/main/java/io/xpipe/app/browser/{fs/OpenFileSystemComp.java => file/BrowserFileSystemTabComp.java} (88%) rename app/src/main/java/io/xpipe/app/browser/{fs/OpenFileSystemModel.java => file/BrowserFileSystemTabModel.java} (80%) rename app/src/main/java/io/xpipe/app/browser/{ => file}/BrowserGreetingComp.java (90%) rename app/src/main/java/io/xpipe/app/browser/{BrowserSavedState.java => file/BrowserHistorySavedState.java} (83%) rename app/src/main/java/io/xpipe/app/browser/{BrowserSavedStateImpl.java => file/BrowserHistorySavedStateImpl.java} (66%) rename app/src/main/java/io/xpipe/app/browser/{BrowserWelcomeComp.java => file/BrowserHistoryTabComp.java} (86%) create mode 100644 app/src/main/java/io/xpipe/app/browser/file/BrowserHistoryTabModel.java rename app/src/main/java/io/xpipe/app/browser/file/{LocalFileSystem.java => BrowserLocalFileSystem.java} (97%) rename app/src/main/java/io/xpipe/app/browser/{BrowserNavBar.java => file/BrowserNavBarComp.java} (89%) rename app/src/main/java/io/xpipe/app/browser/{ => file}/BrowserOverviewComp.java (87%) rename app/src/main/java/io/xpipe/app/browser/{ => file}/BrowserStatusBarComp.java (91%) create mode 100644 app/src/main/java/io/xpipe/app/browser/file/BrowserTerminalDockTabModel.java rename app/src/main/java/io/xpipe/app/browser/{ => file}/BrowserTransferComp.java (51%) rename app/src/main/java/io/xpipe/app/browser/{ => file}/BrowserTransferModel.java (84%) rename app/src/main/java/io/xpipe/app/browser/{ => file}/BrowserTransferProgress.java (96%) rename app/src/main/java/io/xpipe/app/browser/icon/{FileIconManager.java => BrowserIconManager.java} (96%) rename app/src/main/java/io/xpipe/app/browser/icon/{IconVariant.java => BrowserIconVariant.java} (77%) delete mode 100644 app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java delete mode 100644 app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTab.java rename app/src/main/java/io/xpipe/app/{fxcomps => comp}/Comp.java (94%) rename app/src/main/java/io/xpipe/app/{fxcomps => comp}/CompStructure.java (77%) delete mode 100644 app/src/main/java/io/xpipe/app/comp/DeveloperTabComp.java rename app/src/main/java/io/xpipe/app/{fxcomps => comp}/README.md (100%) rename app/src/main/java/io/xpipe/app/{fxcomps => comp}/SimpleComp.java (90%) rename app/src/main/java/io/xpipe/app/{fxcomps => comp}/SimpleCompStructure.java (90%) rename app/src/main/java/io/xpipe/app/{fxcomps => comp}/augment/Augment.java (68%) rename app/src/main/java/io/xpipe/app/{fxcomps => comp}/augment/ContextMenuAugment.java (97%) rename app/src/main/java/io/xpipe/app/{fxcomps => comp}/augment/GrowAugment.java (97%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/AnchorComp.java (77%) rename app/src/main/java/io/xpipe/app/comp/{ => base}/AppLayoutComp.java (89%) delete mode 100644 app/src/main/java/io/xpipe/app/comp/base/BackgroundImageComp.java delete mode 100644 app/src/main/java/io/xpipe/app/comp/base/BigIconButton.java rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/ChoiceComp.java (93%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/ChoicePaneComp.java (93%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/ComboTextFieldComp.java (90%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/ContextualFileReferenceChoiceComp.java (95%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/FilterComp.java (92%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/GrowPaneComp.java (78%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/HorizontalComp.java (82%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/IconButtonComp.java (89%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/IntComboFieldComp.java (92%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/IntFieldComp.java (92%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/LabelComp.java (78%) rename app/src/main/java/io/xpipe/app/comp/base/{SideSplitPaneComp.java => LeftSplitPaneComp.java} (86%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/OptionsComp.java (96%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/PrettyImageComp.java (97%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/PrettyImageHelper.java (95%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/PrettySvgComp.java (97%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/ScrollComp.java (90%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/SecretFieldComp.java (94%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/StackComp.java (79%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/SvgHelper.java (97%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/SvgView.java (97%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/TextAreaComp.java (95%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/TextFieldComp.java (91%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/ToggleGroupComp.java (92%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/TooltipAugment.java (91%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/base}/VerticalComp.java (84%) rename app/src/main/java/io/xpipe/app/comp/{base => store}/OsLogoComp.java (91%) create mode 100644 app/src/main/java/io/xpipe/app/comp/store/StoreActiveComp.java rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/store}/StoreCategoryComp.java (95%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl => comp/store}/StoreCategoryListComp.java (88%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl/DataStoreChoiceComp.java => comp/store/StoreChoiceComp.java} (92%) rename app/src/main/java/io/xpipe/app/{fxcomps/impl/DataStoreListChoiceComp.java => comp/store/StoreListChoiceComp.java} (85%) create mode 100644 app/src/main/java/io/xpipe/app/comp/store/StoreScriptsIntroComp.java rename app/src/main/java/io/xpipe/app/comp/{base => store}/StoreToggleComp.java (96%) rename app/src/main/java/io/xpipe/app/comp/{base => store}/SystemStateComp.java (93%) delete mode 100644 app/src/main/java/io/xpipe/app/core/AppState.java create mode 100644 app/src/main/java/io/xpipe/app/core/check/AppTestCommandCheck.java rename app/src/main/java/io/xpipe/app/{ => core}/launcher/LauncherCommand.java (99%) rename app/src/main/java/io/xpipe/app/{ => core}/launcher/LauncherInput.java (95%) rename app/src/main/java/io/xpipe/app/{ => core}/launcher/LauncherModeConverter.java (88%) create mode 100644 app/src/main/java/io/xpipe/app/ext/ShellControlFunction.java create mode 100644 app/src/main/java/io/xpipe/app/ext/ShellControlParentStoreFunction.java create mode 100644 app/src/main/java/io/xpipe/app/ext/ShellSession.java create mode 100644 app/src/main/java/io/xpipe/app/ext/ShellStore.java create mode 100644 app/src/main/java/io/xpipe/app/ext/StubShellControl.java create mode 100644 app/src/main/java/io/xpipe/app/ext/WrapperShellControl.java delete mode 100644 app/src/main/java/io/xpipe/app/fxcomps/augment/DragOverPseudoClassAugment.java delete mode 100644 app/src/main/java/io/xpipe/app/fxcomps/augment/DraggableAugment.java delete mode 100644 app/src/main/java/io/xpipe/app/fxcomps/impl/CharChoiceComp.java delete mode 100644 app/src/main/java/io/xpipe/app/fxcomps/impl/CharComp.java delete mode 100644 app/src/main/java/io/xpipe/app/fxcomps/impl/CodeSnippet.java delete mode 100644 app/src/main/java/io/xpipe/app/fxcomps/impl/CodeSnippetComp.java delete mode 100644 app/src/main/java/io/xpipe/app/fxcomps/impl/PaneComp.java rename app/src/main/java/io/xpipe/app/prefs/{WorkflowCategory.java => ConnectionsCategory.java} (50%) create mode 100644 app/src/main/java/io/xpipe/app/prefs/FileBrowserCategory.java delete mode 100644 app/src/main/java/io/xpipe/app/prefs/LoggingCategory.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/CmdTerminalType.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/ControllableTerminalSession.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/GnomeTerminalType.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/MobaXTermTerminalType.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/PowerShellTerminalType.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/PwshTerminalType.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/SecureCrtTerminalType.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/TerminalDockComp.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/TerminalDockModel.java rename app/src/main/java/io/xpipe/app/{util/TerminalLauncher.java => terminal/TerminalLaunchConfiguration.java} (52%) rename app/src/main/java/io/xpipe/app/{util => terminal}/TerminalLaunchRequest.java (80%) rename app/src/main/java/io/xpipe/app/{util => terminal}/TerminalLaunchResult.java (91%) create mode 100644 app/src/main/java/io/xpipe/app/terminal/TerminalLauncher.java rename app/src/main/java/io/xpipe/app/{util => terminal}/TerminalLauncherManager.java (71%) create mode 100644 app/src/main/java/io/xpipe/app/terminal/TerminalOpenFormat.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/TerminalView.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/TermiusTerminalType.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/TrackableTerminalType.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/WarpTerminalType.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/WindowsTerminalSession.java create mode 100644 app/src/main/java/io/xpipe/app/terminal/XShellTerminalType.java delete mode 100644 app/src/main/java/io/xpipe/app/update/ChocoUpdater.java delete mode 100644 app/src/main/java/io/xpipe/app/update/HomebrewUpdater.java rename app/src/main/java/io/xpipe/app/{fxcomps => }/util/BindingsHelper.java (96%) rename app/src/main/java/io/xpipe/app/{fxcomps => }/util/DerivedObservableList.java (99%) delete mode 100644 app/src/main/java/io/xpipe/app/util/DiscreteProgressScope.java rename app/src/main/java/io/xpipe/app/{fxcomps => }/util/LabelGraphic.java (92%) rename app/src/main/java/io/xpipe/app/{fxcomps => }/util/PlatformThread.java (72%) create mode 100644 app/src/main/java/io/xpipe/app/util/Rect.java create mode 100644 app/src/main/java/io/xpipe/app/util/ShellStoreFormat.java delete mode 100644 app/src/main/java/io/xpipe/app/util/XPipeSession.java create mode 100644 app/src/main/resources/io/xpipe/app/resources/img/os/assistant-24-dark.png create mode 100644 app/src/main/resources/io/xpipe/app/resources/img/os/assistant-24.png create mode 100644 app/src/main/resources/io/xpipe/app/resources/img/os/assistant-40-dark.png create mode 100644 app/src/main/resources/io/xpipe/app/resources/img/os/assistant-40.png create mode 100644 app/src/main/resources/io/xpipe/app/resources/style/frame.css delete mode 100644 core/src/main/java/io/xpipe/core/store/ShellStore.java delete mode 100644 core/src/main/java/io/xpipe/core/store/ShellValidationContext.java delete mode 100644 core/src/main/java/io/xpipe/core/store/ValidationContext.java delete mode 100644 core/src/main/java/io/xpipe/core/util/Proxyable.java create mode 100644 dist/changelogs/13.0.md create mode 100644 ext/base/src/main/java/io/xpipe/ext/base/action/EditScriptStoreAction.java create mode 100644 ext/base/src/main/java/io/xpipe/ext/base/store/StoreRestartAction.java delete mode 100644 ext/jdbc/build.gradle delete mode 100644 ext/jdbc/src/main/java/module-info.java create mode 100644 img/os/assistant-dark.svg create mode 100644 img/os/assistant.svg create mode 100644 img/proc/virsh_icon-16.png create mode 100644 img/proc/virsh_icon-24.png create mode 100644 img/proc/virsh_icon-40.png create mode 100644 img/proc/virsh_icon-80.png delete mode 100644 lang/proc/texts/sshKeyVm_da.md delete mode 100644 lang/proc/texts/sshKeyVm_de.md delete mode 100644 lang/proc/texts/sshKeyVm_en.md delete mode 100644 lang/proc/texts/sshKeyVm_es.md delete mode 100644 lang/proc/texts/sshKeyVm_fr.md delete mode 100644 lang/proc/texts/sshKeyVm_it.md delete mode 100644 lang/proc/texts/sshKeyVm_ja.md delete mode 100644 lang/proc/texts/sshKeyVm_nl.md delete mode 100644 lang/proc/texts/sshKeyVm_pt.md delete mode 100644 lang/proc/texts/sshKeyVm_ru.md delete mode 100644 lang/proc/texts/sshKeyVm_tr.md delete mode 100644 lang/proc/texts/sshKeyVm_zh.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33648c595..57a77dedb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,7 +27,7 @@ You should therefore always check out the matching version tag for your local re You can find the available version tags at https://github.com/xpipe-io/xpipe/tags. So for example if you currently have XPipe `11.3` installed, you should run `git reset --hard 11.3` first to properly compile against it. -You need to have JDK for Java 21 installed to compile the project. +You need to have JDK for Java 22 installed to compile the project. If you are on Linux or macOS, you can easily accomplish that by running ```bash curl -s "https://get.sdkman.io" | bash @@ -57,7 +57,7 @@ to connect to that debugger through [AttachMe](https://plugins.jetbrains.com/plu ## Modularity and IDEs -All XPipe components target [Java 21](https://openjdk.java.net/projects/jdk/21/) and make full use of the Java Module System (JPMS). +All XPipe components target [Java 22](https://openjdk.java.net/projects/jdk/22/) and make full use of the Java Module System (JPMS). All components are modularized, including all their dependencies. In case a dependency is (sadly) not modularized yet, module information is manually added using [extra-java-module-info](https://github.com/gradlex-org/extra-java-module-info). Further, note that as this is a pretty complicated Java project that fully utilizes modularity, @@ -65,7 +65,7 @@ many IDEs still have problems building this project properly. For example, you can't build this project in eclipse or vscode as it will complain about missing modules. The tested and recommended IDE is IntelliJ. -When setting up the project in IntelliJ, make sure that the correct JDK (Java 21) +When setting up the project in IntelliJ, make sure that the correct JDK (Java 22) is selected both for the project and for gradle itself. ## Contributing guide diff --git a/app/build.gradle b/app/build.gradle index b45cd5cea..bbb76d271 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -23,8 +23,8 @@ dependencies { api project(':beacon') compileOnly 'org.hamcrest:hamcrest:3.0' - compileOnly 'org.junit.jupiter:junit-jupiter-api:5.11.0' - compileOnly 'org.junit.jupiter:junit-jupiter-params:5.11.0' + compileOnly 'org.junit.jupiter:junit-jupiter-api:5.11.3' + compileOnly 'org.junit.jupiter:junit-jupiter-params:5.11.3' api 'com.vladsch.flexmark:flexmark:0.64.8' api 'com.vladsch.flexmark:flexmark-util:0.64.8' @@ -58,8 +58,8 @@ dependencies { api 'org.apache.commons:commons-lang3:3.17.0' api 'io.sentry:sentry:7.14.0' api 'commons-io:commons-io:2.16.1' - api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.17.2" - api group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.17.2" + api group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: "2.18.1" + api group: 'com.fasterxml.jackson.datatype', name: 'jackson-datatype-jsr310', version: "2.18.1" api group: 'org.kordamp.ikonli', name: 'ikonli-material2-pack', version: "12.2.0" api group: 'org.kordamp.ikonli', name: 'ikonli-materialdesign2-pack', version: "12.2.0" api group: 'org.kordamp.ikonli', name: 'ikonli-javafx', version: "12.2.0" diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/AskpassExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/AskpassExchangeImpl.java index b19b14a5a..40cc48259 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/AskpassExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/AskpassExchangeImpl.java @@ -1,5 +1,6 @@ package io.xpipe.app.beacon.impl; +import io.xpipe.app.terminal.TerminalView; import io.xpipe.app.util.AskpassAlert; import io.xpipe.app.util.SecretManager; import io.xpipe.app.util.SecretQueryState; @@ -34,9 +35,30 @@ public class AskpassExchangeImpl extends AskpassExchange { if (p.getState() != SecretQueryState.NORMAL) { throw new BeaconClientException(SecretQueryState.toErrorMessage(p.getState())); } + focusTerminalIfNeeded(msg.getPid()); return Response.builder().value(secret.inPlace()).build(); } + private void focusTerminalIfNeeded(long pid) { + var found = TerminalView.get().findSession(pid); + if (found.isEmpty()) { + return; + } + + var term = TerminalView.get().getTerminalInstances().stream() + .filter(instance -> + instance.getTerminalProcess().equals(found.get().getTerminal())) + .findFirst(); + if (term.isEmpty()) { + return; + } + + var control = term.get().controllable(); + control.ifPresent(controllableTerminalSession -> { + controllableTerminalSession.focus(); + }); + } + @Override public boolean requiresEnabledApi() { return false; diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionBrowseExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionBrowseExchangeImpl.java index 1fb2f1d2a..5624122a1 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionBrowseExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionBrowseExchangeImpl.java @@ -1,6 +1,6 @@ package io.xpipe.app.beacon.impl; -import io.xpipe.app.browser.session.BrowserSessionModel; +import io.xpipe.app.browser.BrowserFullSessionModel; import io.xpipe.app.core.AppLayoutModel; import io.xpipe.app.storage.DataStorage; import io.xpipe.beacon.BeaconClientException; @@ -19,8 +19,8 @@ public class ConnectionBrowseExchangeImpl extends ConnectionBrowseExchange { if (!(e.getStore() instanceof FileSystemStore)) { throw new BeaconClientException("Not a file system connection"); } - BrowserSessionModel.DEFAULT.openFileSystemSync( - e.ref(), msg.getDirectory() != null ? ignored -> msg.getDirectory() : null, null); + BrowserFullSessionModel.DEFAULT.openFileSystemSync( + e.ref(), msg.getDirectory() != null ? ignored -> msg.getDirectory() : null, null, true); AppLayoutModel.get().selectBrowser(); return Response.builder().build(); } diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionRefreshExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionRefreshExchangeImpl.java index 3c97868e9..5fa336528 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionRefreshExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionRefreshExchangeImpl.java @@ -15,9 +15,9 @@ public class ConnectionRefreshExchangeImpl extends ConnectionRefreshExchange { .getStoreEntryIfPresent(msg.getConnection()) .orElseThrow(() -> new BeaconClientException("Unknown connection: " + msg.getConnection())); if (e.getStore() instanceof FixedHierarchyStore) { - DataStorage.get().refreshChildren(e, null, true); + DataStorage.get().refreshChildren(e, true); } else { - e.validateOrThrowAndClose(null); + e.validateOrThrow(); } return Response.builder().build(); } diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionTerminalExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionTerminalExchangeImpl.java index 0717de7f4..d88fa48e1 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionTerminalExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ConnectionTerminalExchangeImpl.java @@ -1,10 +1,10 @@ package io.xpipe.app.beacon.impl; +import io.xpipe.app.ext.ShellStore; import io.xpipe.app.storage.DataStorage; -import io.xpipe.app.util.TerminalLauncher; +import io.xpipe.app.terminal.TerminalLauncher; import io.xpipe.beacon.BeaconClientException; import io.xpipe.beacon.api.ConnectionTerminalExchange; -import io.xpipe.core.store.ShellStore; import com.sun.net.httpserver.HttpExchange; @@ -18,9 +18,8 @@ public class ConnectionTerminalExchangeImpl extends ConnectionTerminalExchange { if (!(e.getStore() instanceof ShellStore shellStore)) { throw new BeaconClientException("Not a shell connection"); } - try (var sc = shellStore.control().start()) { - TerminalLauncher.open(e, e.getName(), msg.getDirectory(), sc); - } + var sc = shellStore.getOrStartSession(); + TerminalLauncher.open(e, e.getName(), msg.getDirectory(), sc); return Response.builder().build(); } } diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/DaemonOpenExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/DaemonOpenExchangeImpl.java index a8bf0df51..75f9e4b7f 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/DaemonOpenExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/DaemonOpenExchangeImpl.java @@ -1,7 +1,7 @@ package io.xpipe.app.beacon.impl; +import io.xpipe.app.core.launcher.LauncherInput; import io.xpipe.app.core.mode.OperationMode; -import io.xpipe.app.launcher.LauncherInput; import io.xpipe.app.util.PlatformState; import io.xpipe.beacon.BeaconServerException; import io.xpipe.beacon.api.DaemonOpenExchange; diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/ShellStartExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/ShellStartExchangeImpl.java index 21cefe252..e9456fd45 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/ShellStartExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/ShellStartExchangeImpl.java @@ -2,10 +2,10 @@ package io.xpipe.app.beacon.impl; import io.xpipe.app.beacon.AppBeaconServer; import io.xpipe.app.beacon.BeaconShellSession; +import io.xpipe.app.ext.ShellStore; import io.xpipe.app.storage.DataStorage; import io.xpipe.beacon.BeaconClientException; import io.xpipe.beacon.api.ShellStartExchange; -import io.xpipe.core.store.ShellStore; import com.sun.net.httpserver.HttpExchange; import lombok.SneakyThrows; @@ -25,7 +25,9 @@ public class ShellStartExchangeImpl extends ShellStartExchange { var existing = AppBeaconServer.get().getCache().getShellSessions().stream() .filter(beaconShellSession -> beaconShellSession.getEntry().equals(e)) .findFirst(); - var control = (existing.isPresent() ? existing.get().getControl() : s.control()); + var control = (existing.isPresent() + ? existing.get().getControl() + : s.standaloneControl().start()); control.setNonInteractive(); control.start(); diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/SshLaunchExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/SshLaunchExchangeImpl.java index 2b8c8c410..c287553d7 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/SshLaunchExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/SshLaunchExchangeImpl.java @@ -1,7 +1,7 @@ package io.xpipe.app.beacon.impl; import io.xpipe.app.ext.ProcessControlProvider; -import io.xpipe.app.util.TerminalLauncherManager; +import io.xpipe.app.terminal.TerminalLauncherManager; import io.xpipe.beacon.api.SshLaunchExchange; import io.xpipe.core.process.ShellDialects; diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/TerminalLaunchExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/TerminalLaunchExchangeImpl.java index d1982ca7f..03b80342a 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/TerminalLaunchExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/TerminalLaunchExchangeImpl.java @@ -1,6 +1,6 @@ package io.xpipe.app.beacon.impl; -import io.xpipe.app.util.TerminalLauncherManager; +import io.xpipe.app.terminal.TerminalLauncherManager; import io.xpipe.beacon.BeaconClientException; import io.xpipe.beacon.api.TerminalLaunchExchange; diff --git a/app/src/main/java/io/xpipe/app/beacon/impl/TerminalWaitExchangeImpl.java b/app/src/main/java/io/xpipe/app/beacon/impl/TerminalWaitExchangeImpl.java index 885247908..131450996 100644 --- a/app/src/main/java/io/xpipe/app/beacon/impl/TerminalWaitExchangeImpl.java +++ b/app/src/main/java/io/xpipe/app/beacon/impl/TerminalWaitExchangeImpl.java @@ -1,6 +1,7 @@ package io.xpipe.app.beacon.impl; -import io.xpipe.app.util.TerminalLauncherManager; +import io.xpipe.app.terminal.TerminalLauncherManager; +import io.xpipe.app.terminal.TerminalView; import io.xpipe.beacon.BeaconClientException; import io.xpipe.beacon.BeaconServerException; import io.xpipe.beacon.api.TerminalWaitExchange; @@ -10,7 +11,8 @@ import com.sun.net.httpserver.HttpExchange; public class TerminalWaitExchangeImpl extends TerminalWaitExchange { @Override public Object handle(HttpExchange exchange, Request msg) throws BeaconClientException, BeaconServerException { - TerminalLauncherManager.waitExchange(msg.getRequest()); + TerminalView.get().open(msg.getRequest(), msg.getPid()); + TerminalLauncherManager.waitExchange(msg.getRequest(), msg.getPid()); return Response.builder().build(); } diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserAbstractSessionModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserAbstractSessionModel.java similarity index 90% rename from app/src/main/java/io/xpipe/app/browser/session/BrowserAbstractSessionModel.java rename to app/src/main/java/io/xpipe/app/browser/BrowserAbstractSessionModel.java index 0a28540ab..86fcb3816 100644 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserAbstractSessionModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserAbstractSessionModel.java @@ -1,4 +1,4 @@ -package io.xpipe.app.browser.session; +package io.xpipe.app.browser; import io.xpipe.app.util.BooleanScope; import io.xpipe.app.util.ThreadHelper; @@ -13,13 +13,13 @@ import javafx.collections.ObservableList; import lombok.Getter; @Getter -public class BrowserAbstractSessionModel> { +public class BrowserAbstractSessionModel { protected final ObservableList sessionEntries = FXCollections.observableArrayList(); protected final Property selectedEntry = new SimpleObjectProperty<>(); protected final BooleanProperty busy = new SimpleBooleanProperty(); - public void closeAsync(BrowserSessionTab e) { + public void closeAsync(BrowserSessionTab e) { ThreadHelper.runAsync(() -> { closeSync(e); }); @@ -37,7 +37,7 @@ public class BrowserAbstractSessionModel> { } } - public void closeSync(BrowserSessionTab e) { + public void closeSync(BrowserSessionTab e) { e.close(); synchronized (BrowserAbstractSessionModel.this) { this.sessionEntries.remove(e); diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionComp.java similarity index 83% rename from app/src/main/java/io/xpipe/app/browser/session/BrowserChooserComp.java rename to app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionComp.java index 74d7f2135..b0f6c25c4 100644 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserChooserComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionComp.java @@ -1,25 +1,25 @@ -package io.xpipe.app.browser.session; +package io.xpipe.app.browser; -import io.xpipe.app.browser.BrowserBookmarkComp; -import io.xpipe.app.browser.BrowserBookmarkHeaderComp; +import io.xpipe.app.browser.file.BrowserConnectionListComp; +import io.xpipe.app.browser.file.BrowserConnectionListFilterComp; import io.xpipe.app.browser.file.BrowserEntry; -import io.xpipe.app.browser.fs.OpenFileSystemComp; -import io.xpipe.app.browser.fs.OpenFileSystemModel; +import io.xpipe.app.browser.file.BrowserFileSystemTabComp; +import io.xpipe.app.browser.file.BrowserFileSystemTabModel; +import io.xpipe.app.comp.Comp; import io.xpipe.app.comp.base.DialogComp; -import io.xpipe.app.comp.base.SideSplitPaneComp; +import io.xpipe.app.comp.base.LeftSplitPaneComp; +import io.xpipe.app.comp.base.StackComp; +import io.xpipe.app.comp.base.VerticalComp; import io.xpipe.app.comp.store.StoreEntryWrapper; import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppLayoutModel; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.impl.StackComp; -import io.xpipe.app.fxcomps.impl.VerticalComp; -import io.xpipe.app.fxcomps.util.BindingsHelper; -import io.xpipe.app.fxcomps.util.PlatformThread; +import io.xpipe.app.ext.ShellStore; import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.app.util.BindingsHelper; import io.xpipe.app.util.FileReference; +import io.xpipe.app.util.PlatformThread; import io.xpipe.app.util.ThreadHelper; import io.xpipe.core.store.FileSystemStore; -import io.xpipe.core.store.ShellStore; import javafx.beans.property.BooleanProperty; import javafx.collections.ListChangeListener; @@ -37,12 +37,12 @@ import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier; -public class BrowserChooserComp extends DialogComp { +public class BrowserFileChooserSessionComp extends DialogComp { private final Stage stage; - private final BrowserFileChooserModel model; + private final BrowserFileChooserSessionModel model; - public BrowserChooserComp(Stage stage, BrowserFileChooserModel model) { + public BrowserFileChooserSessionComp(Stage stage, BrowserFileChooserSessionModel model) { this.stage = stage; this.model = model; } @@ -50,9 +50,9 @@ public class BrowserChooserComp extends DialogComp { public static void openSingleFile( Supplier> store, Consumer file, boolean save) { PlatformThread.runLaterIfNeeded(() -> { - var model = new BrowserFileChooserModel(OpenFileSystemModel.SelectionMode.SINGLE_FILE); + var model = new BrowserFileChooserSessionModel(BrowserFileSystemTabModel.SelectionMode.SINGLE_FILE); DialogComp.showWindow(save ? "saveFileTitle" : "openFileTitle", stage -> { - var comp = new BrowserChooserComp(stage, model); + var comp = new BrowserFileChooserSessionComp(stage, model); comp.apply(struc -> struc.get().setPrefSize(1200, 700)) .apply(struc -> AppFont.normal(struc.get())) .styleClass("browser") @@ -114,8 +114,8 @@ public class BrowserChooserComp extends DialogComp { }); }; - var bookmarkTopBar = new BrowserBookmarkHeaderComp(); - var bookmarksList = new BrowserBookmarkComp( + var bookmarkTopBar = new BrowserConnectionListFilterComp(); + var bookmarksList = new BrowserConnectionListComp( BindingsHelper.map(model.getSelectedEntry(), v -> v.getEntry().get()), applicable, action, @@ -138,7 +138,7 @@ public class BrowserChooserComp extends DialogComp { model.getSelectedEntry().subscribe(selected -> { PlatformThread.runLaterIfNeeded(() -> { if (selected != null) { - s.getChildren().setAll(new OpenFileSystemComp(selected, false).createRegion()); + s.getChildren().setAll(new BrowserFileSystemTabComp(selected, false).createRegion()); } else { s.getChildren().clear(); } @@ -148,7 +148,7 @@ public class BrowserChooserComp extends DialogComp { }); var vertical = new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer)).styleClass("left"); - var splitPane = new SideSplitPaneComp(vertical, stack) + var splitPane = new LeftSplitPaneComp(vertical, stack) .withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()) .withOnDividerChange(AppLayoutModel.get().getSavedState()::setBrowserConnectionsWidth) .styleClass("background") diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserFileChooserModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionModel.java similarity index 77% rename from app/src/main/java/io/xpipe/app/browser/session/BrowserFileChooserModel.java rename to app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionModel.java index 162fe3c6d..bfcca2008 100644 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserFileChooserModel.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFileChooserSessionModel.java @@ -1,10 +1,10 @@ -package io.xpipe.app.browser.session; +package io.xpipe.app.browser; import io.xpipe.app.browser.file.BrowserEntry; -import io.xpipe.app.browser.fs.OpenFileSystemModel; -import io.xpipe.app.fxcomps.util.DerivedObservableList; +import io.xpipe.app.browser.file.BrowserFileSystemTabModel; import io.xpipe.app.storage.DataStoreEntryRef; import io.xpipe.app.util.BooleanScope; +import io.xpipe.app.util.DerivedObservableList; import io.xpipe.app.util.FileReference; import io.xpipe.app.util.ThreadHelper; import io.xpipe.core.store.FileNames; @@ -24,15 +24,15 @@ import java.util.List; import java.util.function.Consumer; @Getter -public class BrowserFileChooserModel extends BrowserAbstractSessionModel { +public class BrowserFileChooserSessionModel extends BrowserAbstractSessionModel { - private final OpenFileSystemModel.SelectionMode selectionMode; + private final BrowserFileSystemTabModel.SelectionMode selectionMode; private final ObservableList fileSelection = FXCollections.observableArrayList(); @Setter private Consumer> onFinish; - public BrowserFileChooserModel(OpenFileSystemModel.SelectionMode selectionMode) { + public BrowserFileChooserSessionModel(BrowserFileSystemTabModel.SelectionMode selectionMode) { this.selectionMode = selectionMode; selectedEntry.addListener((observable, oldValue, newValue) -> { if (newValue == null) { @@ -48,7 +48,7 @@ public class BrowserFileChooserModel extends BrowserAbstractSessionModel(fileSelection); - synchronized (BrowserFileChooserModel.this) { + synchronized (BrowserFileChooserSessionModel.this) { var open = selectedEntry.getValue(); if (open != null) { ThreadHelper.runAsync(() -> { @@ -66,7 +66,7 @@ public class BrowserFileChooserModel extends BrowserAbstractSessionModel { @@ -78,20 +78,20 @@ public class BrowserFileChooserModel extends BrowserAbstractSessionModel store, - FailableFunction path, + FailableFunction path, BooleanProperty externalBusy) { if (store == null) { return; } ThreadHelper.runFailableAsync(() -> { - OpenFileSystemModel model; + BrowserFileSystemTabModel model; try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) { - model = new OpenFileSystemModel(this, store, selectionMode); + model = new BrowserFileSystemTabModel(this, store, selectionMode); model.init(); // Prevent multiple calls from interfering with each other - synchronized (BrowserFileChooserModel.this) { + synchronized (BrowserFileChooserSessionModel.this) { selectedEntry.setValue(model); sessionEntries.add(model); } diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionComp.java similarity index 50% rename from app/src/main/java/io/xpipe/app/browser/session/BrowserSessionComp.java rename to app/src/main/java/io/xpipe/app/browser/BrowserFullSessionComp.java index 2a1a07084..c5081f2fc 100644 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionComp.java @@ -1,44 +1,108 @@ -package io.xpipe.app.browser.session; +package io.xpipe.app.browser; -import io.xpipe.app.browser.BrowserBookmarkComp; -import io.xpipe.app.browser.BrowserBookmarkHeaderComp; -import io.xpipe.app.browser.BrowserTransferComp; +import io.xpipe.app.browser.file.BrowserConnectionListComp; +import io.xpipe.app.browser.file.BrowserConnectionListFilterComp; +import io.xpipe.app.browser.file.BrowserTransferComp; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.CompStructure; +import io.xpipe.app.comp.SimpleComp; +import io.xpipe.app.comp.base.AnchorComp; +import io.xpipe.app.comp.base.LeftSplitPaneComp; import io.xpipe.app.comp.base.LoadingOverlayComp; -import io.xpipe.app.comp.base.SideSplitPaneComp; +import io.xpipe.app.comp.base.StackComp; +import io.xpipe.app.comp.base.VerticalComp; import io.xpipe.app.comp.store.StoreEntryWrapper; import io.xpipe.app.core.AppLayoutModel; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.impl.AnchorComp; -import io.xpipe.app.fxcomps.impl.StackComp; -import io.xpipe.app.fxcomps.impl.VerticalComp; -import io.xpipe.app.fxcomps.util.BindingsHelper; -import io.xpipe.app.fxcomps.util.PlatformThread; +import io.xpipe.app.ext.ShellStore; +import io.xpipe.app.util.BindingsHelper; +import io.xpipe.app.util.PlatformThread; import io.xpipe.app.util.ThreadHelper; -import io.xpipe.core.store.ShellStore; import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleDoubleProperty; +import javafx.geometry.Insets; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; import javafx.scene.shape.Rectangle; +import java.util.HashMap; import java.util.List; import java.util.function.BiConsumer; import java.util.function.Predicate; -public class BrowserSessionComp extends SimpleComp { +public class BrowserFullSessionComp extends SimpleComp { - private final BrowserSessionModel model; + private final BrowserFullSessionModel model; - public BrowserSessionComp(BrowserSessionModel model) { + public BrowserFullSessionComp(BrowserFullSessionModel model) { this.model = model; } @Override protected Region createSimple() { + var vertical = createLeftSide(); + + var leftSplit = new SimpleDoubleProperty(); + var rightSplit = new SimpleDoubleProperty(); + var tabs = new BrowserSessionTabsComp(model, leftSplit, rightSplit); + tabs.apply(struc -> { + struc.get().setViewOrder(1); + struc.get().setPickOnBounds(false); + AnchorPane.setTopAnchor(struc.get(), 0.0); + AnchorPane.setBottomAnchor(struc.get(), 0.0); + AnchorPane.setLeftAnchor(struc.get(), 0.0); + AnchorPane.setRightAnchor(struc.get(), 0.0); + }); + + vertical.apply(struc -> { + struc.get() + .paddingProperty() + .bind(Bindings.createObjectBinding( + () -> new Insets(tabs.getHeaderHeight().get(), 0, 0, 0), tabs.getHeaderHeight())); + }); + var loadingIndicator = LoadingOverlayComp.noProgress(Comp.empty(), model.getBusy()) + .apply(struc -> { + AnchorPane.setTopAnchor(struc.get(), 3.0); + AnchorPane.setRightAnchor(struc.get(), 0.0); + }) + .styleClass("tab-loading-indicator"); + + var pinnedStack = createSplitStack(rightSplit, tabs); + + var loadingStack = new AnchorComp(List.of(tabs, pinnedStack, loadingIndicator)); + var splitPane = new LeftSplitPaneComp(vertical, loadingStack) + .withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()) + .withOnDividerChange(d -> { + AppLayoutModel.get().getSavedState().setBrowserConnectionsWidth(d); + leftSplit.set(d); + }); + splitPane.apply(struc -> { + struc.getLeft().setMinWidth(200); + struc.getLeft().setMaxWidth(500); + struc.get().setPickOnBounds(false); + }); + + splitPane.apply(struc -> { + struc.get().skinProperty().subscribe(newValue -> { + if (newValue != null) { + Platform.runLater(() -> { + struc.get().getChildrenUnmodifiable().forEach(node -> { + node.setClip(null); + node.setPickOnBounds(false); + }); + struc.get().lookupAll(".split-pane-divider").forEach(node -> node.setViewOrder(1)); + }); + } + }); + }); + splitPane.styleClass("browser"); + return splitPane.createRegion(); + } + + private Comp> createLeftSide() { Predicate applicable = storeEntryWrapper -> { if (!storeEntryWrapper.getEntry().getValidity().isUsable()) { return false; @@ -65,9 +129,13 @@ public class BrowserSessionComp extends SimpleComp { }); }; - var bookmarkTopBar = new BrowserBookmarkHeaderComp(); - var bookmarksList = new BrowserBookmarkComp( - BindingsHelper.map(model.getSelectedEntry(), v -> v.getEntry().get()), + var bookmarkTopBar = new BrowserConnectionListFilterComp(); + var bookmarksList = new BrowserConnectionListComp( + BindingsHelper.map( + model.getSelectedEntry(), + v -> v instanceof BrowserStoreSessionTab st + ? st.getEntry().get() + : null), applicable, action, bookmarkTopBar.getCategory(), @@ -98,51 +166,49 @@ public class BrowserSessionComp extends SimpleComp { localDownloadStage.maxHeight(200); var vertical = new VerticalComp(List.of(bookmarkTopBar, bookmarksContainer, localDownloadStage)).styleClass("left"); + return vertical; + } - var split = new SimpleDoubleProperty(); - var tabs = new BrowserSessionTabsComp(model, split).apply(struc -> { - struc.get().setViewOrder(1); - struc.get().setPickOnBounds(false); - AnchorPane.setTopAnchor(struc.get(), 0.0); - AnchorPane.setBottomAnchor(struc.get(), 0.0); - AnchorPane.setLeftAnchor(struc.get(), 0.0); - AnchorPane.setRightAnchor(struc.get(), 0.0); - }); - var loadingIndicator = LoadingOverlayComp.noProgress(Comp.empty(), model.getBusy()) - .apply(struc -> { - AnchorPane.setTopAnchor(struc.get(), 0.0); - AnchorPane.setRightAnchor(struc.get(), 0.0); - }) - .styleClass("tab-loading-indicator"); - var loadingStack = new AnchorComp(List.of(tabs, loadingIndicator)); - var splitPane = new SideSplitPaneComp(vertical, loadingStack) - .withInitialWidth(AppLayoutModel.get().getSavedState().getBrowserConnectionsWidth()) - .withOnDividerChange(d -> { - AppLayoutModel.get().getSavedState().setBrowserConnectionsWidth(d); - split.set(d); - }) - .apply(struc -> { - struc.getLeft().setMinWidth(200); - struc.getLeft().setMaxWidth(500); - struc.get().setPickOnBounds(false); + private StackComp createSplitStack(SimpleDoubleProperty rightSplit, BrowserSessionTabsComp tabs) { + var cache = new HashMap(); + var splitStack = new StackComp(List.of()); + splitStack.apply(struc -> { + model.getEffectiveRightTab().subscribe((newValue) -> { + PlatformThread.runLaterIfNeeded(() -> { + var all = model.getAllTabs(); + cache.keySet().removeIf(browserSessionTab -> !all.contains(browserSessionTab)); + + if (newValue == null) { + struc.get().getChildren().clear(); + return; + } + + var cached = cache.containsKey(newValue); + if (!cached) { + cache.put(newValue, newValue.comp().createRegion()); + } + var r = cache.get(newValue); + struc.get().getChildren().clear(); + struc.get().getChildren().add(r); + + struc.get().setMinWidth(rightSplit.get()); + struc.get().setMaxWidth(rightSplit.get()); + struc.get().setPrefWidth(rightSplit.get()); }); + }); - splitPane.apply(struc -> { - struc.get().skinProperty().subscribe(newValue -> { - if (newValue != null) { - Platform.runLater(() -> { - struc.get().getChildrenUnmodifiable().forEach(node -> { - node.setClip(null); - node.setPickOnBounds(false); - }); - struc.get().lookupAll(".split-pane-divider").forEach(node -> node.setViewOrder(1)); - }); - } + rightSplit.addListener((observable, oldValue, newValue) -> { + struc.get().setMinWidth(newValue.doubleValue()); + struc.get().setMaxWidth(newValue.doubleValue()); + struc.get().setPrefWidth(newValue.doubleValue()); + }); + + AnchorPane.setBottomAnchor(struc.get(), 0.0); + AnchorPane.setRightAnchor(struc.get(), 0.0); + tabs.getHeaderHeight().subscribe(number -> { + AnchorPane.setTopAnchor(struc.get(), number.doubleValue()); }); }); - - var r = splitPane.createRegion(); - r.getStyleClass().add("browser"); - return r; + return splitStack; } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionModel.java b/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionModel.java new file mode 100644 index 000000000..5df06abad --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/BrowserFullSessionModel.java @@ -0,0 +1,222 @@ +package io.xpipe.app.browser; + +import io.xpipe.app.browser.file.BrowserFileSystemTabModel; +import io.xpipe.app.browser.file.BrowserHistorySavedState; +import io.xpipe.app.browser.file.BrowserHistorySavedStateImpl; +import io.xpipe.app.browser.file.BrowserHistoryTabModel; +import io.xpipe.app.browser.file.BrowserTransferModel; +import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.app.util.BooleanScope; +import io.xpipe.app.util.ThreadHelper; +import io.xpipe.core.store.FileNames; +import io.xpipe.core.store.FileSystemStore; +import io.xpipe.core.util.FailableFunction; + +import javafx.beans.binding.Bindings; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.value.ObservableValue; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableMap; + +import lombok.Getter; + +import java.util.*; + +@Getter +public class BrowserFullSessionModel extends BrowserAbstractSessionModel { + + public static final BrowserFullSessionModel DEFAULT = new BrowserFullSessionModel(); + + static { + DEFAULT.getSessionEntries().add(new BrowserHistoryTabModel(DEFAULT)); + } + + private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this); + private final Property draggingFiles = new SimpleBooleanProperty(); + private final Property globalPinnedTab = new SimpleObjectProperty<>(); + private final ObservableMap splits = FXCollections.observableHashMap(); + private final ObservableValue effectiveRightTab = createEffectiveRightTab(); + + private ObservableValue createEffectiveRightTab() { + return Bindings.createObjectBinding( + () -> { + var current = selectedEntry.getValue(); + if (!current.isCloseable()) { + return null; + } + + var split = splits.get(current); + if (split != null) { + return split; + } + + var global = globalPinnedTab.getValue(); + if (global == null) { + return null; + } + + if (global == selectedEntry.getValue()) { + return null; + } + + return global; + }, + globalPinnedTab, + selectedEntry, + splits); + } + + public BrowserFullSessionModel() { + sessionEntries.addListener((ListChangeListener) c -> { + var v = globalPinnedTab.getValue(); + if (v != null && !c.getList().contains(v)) { + globalPinnedTab.setValue(null); + } + + splits.keySet().removeIf(browserSessionTab -> !c.getList().contains(browserSessionTab)); + }); + } + + public Set getAllTabs() { + var set = new HashSet(); + set.addAll(sessionEntries); + set.addAll(splits.values()); + if (globalPinnedTab.getValue() != null) { + set.add(globalPinnedTab.getValue()); + } + return set; + } + + public void splitTab(BrowserSessionTab tab, BrowserSessionTab split) { + if (splits.containsKey(tab)) { + return; + } + + splits.put(tab, split); + ThreadHelper.runFailableAsync(() -> { + split.init(); + }); + } + + public void unsplitTab(BrowserSessionTab tab) { + if (splits.values().remove(tab)) { + ThreadHelper.runFailableAsync(() -> { + tab.close(); + }); + } + } + + public void pinTab(BrowserSessionTab tab) { + if (tab.equals(globalPinnedTab.getValue())) { + return; + } + + globalPinnedTab.setValue(tab); + + var nextIndex = getSessionEntries().indexOf(tab) + 1; + if (nextIndex < getSessionEntries().size()) { + getSelectedEntry().setValue(getSessionEntries().get(nextIndex)); + } + } + + public void unpinTab(BrowserSessionTab tab) { + ThreadHelper.runFailableAsync(() -> { + globalPinnedTab.setValue(null); + }); + } + + public void restoreState(BrowserHistorySavedState state) { + ThreadHelper.runAsync(() -> { + var l = new ArrayList<>(state.getEntries()); + l.forEach(e -> { + restoreStateAsync(e, null); + // Don't try to run everything in parallel as that can be taxing + ThreadHelper.sleep(1000); + }); + }); + } + + public void restoreStateAsync(BrowserHistorySavedState.Entry e, BooleanProperty busy) { + var storageEntry = DataStorage.get().getStoreEntryIfPresent(e.getUuid()); + storageEntry.ifPresent(entry -> { + openFileSystemAsync(entry.ref(), model -> e.getPath(), busy); + }); + } + + public void reset() { + synchronized (BrowserFullSessionModel.this) { + var all = new ArrayList<>(sessionEntries); + for (var o : all) { + // Don't close busy connections gracefully + // as we otherwise might lock up + if (!o.canImmediatelyClose()) { + continue; + } + + // Prevent blocking of shutdown + closeAsync(o); + } + BrowserHistorySavedStateImpl.get().save(); + } + + // Delete all files + localTransfersStage.clear(true); + } + + public void openFileSystemAsync( + DataStoreEntryRef store, + FailableFunction path, + BooleanProperty externalBusy) { + if (store == null) { + return; + } + + ThreadHelper.runFailableAsync(() -> { + openFileSystemSync(store, path, externalBusy, true); + }); + } + + public BrowserFileSystemTabModel openFileSystemSync( + DataStoreEntryRef store, + FailableFunction path, + BooleanProperty externalBusy, + boolean select) + throws Exception { + BrowserFileSystemTabModel model; + try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) { + try (var sessionBusy = new BooleanScope(busy).exclusive().start()) { + model = new BrowserFileSystemTabModel(this, store, BrowserFileSystemTabModel.SelectionMode.ALL); + model.init(); + // Prevent multiple calls from interfering with each other + synchronized (BrowserFullSessionModel.this) { + sessionEntries.add(model); + if (select) { + // The tab pane doesn't automatically select new tabs + selectedEntry.setValue(model); + } + } + } + } + if (path != null) { + model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model))); + } else { + model.initWithDefaultDirectory(); + } + return model; + } + + @Override + public void closeSync(BrowserSessionTab e) { + var split = splits.get(e); + if (split != null) { + split.close(); + } + + super.closeSync(e); + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserSessionTab.java b/app/src/main/java/io/xpipe/app/browser/BrowserSessionTab.java new file mode 100644 index 000000000..b0d3d9584 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/BrowserSessionTab.java @@ -0,0 +1,41 @@ +package io.xpipe.app.browser; + +import io.xpipe.app.comp.Comp; +import io.xpipe.app.storage.DataColor; + +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.Property; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleObjectProperty; + +import lombok.Getter; + +@Getter +public abstract class BrowserSessionTab { + + protected final BooleanProperty busy = new SimpleBooleanProperty(); + protected final BrowserAbstractSessionModel browserModel; + protected final String name; + protected final Property splitTab = new SimpleObjectProperty<>(); + + public BrowserSessionTab(BrowserAbstractSessionModel browserModel, String name) { + this.browserModel = browserModel; + this.name = name; + } + + public abstract Comp comp(); + + public abstract boolean canImmediatelyClose(); + + public abstract void init() throws Exception; + + public abstract void close(); + + public abstract String getIcon(); + + public abstract DataColor getColor(); + + public boolean isCloseable() { + return true; + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java b/app/src/main/java/io/xpipe/app/browser/BrowserSessionTabsComp.java similarity index 61% rename from app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java rename to app/src/main/java/io/xpipe/app/browser/BrowserSessionTabsComp.java index e4cb2ef1a..2f4055c10 100644 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTabsComp.java +++ b/app/src/main/java/io/xpipe/app/browser/BrowserSessionTabsComp.java @@ -1,35 +1,34 @@ -package io.xpipe.app.browser.session; +package io.xpipe.app.browser; -import io.xpipe.app.browser.BrowserWelcomeComp; -import io.xpipe.app.comp.base.MultiContentComp; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.SimpleComp; +import io.xpipe.app.comp.base.PrettyImageHelper; import io.xpipe.app.core.AppI18n; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.impl.PrettyImageHelper; -import io.xpipe.app.fxcomps.impl.TooltipAugment; -import io.xpipe.app.fxcomps.util.LabelGraphic; -import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.prefs.AppPrefs; -import io.xpipe.app.storage.DataStorage; import io.xpipe.app.util.BooleanScope; import io.xpipe.app.util.ContextMenuHelper; +import io.xpipe.app.util.LabelGraphic; +import io.xpipe.app.util.PlatformThread; import javafx.application.Platform; import javafx.beans.binding.Bindings; +import javafx.beans.property.DoubleProperty; import javafx.beans.property.SimpleBooleanProperty; -import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.value.ObservableDoubleValue; -import javafx.beans.value.ObservableValue; import javafx.collections.ListChangeListener; +import javafx.css.PseudoClass; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.*; +import javafx.scene.control.skin.TabPaneSkin; import javafx.scene.input.*; import javafx.scene.layout.Region; import javafx.scene.layout.StackPane; import atlantafx.base.controls.RingProgressIndicator; import atlantafx.base.theme.Styles; +import lombok.Getter; import java.util.*; @@ -39,28 +38,33 @@ import static javafx.scene.control.TabPane.TabClosingPolicy.ALL_TABS; public class BrowserSessionTabsComp extends SimpleComp { - private final BrowserSessionModel model; + private final BrowserFullSessionModel model; private final ObservableDoubleValue leftPadding; + private final DoubleProperty rightPadding; - public BrowserSessionTabsComp(BrowserSessionModel model, ObservableDoubleValue leftPadding) { + @Getter + private final DoubleProperty headerHeight; + + public BrowserSessionTabsComp( + BrowserFullSessionModel model, ObservableDoubleValue leftPadding, DoubleProperty rightPadding) { this.model = model; this.leftPadding = leftPadding; + this.rightPadding = rightPadding; + this.headerHeight = new SimpleDoubleProperty(); } public Region createSimple() { - var map = new LinkedHashMap, ObservableValue>(); - map.put(Comp.hspacer().styleClass("top-spacer"), new SimpleBooleanProperty(true)); - map.put(Comp.of(() -> createTabPane()), Bindings.isNotEmpty(model.getSessionEntries())); - map.put( - new BrowserWelcomeComp(model).apply(struc -> StackPane.setAlignment(struc.get(), Pos.CENTER_LEFT)), - Bindings.createBooleanBinding( - () -> { - return model.getSessionEntries().size() == 0; - }, - model.getSessionEntries())); - var multi = new MultiContentComp(map); - multi.apply(struc -> ((StackPane) struc.get()).setAlignment(Pos.TOP_CENTER)); - return multi.createRegion(); + var tabs = createTabPane(); + var topBackground = Comp.hspacer().styleClass("top-spacer").createRegion(); + leftPadding.subscribe(number -> { + StackPane.setMargin(topBackground, new Insets(0, 0, 0, -number.doubleValue() - 6)); + }); + var stack = new StackPane(topBackground, tabs); + stack.setAlignment(Pos.TOP_CENTER); + topBackground.prefHeightProperty().bind(headerHeight); + topBackground.maxHeightProperty().bind(topBackground.prefHeightProperty()); + topBackground.prefWidthProperty().bind(tabs.widthProperty()); + return stack; } private TabPane createTabPane() { @@ -69,6 +73,7 @@ public class BrowserSessionTabsComp extends SimpleComp { tabs.setTabMinWidth(Region.USE_PREF_SIZE); tabs.setTabMaxWidth(400); tabs.setTabClosingPolicy(ALL_TABS); + tabs.setSkin(new TabPaneSkin(tabs)); Styles.toggleStyleClass(tabs, TabPane.STYLE_CLASS_FLOATING); toggleStyleClass(tabs, DENSE); @@ -80,22 +85,31 @@ public class BrowserSessionTabsComp extends SimpleComp { tabs.lookupAll(".tab-header-area").forEach(node -> { node.setClip(null); node.setPickOnBounds(false); + + var r = (Region) node; + r.prefHeightProperty().bind(r.maxHeightProperty()); + r.setMinHeight(Region.USE_PREF_SIZE); }); tabs.lookupAll(".headers-region").forEach(node -> { node.setClip(null); node.setPickOnBounds(false); + + var r = (Region) node; + r.prefHeightProperty().bind(r.maxHeightProperty()); + r.setMinHeight(Region.USE_PREF_SIZE); }); Region headerArea = (Region) tabs.lookup(".tab-header-area"); headerArea .paddingProperty() .bind(Bindings.createObjectBinding( - () -> new Insets(0, 0, 0, -leftPadding.get() + 2), leftPadding)); + () -> new Insets(2, 0, 4, -leftPadding.get() + 2), leftPadding)); + headerHeight.bind(headerArea.heightProperty()); }); } }); - var map = new HashMap, Tab>(); + var map = new HashMap(); // Restore state model.getSessionEntries().forEach(v -> { @@ -156,7 +170,7 @@ public class BrowserSessionTabsComp extends SimpleComp { }); }); - model.getSessionEntries().addListener((ListChangeListener>) c -> { + model.getSessionEntries().addListener((ListChangeListener) c -> { while (c.next()) { for (var r : c.getRemoved()) { PlatformThread.runLaterIfNeeded(() -> { @@ -245,9 +259,38 @@ public class BrowserSessionTabsComp extends SimpleComp { return tabs; } - private ContextMenu createContextMenu(TabPane tabs, Tab tab) { + private ContextMenu createContextMenu(TabPane tabs, Tab tab, BrowserSessionTab tabModel) { var cm = ContextMenuHelper.create(); + if (tabModel.isCloseable()) { + var unpin = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("unpinTab")); + unpin.visibleProperty() + .bind(PlatformThread.sync(Bindings.createBooleanBinding( + () -> { + return model.getGlobalPinnedTab().getValue() != null + && model.getGlobalPinnedTab().getValue().equals(tabModel); + }, + model.getGlobalPinnedTab()))); + unpin.setOnAction(event -> { + model.unpinTab(tabModel); + event.consume(); + }); + cm.getItems().add(unpin); + + var pin = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("pinTab")); + pin.visibleProperty() + .bind(PlatformThread.sync(Bindings.createBooleanBinding( + () -> { + return model.getGlobalPinnedTab().getValue() == null; + }, + model.getGlobalPinnedTab()))); + pin.setOnAction(event -> { + model.pinTab(tabModel); + event.consume(); + }); + cm.getItems().add(pin); + } + var select = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("selectTab")); select.acceleratorProperty() .bind(Bindings.createObjectBinding( @@ -272,7 +315,9 @@ public class BrowserSessionTabsComp extends SimpleComp { var close = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("closeTab")); close.setAccelerator(new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN)); close.setOnAction(event -> { - tabs.getTabs().remove(tab); + if (tab.isClosable()) { + tabs.getTabs().remove(tab); + } event.consume(); }); cm.getItems().add(close); @@ -280,7 +325,9 @@ public class BrowserSessionTabsComp extends SimpleComp { var closeOthers = ContextMenuHelper.item(LabelGraphic.none(), AppI18n.get("closeOtherTabs")); closeOthers.setOnAction(event -> { tabs.getTabs() - .removeAll(tabs.getTabs().stream().filter(t -> t != tab).toList()); + .removeAll(tabs.getTabs().stream() + .filter(t -> t != tab && t.isClosable()) + .toList()); event.consume(); }); cm.getItems().add(closeOthers); @@ -290,7 +337,7 @@ public class BrowserSessionTabsComp extends SimpleComp { var index = tabs.getTabs().indexOf(tab); tabs.getTabs() .removeAll(tabs.getTabs().stream() - .filter(t -> tabs.getTabs().indexOf(t) < index) + .filter(t -> tabs.getTabs().indexOf(t) < index && t.isClosable()) .toList()); event.consume(); }); @@ -301,7 +348,7 @@ public class BrowserSessionTabsComp extends SimpleComp { var index = tabs.getTabs().indexOf(tab); tabs.getTabs() .removeAll(tabs.getTabs().stream() - .filter(t -> tabs.getTabs().indexOf(t) > index) + .filter(t -> tabs.getTabs().indexOf(t) > index && t.isClosable()) .toList()); event.consume(); }); @@ -311,7 +358,9 @@ public class BrowserSessionTabsComp extends SimpleComp { closeAll.setAccelerator( new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN)); closeAll.setOnAction(event -> { - tabs.getTabs().clear(); + tabs.getTabs() + .removeAll( + tabs.getTabs().stream().filter(t -> t.isClosable()).toList()); event.consume(); }); cm.getItems().add(closeAll); @@ -319,36 +368,84 @@ public class BrowserSessionTabsComp extends SimpleComp { return cm; } - private Tab createTab(TabPane tabs, BrowserSessionTab model) { + private Tab createTab(TabPane tabs, BrowserSessionTab tabModel) { var tab = new Tab(); - tab.setContextMenu(createContextMenu(tabs, tab)); + if (tabModel.isCloseable()) { + tab.setContextMenu(createContextMenu(tabs, tab, tabModel)); + } - var ring = new RingProgressIndicator(0, false); - ring.setMinSize(16, 16); - ring.setPrefSize(16, 16); - ring.setMaxSize(16, 16); - ring.progressProperty() - .bind(Bindings.createDoubleBinding( - () -> model.getBusy().get() - && !AppPrefs.get().performanceMode().get() - ? -1d - : 0, - PlatformThread.sync(model.getBusy()), - AppPrefs.get().performanceMode())); + tab.setClosable(tabModel.isCloseable()); + // Prevent closing while busy + tab.setOnCloseRequest(event -> { + if (!tabModel.canImmediatelyClose()) { + event.consume(); + } + }); - var image = model.getEntry().get().getEffectiveIconFile(); - var logo = PrettyImageHelper.ofFixedSizeSquare(image, 16).createRegion(); + if (tabModel.getIcon() != null) { + var ring = new RingProgressIndicator(0, false); + ring.setMinSize(16, 16); + ring.setPrefSize(16, 16); + ring.setMaxSize(16, 16); + ring.progressProperty() + .bind(Bindings.createDoubleBinding( + () -> tabModel.getBusy().get() + && !AppPrefs.get().performanceMode().get() + ? -1d + : 0, + PlatformThread.sync(tabModel.getBusy()), + AppPrefs.get().performanceMode())); - tab.graphicProperty() - .bind(Bindings.createObjectBinding( - () -> { - return model.getBusy().get() ? ring : logo; - }, - PlatformThread.sync(model.getBusy()))); - tab.setText(model.getName()); + var image = tabModel.getIcon(); + var logo = PrettyImageHelper.ofFixedSizeSquare(image, 16).createRegion(); - Comp comp = model.comp(); - tab.setContent(comp.createRegion()); + tab.graphicProperty() + .bind(Bindings.createObjectBinding( + () -> { + return tabModel.getBusy().get() ? ring : logo; + }, + PlatformThread.sync(tabModel.getBusy()))); + } + + if (tabModel.getBrowserModel() instanceof BrowserFullSessionModel sessionModel) { + var global = PlatformThread.sync(sessionModel.getGlobalPinnedTab()); + tab.textProperty() + .bind(Bindings.createStringBinding( + () -> { + return tabModel.getName() + + (global.getValue() == tabModel ? " (" + AppI18n.get("pinned") + ")" : ""); + }, + global, + AppPrefs.get().language())); + } else { + tab.setText(tabModel.getName()); + } + + Comp comp = tabModel.comp(); + var compRegion = comp.createRegion(); + var empty = new StackPane(); + empty.setMinWidth(100); + empty.widthProperty().addListener((observable, oldValue, newValue) -> { + if (tabModel.isCloseable() && tabs.getSelectionModel().getSelectedItem() == tab) { + rightPadding.setValue(newValue.doubleValue()); + } + }); + var split = new SplitPane(compRegion); + if (tabModel.isCloseable()) { + split.getItems().add(empty); + } + model.getEffectiveRightTab().subscribe(browserSessionTab -> { + PlatformThread.runLaterIfNeeded(() -> { + if (browserSessionTab != null && split.getItems().size() > 1) { + split.getItems().set(1, empty); + } else if (browserSessionTab != null && split.getItems().size() == 1) { + split.getItems().add(empty); + } else if (browserSessionTab == null && split.getItems().size() > 1) { + split.getItems().remove(1); + } + }); + }); + tab.setContent(split); var id = UUID.randomUUID().toString(); tab.setId(id); @@ -360,18 +457,19 @@ public class BrowserSessionTabsComp extends SimpleComp { var w = l.maxWidthProperty(); l.minWidthProperty().bind(w); l.prefWidthProperty().bind(w); + if (!tabModel.isCloseable()) { + l.pseudoClassStateChanged(PseudoClass.getPseudoClass("static"), true); + } var close = (StackPane) tabs.lookup("#" + id + " .tab-close-button"); close.setPrefWidth(30); StackPane c = (StackPane) tabs.lookup("#" + id + " .tab-container"); c.getStyleClass().add("color-box"); - var color = - DataStorage.get().getEffectiveColor(model.getEntry().get()); + var color = tabModel.getColor(); if (color != null) { c.getStyleClass().add(color.getId()); } - new TooltipAugment<>(new SimpleStringProperty(model.getTooltip()), null).augment(c); c.addEventHandler( DragEvent.DRAG_ENTERED, mouseEvent -> Platform.runLater( diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserStoreSessionTab.java b/app/src/main/java/io/xpipe/app/browser/BrowserStoreSessionTab.java new file mode 100644 index 000000000..a9320fa15 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/BrowserStoreSessionTab.java @@ -0,0 +1,38 @@ +package io.xpipe.app.browser; + +import io.xpipe.app.comp.Comp; +import io.xpipe.app.storage.DataColor; +import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.core.store.DataStore; + +import lombok.Getter; + +@Getter +public abstract class BrowserStoreSessionTab extends BrowserSessionTab { + + protected final DataStoreEntryRef entry; + + public BrowserStoreSessionTab(BrowserAbstractSessionModel browserModel, DataStoreEntryRef entry) { + super(browserModel, DataStorage.get().getStoreEntryDisplayName(entry.get())); + this.entry = entry; + } + + public abstract Comp comp(); + + public abstract boolean canImmediatelyClose(); + + public abstract void init() throws Exception; + + public abstract void close(); + + @Override + public String getIcon() { + return entry.get().getEffectiveIconFile(); + } + + @Override + public DataColor getColor() { + return DataStorage.get().getEffectiveColor(entry.get()); + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/action/BrowserAction.java b/app/src/main/java/io/xpipe/app/browser/action/BrowserAction.java index 839283793..4ab2c2ef0 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/BrowserAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/BrowserAction.java @@ -1,7 +1,7 @@ package io.xpipe.app.browser.action; import io.xpipe.app.browser.file.BrowserEntry; -import io.xpipe.app.browser.fs.OpenFileSystemModel; +import io.xpipe.app.browser.file.BrowserFileSystemTabModel; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.core.util.ModuleLayerLoader; @@ -18,25 +18,25 @@ public interface BrowserAction { List ALL = new ArrayList<>(); - static List getFlattened(OpenFileSystemModel model, List entries) { + static List getFlattened(BrowserFileSystemTabModel model, List entries) { return ALL.stream() .map(browserAction -> getFlattened(browserAction, model, entries)) .flatMap(List::stream) .toList(); } - static List getFlattened( - BrowserAction browserAction, OpenFileSystemModel model, List entries) { - return browserAction instanceof LeafAction - ? List.of((LeafAction) browserAction) - : ((BranchAction) browserAction) + static List getFlattened( + BrowserAction browserAction, BrowserFileSystemTabModel model, List entries) { + return browserAction instanceof BrowserLeafAction + ? List.of((BrowserLeafAction) browserAction) + : ((BrowserBranchAction) browserAction) .getBranchingActions(model, entries).stream() .map(action -> getFlattened(action, model, entries)) .flatMap(List::stream) .toList(); } - static LeafAction byId(String id, OpenFileSystemModel model, List entries) { + static BrowserLeafAction byId(String id, BrowserFileSystemTabModel model, List entries) { return getFlattened(model, entries).stream() .filter(browserAction -> id.equals(browserAction.getId())) .findAny() @@ -52,15 +52,15 @@ public interface BrowserAction { : selected; } - MenuItem toMenuItem(OpenFileSystemModel model, List selected); + MenuItem toMenuItem(BrowserFileSystemTabModel model, List selected); - default void init(OpenFileSystemModel model) throws Exception {} + default void init(BrowserFileSystemTabModel model) throws Exception {} default String getProFeatureId() { return null; } - default Node getIcon(OpenFileSystemModel model, List entries) { + default Node getIcon(BrowserFileSystemTabModel model, List entries) { return null; } @@ -76,9 +76,9 @@ public interface BrowserAction { return false; } - ObservableValue getName(OpenFileSystemModel model, List entries); + ObservableValue getName(BrowserFileSystemTabModel model, List entries); - default boolean isApplicable(OpenFileSystemModel model, List entries) { + default boolean isApplicable(BrowserFileSystemTabModel model, List entries) { return true; } @@ -86,7 +86,7 @@ public interface BrowserAction { return true; } - default boolean isActive(OpenFileSystemModel model, List entries) { + default boolean isActive(BrowserFileSystemTabModel model, List entries) { return true; } diff --git a/app/src/main/java/io/xpipe/app/browser/action/ApplicationPathAction.java b/app/src/main/java/io/xpipe/app/browser/action/BrowserApplicationPathAction.java similarity index 56% rename from app/src/main/java/io/xpipe/app/browser/action/ApplicationPathAction.java rename to app/src/main/java/io/xpipe/app/browser/action/BrowserApplicationPathAction.java index 45c5f3e20..5a5c9b572 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/ApplicationPathAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/BrowserApplicationPathAction.java @@ -1,22 +1,22 @@ package io.xpipe.app.browser.action; import io.xpipe.app.browser.file.BrowserEntry; -import io.xpipe.app.browser.fs.OpenFileSystemModel; +import io.xpipe.app.browser.file.BrowserFileSystemTabModel; import java.util.List; -public interface ApplicationPathAction extends BrowserAction { +public interface BrowserApplicationPathAction extends BrowserAction { String getExecutable(); @Override - default void init(OpenFileSystemModel model) { + default void init(BrowserFileSystemTabModel model) { // Cache result for later calls model.getCache().isApplicationInPath(getExecutable()); } @Override - default boolean isActive(OpenFileSystemModel model, List entries) { + default boolean isActive(BrowserFileSystemTabModel model, List entries) { return model.getCache().isApplicationInPath(getExecutable()); } } diff --git a/app/src/main/java/io/xpipe/app/browser/action/BranchAction.java b/app/src/main/java/io/xpipe/app/browser/action/BrowserBranchAction.java similarity index 76% rename from app/src/main/java/io/xpipe/app/browser/action/BranchAction.java rename to app/src/main/java/io/xpipe/app/browser/action/BrowserBranchAction.java index b30222c62..cf2900cd6 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/BranchAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/BrowserBranchAction.java @@ -1,7 +1,7 @@ package io.xpipe.app.browser.action; import io.xpipe.app.browser.file.BrowserEntry; -import io.xpipe.app.browser.fs.OpenFileSystemModel; +import io.xpipe.app.browser.file.BrowserFileSystemTabModel; import io.xpipe.app.util.LicenseProvider; import javafx.scene.control.Menu; @@ -11,9 +11,9 @@ import org.kordamp.ikonli.javafx.FontIcon; import java.util.List; -public interface BranchAction extends BrowserAction { +public interface BrowserBranchAction extends BrowserAction { - default MenuItem toMenuItem(OpenFileSystemModel model, List selected) { + default MenuItem toMenuItem(BrowserFileSystemTabModel model, List selected) { var m = new Menu(getName(model, selected).getValue() + " ..."); for (var sub : getBranchingActions(model, selected)) { var subselected = resolveFilesIfNeeded(selected); @@ -37,5 +37,5 @@ public interface BranchAction extends BrowserAction { return m; } - List getBranchingActions(OpenFileSystemModel model, List entries); + List getBranchingActions(BrowserFileSystemTabModel model, List entries); } diff --git a/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java b/app/src/main/java/io/xpipe/app/browser/action/BrowserLeafAction.java similarity index 79% rename from app/src/main/java/io/xpipe/app/browser/action/LeafAction.java rename to app/src/main/java/io/xpipe/app/browser/action/BrowserLeafAction.java index 5c1ad7815..75bbdac93 100644 --- a/app/src/main/java/io/xpipe/app/browser/action/LeafAction.java +++ b/app/src/main/java/io/xpipe/app/browser/action/BrowserLeafAction.java @@ -1,9 +1,9 @@ package io.xpipe.app.browser.action; import io.xpipe.app.browser.file.BrowserEntry; -import io.xpipe.app.browser.fs.OpenFileSystemModel; -import io.xpipe.app.fxcomps.impl.TooltipAugment; -import io.xpipe.app.fxcomps.util.BindingsHelper; +import io.xpipe.app.browser.file.BrowserFileSystemTabModel; +import io.xpipe.app.comp.base.TooltipAugment; +import io.xpipe.app.util.BindingsHelper; import io.xpipe.app.util.BooleanScope; import io.xpipe.app.util.LicenseProvider; import io.xpipe.app.util.ThreadHelper; @@ -17,18 +17,13 @@ import org.kordamp.ikonli.javafx.FontIcon; import java.util.List; -public interface LeafAction extends BrowserAction { +public interface BrowserLeafAction extends BrowserAction { - void execute(OpenFileSystemModel model, List entries) throws Exception; + void execute(BrowserFileSystemTabModel model, List entries) throws Exception; - default Button toButton(Region root, OpenFileSystemModel model, List selected) { + default Button toButton(Region root, BrowserFileSystemTabModel model, List selected) { var b = new Button(); b.setOnAction(event -> { - // Only accept shortcut actions in the current tab - if (!model.equals(model.getBrowserModel().getSelectedEntry().getValue())) { - return; - } - ThreadHelper.runFailableAsync(() -> { BooleanScope.executeExclusive(model.getBusy(), () -> { if (model.getFileSystem() == null) { @@ -71,13 +66,12 @@ public interface LeafAction extends BrowserAction { return b; } - default MenuItem toMenuItem(OpenFileSystemModel model, List selected) { + default MenuItem toMenuItem(BrowserFileSystemTabModel model, List selected) { var name = getName(model, selected); var mi = new MenuItem(); mi.textProperty().bind(BindingsHelper.map(name, s -> { - if (getProFeatureId() != null - && !LicenseProvider.get().getFeature(getProFeatureId()).isSupported()) { - return s + " (Pro)"; + if (getProFeatureId() != null) { + return LicenseProvider.get().getFeature(getProFeatureId()).suffix(s); } return s; })); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserBreadcrumbBar.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserBreadcrumbBar.java similarity index 91% rename from app/src/main/java/io/xpipe/app/browser/BrowserBreadcrumbBar.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserBreadcrumbBar.java index 6b31779af..f6406a6cf 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserBreadcrumbBar.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserBreadcrumbBar.java @@ -1,8 +1,7 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; -import io.xpipe.app.browser.fs.OpenFileSystemModel; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.util.PlatformThread; +import io.xpipe.app.comp.SimpleComp; +import io.xpipe.app.util.PlatformThread; import io.xpipe.core.store.FileNames; import javafx.scene.Node; @@ -18,9 +17,9 @@ import java.util.ArrayList; public class BrowserBreadcrumbBar extends SimpleComp { - private final OpenFileSystemModel model; + private final BrowserFileSystemTabModel model; - public BrowserBreadcrumbBar(OpenFileSystemModel model) { + public BrowserBreadcrumbBar(BrowserFileSystemTabModel model) { this.model = model; } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserClipboard.java similarity index 94% rename from app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserClipboard.java index c07dfc0e6..ff1b380d4 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserClipboard.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserClipboard.java @@ -1,8 +1,5 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; -import io.xpipe.app.browser.file.BrowserEntry; -import io.xpipe.app.browser.file.BrowserFileTransferMode; -import io.xpipe.app.browser.file.LocalFileSystem; import io.xpipe.app.ext.ProcessControlProvider; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.app.util.ThreadHelper; @@ -55,7 +52,7 @@ public class BrowserClipboard { var entries = new ArrayList(); for (Path file : files) { - entries.add(LocalFileSystem.getLocalBrowserEntry(file)); + entries.add(BrowserLocalFileSystem.getLocalBrowserEntry(file)); } currentCopyClipboard.setValue( diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserConnectionListComp.java similarity index 92% rename from app/src/main/java/io/xpipe/app/browser/BrowserBookmarkComp.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserConnectionListComp.java index 446438b16..0926b5499 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserConnectionListComp.java @@ -1,11 +1,11 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.CompStructure; +import io.xpipe.app.comp.SimpleComp; import io.xpipe.app.comp.store.*; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.CompStructure; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.storage.DataStoreEntry; +import io.xpipe.app.util.PlatformThread; import javafx.beans.binding.Bindings; import javafx.beans.property.*; @@ -19,7 +19,7 @@ import java.util.HashSet; import java.util.function.BiConsumer; import java.util.function.Predicate; -public final class BrowserBookmarkComp extends SimpleComp { +public final class BrowserConnectionListComp extends SimpleComp { private static final PseudoClass SELECTED = PseudoClass.getPseudoClass("selected"); private final ObservableValue selected; @@ -28,7 +28,7 @@ public final class BrowserBookmarkComp extends SimpleComp { private final Property category; private final Property filter; - public BrowserBookmarkComp( + public BrowserConnectionListComp( ObservableValue selected, Predicate applicable, BiConsumer action, diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkHeaderComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserConnectionListFilterComp.java similarity index 89% rename from app/src/main/java/io/xpipe/app/browser/BrowserBookmarkHeaderComp.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserConnectionListFilterComp.java index 6e67c0a84..1a9f5688e 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserBookmarkHeaderComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserConnectionListFilterComp.java @@ -1,11 +1,11 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; +import io.xpipe.app.comp.SimpleComp; +import io.xpipe.app.comp.base.FilterComp; +import io.xpipe.app.comp.base.HorizontalComp; import io.xpipe.app.comp.store.StoreCategoryWrapper; import io.xpipe.app.comp.store.StoreViewState; import io.xpipe.app.core.AppFont; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.impl.FilterComp; -import io.xpipe.app.fxcomps.impl.HorizontalComp; import io.xpipe.app.util.DataStoreCategoryChoiceComp; import javafx.beans.property.Property; @@ -19,7 +19,7 @@ import lombok.Getter; import java.util.List; @Getter -public final class BrowserBookmarkHeaderComp extends SimpleComp { +public final class BrowserConnectionListFilterComp extends SimpleComp { private final Property category = new SimpleObjectProperty<>(StoreViewState.get().getActiveCategory().getValue()); diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java index 4fb3cc424..ce70027b0 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserContextMenu.java @@ -1,7 +1,6 @@ package io.xpipe.app.browser.file; import io.xpipe.app.browser.action.BrowserAction; -import io.xpipe.app.browser.fs.OpenFileSystemModel; import io.xpipe.app.core.AppFont; import io.xpipe.app.util.InputHelper; @@ -13,11 +12,11 @@ import java.util.List; public final class BrowserContextMenu extends ContextMenu { - private final OpenFileSystemModel model; + private final BrowserFileSystemTabModel model; private final BrowserEntry source; private final boolean quickAccess; - public BrowserContextMenu(OpenFileSystemModel model, BrowserEntry source, boolean quickAccess) { + public BrowserContextMenu(BrowserFileSystemTabModel model, BrowserEntry source, boolean quickAccess) { this.model = model; this.source = source; this.quickAccess = quickAccess; diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java index 68b0e70fe..78784f670 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListComp.java @@ -1,12 +1,10 @@ package io.xpipe.app.browser.file; import io.xpipe.app.browser.action.BrowserAction; -import io.xpipe.app.comp.base.LazyTextFieldComp; +import io.xpipe.app.comp.SimpleComp; import io.xpipe.app.core.AppI18n; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.impl.PrettyImageHelper; -import io.xpipe.app.fxcomps.util.PlatformThread; import io.xpipe.app.util.*; +import io.xpipe.app.util.PlatformThread; import io.xpipe.core.process.OsType; import io.xpipe.core.store.FileEntry; import io.xpipe.core.store.FileInfo; @@ -16,30 +14,23 @@ import io.xpipe.core.store.FileNames; import javafx.application.Platform; import javafx.beans.binding.Bindings; import javafx.beans.property.*; -import javafx.beans.value.ChangeListener; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.css.PseudoClass; import javafx.geometry.Bounds; -import javafx.geometry.Pos; -import javafx.geometry.Side; -import javafx.scene.AccessibleRole; -import javafx.scene.Node; import javafx.scene.control.*; import javafx.scene.control.skin.TableViewSkin; import javafx.scene.control.skin.VirtualFlow; import javafx.scene.input.*; -import javafx.scene.layout.HBox; -import javafx.scene.layout.Priority; import javafx.scene.layout.Region; -import atlantafx.base.controls.Spacer; import atlantafx.base.theme.Styles; import java.time.Duration; import java.time.Instant; import java.time.ZoneId; import java.util.ArrayList; +import java.util.Arrays; import java.util.Comparator; import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; @@ -81,7 +72,8 @@ public final class BrowserFileListComp extends SimpleComp { : null)); filenameCol.setComparator(Comparator.comparing(String::toLowerCase)); filenameCol.setSortType(ASCENDING); - filenameCol.setCellFactory(col -> new FilenameCell(fileList.getEditing(), col.getTableView())); + filenameCol.setCellFactory(col -> + new BrowserFileListNameCell(fileList, typedSelection, fileList.getEditing(), col.getTableView())); filenameCol.setReorderable(false); filenameCol.setResizable(false); @@ -130,7 +122,7 @@ public final class BrowserFileListComp extends SimpleComp { table.setAccessibleText("Directory contents"); table.setPlaceholder(new Region()); table.getStyleClass().add(Styles.STRIPED); - table.getColumns().setAll(filenameCol, sizeCol, modeCol, ownerCol, mtimeCol); + table.getColumns().setAll(filenameCol, mtimeCol, modeCol, ownerCol, sizeCol); table.getSortOrder().add(filenameCol); table.setFocusTraversable(true); table.setSortPolicy(param -> { @@ -138,6 +130,34 @@ public final class BrowserFileListComp extends SimpleComp { return true; }); table.setFixedCellSize(32.0); + + prepareColumnVisibility(table, ownerCol, filenameCol); + prepareTableScrollFix(table); + prepareTableSelectionModel(table); + prepareTableShortcuts(table); + prepareTableEntries(table); + prepareTableChanges(table, filenameCol, mtimeCol, modeCol, ownerCol); + prepareTypedSelectionModel(table); + + return table; + } + + private static void prepareTableScrollFix(TableView table) { + table.lookupAll(".scroll-bar").stream() + .filter(node -> node.getPseudoClassStates().contains(PseudoClass.getPseudoClass("horizontal"))) + .findFirst() + .ifPresent(node -> { + Region region = (Region) node; + region.setMinHeight(0); + region.setPrefHeight(0); + region.setMaxHeight(0); + }); + } + + private void prepareColumnVisibility( + TableView table, + TableColumn ownerCol, + TableColumn filenameCol) { var os = fileList.getFileSystemModel() .getFileSystem() .getShell() @@ -150,24 +170,6 @@ public final class BrowserFileListComp extends SimpleComp { var width = getFilenameWidth(table); filenameCol.setPrefWidth(width); }); - - table.lookupAll(".scroll-bar").stream() - .filter(node -> node.getPseudoClassStates().contains(PseudoClass.getPseudoClass("horizontal"))) - .findFirst() - .ifPresent(node -> { - Region region = (Region) node; - region.setMinHeight(0); - region.setPrefHeight(0); - region.setMaxHeight(0); - }); - - prepareTableSelectionModel(table); - prepareTableShortcuts(table); - prepareTableEntries(table); - prepareTableChanges(table, filenameCol, mtimeCol, modeCol, ownerCol); - prepareTypedSelectionModel(table); - - return table; } private double getFilenameWidth(TableView tableView) { @@ -274,8 +276,15 @@ public final class BrowserFileListComp extends SimpleComp { } table.getSelectionModel().setCellSelectionEnabled(false); + var updateFromModel = new BooleanScope(new SimpleBooleanProperty()); table.getSelectionModel().getSelectedItems().addListener((ListChangeListener) c -> { - fileList.getSelection().setAll(c.getList()); + if (updateFromModel.get()) { + return; + } + + try (var ignored = updateFromModel) { + fileList.getSelection().setAll(c.getList()); + } }); fileList.getSelection().addListener((ListChangeListener) c -> { @@ -284,16 +293,27 @@ public final class BrowserFileListComp extends SimpleComp { } Platform.runLater(() -> { - if (c.getList().isEmpty()) { + var tableIndices = table.getSelectionModel().getSelectedItems().stream() + .mapToInt(entry -> table.getItems().indexOf(entry)) + .toArray(); + var indices = c.getList().stream() + .mapToInt(entry -> table.getItems().indexOf(entry)) + .toArray(); + if (Arrays.equals(indices, tableIndices)) { + return; + } + + if (indices.length == 0) { table.getSelectionModel().clearSelection(); return; } - var indices = c.getList().stream() - .mapToInt(entry -> table.getItems().indexOf(entry)) - .toArray(); - table.getSelectionModel() - .selectIndices(table.getItems().indexOf(c.getList().getFirst()), indices); + if (indices.length == 1) { + table.getSelectionModel().clearAndSelect(indices[0]); + } else { + table.getSelectionModel().clearSelection(); + table.getSelectionModel().selectIndices(indices[0], indices); + } }); }); } @@ -313,8 +333,10 @@ public final class BrowserFileListComp extends SimpleComp { .filter(browserAction -> browserAction.getShortcut().match(event)) .findAny(); action.ifPresent(browserAction -> { + // Prevent concurrent modification by creating copy on platform thread + var selectionCopy = new ArrayList<>(selected); ThreadHelper.runFailableAsync(() -> { - browserAction.execute(fileList.getFileSystemModel(), selected); + browserAction.execute(fileList.getFileSystemModel(), selectionCopy); }); event.consume(); }); @@ -610,157 +632,4 @@ public final class BrowserFileListComp extends SimpleComp { } } } - - private class FilenameCell extends TableCell { - - private final StringProperty img = new SimpleStringProperty(); - private final StringProperty text = new SimpleStringProperty(); - - private final BooleanProperty updating = new SimpleBooleanProperty(); - - public FilenameCell(Property editing, TableView tableView) { - accessibleTextProperty() - .bind(Bindings.createStringBinding( - () -> { - return getItem() != null ? getItem() : null; - }, - itemProperty())); - setAccessibleRole(AccessibleRole.TEXT); - - var textField = new LazyTextFieldComp(text) - .minWidth(USE_PREF_SIZE) - .createStructure() - .get(); - var quickAccess = new BrowserQuickAccessButtonComp( - () -> getTableRow().getItem(), fileList.getFileSystemModel()) - .hide(Bindings.createBooleanBinding( - () -> { - if (getTableRow() == null) { - return true; - } - - var item = getTableRow().getItem(); - var notDir = item.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY; - var isParentLink = item.getRawFileEntry() - .equals(fileList.getFileSystemModel().getCurrentParentDirectory()); - return notDir || isParentLink; - }, - itemProperty())) - .focusTraversable(false) - .createRegion(); - - editing.addListener((observable, oldValue, newValue) -> { - if (getTableRow().getItem() != null && getTableRow().getItem().equals(newValue)) { - PlatformThread.runLaterIfNeeded(() -> { - textField.setDisable(false); - textField.requestFocus(); - }); - } - }); - - ChangeListener listener = (observable, oldValue, newValue) -> { - if (updating.get()) { - return; - } - - getTableRow().requestFocus(); - var it = getTableRow().getItem(); - editing.setValue(null); - ThreadHelper.runAsync(() -> { - if (it == null) { - return; - } - - var r = fileList.rename(it, newValue); - Platform.runLater(() -> { - updateItem(getItem(), isEmpty()); - fileList.getSelection().setAll(r); - getTableView().scrollTo(r); - }); - }); - }; - text.addListener(listener); - - Node imageView = PrettyImageHelper.ofFixedSize(img, 24, 24).createRegion(); - HBox graphic = new HBox(imageView, new Spacer(5), quickAccess, new Spacer(1), textField); - quickAccess.prefHeightProperty().bind(graphic.heightProperty()); - graphic.setAlignment(Pos.CENTER_LEFT); - graphic.setPrefHeight(34); - HBox.setHgrow(textField, Priority.ALWAYS); - graphic.setAlignment(Pos.CENTER_LEFT); - setGraphic(graphic); - - InputHelper.onExactKeyCode(tableView, KeyCode.RIGHT, false, event -> { - var selected = fileList.getSelection(); - if (selected.size() == 1 && selected.getFirst() == getTableRow().getItem()) { - ((ButtonBase) quickAccess).fire(); - event.consume(); - } - }); - InputHelper.onExactKeyCode(tableView, KeyCode.SPACE, true, event -> { - var selection = typedSelection.get() + " "; - var found = fileList.getShown().getValue().stream() - .filter(browserEntry -> - browserEntry.getFileName().toLowerCase().startsWith(selection)) - .findFirst(); - // Ugly fix to prevent space from showing the menu when there is a file matching - // Due to the table view input map, these events always get sent and consumed, not allowing us to - // differentiate between these cases - if (found.isPresent()) { - return; - } - - var selected = fileList.getSelection(); - // Only show one menu across all selected entries - if (selected.size() > 0 && selected.getLast() == getTableRow().getItem()) { - var cm = new BrowserContextMenu( - fileList.getFileSystemModel(), getTableRow().getItem(), false); - ContextMenuHelper.toggleShow(cm, this, Side.RIGHT); - event.consume(); - } - }); - } - - @Override - protected void updateItem(String newName, boolean empty) { - if (updating.get()) { - super.updateItem(newName, empty); - return; - } - - try (var ignored = new BooleanScope(updating).start()) { - super.updateItem(newName, empty); - if (empty || newName == null || getTableRow().getItem() == null) { - // Don't set image as that would trigger image comp update - // and cells are emptied on each change, leading to unnecessary changes - // img.set(null); - - // Visibility seems to be bugged, so use opacity - setOpacity(0.0); - } else { - img.set(getTableRow().getItem().getIcon()); - - var isDirectory = getTableRow().getItem().getRawFileEntry().getKind() == FileKind.DIRECTORY; - pseudoClassStateChanged(FOLDER, isDirectory); - - var normalName = getTableRow().getItem().getRawFileEntry().getKind() == FileKind.LINK - ? getTableRow().getItem().getFileName() + " -> " - + getTableRow() - .getItem() - .getRawFileEntry() - .resolved() - .getPath() - : getTableRow().getItem().getFileName(); - var fileName = normalName; - var hidden = - getTableRow().getItem().getRawFileEntry().getInfo().explicitlyHidden() - || fileName.startsWith("."); - getTableRow().pseudoClassStateChanged(HIDDEN, hidden); - text.set(fileName); - // Visibility seems to be bugged, so use opacity - setOpacity(1.0); - } - } - } - } } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListCompEntry.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListCompEntry.java index 7bc1e3485..ba70bf7c4 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListCompEntry.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListCompEntry.java @@ -1,8 +1,6 @@ package io.xpipe.app.browser.file; -import io.xpipe.app.browser.BrowserClipboard; -import io.xpipe.app.browser.BrowserSelectionListComp; -import io.xpipe.app.browser.session.BrowserSessionModel; +import io.xpipe.app.browser.BrowserFullSessionModel; import io.xpipe.core.store.FileKind; import javafx.geometry.Point2D; @@ -219,7 +217,7 @@ public class BrowserFileListCompEntry { return; } - if (model.getFileSystemModel().getBrowserModel() instanceof BrowserSessionModel sessionModel) { + if (model.getFileSystemModel().getBrowserModel() instanceof BrowserFullSessionModel sessionModel) { sessionModel.getDraggingFiles().setValue(true); } var selected = model.getSelection(); @@ -229,7 +227,7 @@ public class BrowserFileListCompEntry { selected, event.isAltDown() ? BrowserFileTransferMode.MOVE : BrowserFileTransferMode.NORMAL)); - Image image = BrowserSelectionListComp.snapshot(selected); + Image image = BrowserFileSelectionListComp.snapshot(selected); db.setDragView(image, -20, 15); event.setDragDetect(true); @@ -237,7 +235,7 @@ public class BrowserFileListCompEntry { } public void onDragDone(DragEvent event) { - if (model.getFileSystemModel().getBrowserModel() instanceof BrowserSessionModel sessionModel) { + if (model.getFileSystemModel().getBrowserModel() instanceof BrowserFullSessionModel sessionModel) { sessionModel.getDraggingFiles().setValue(false); event.consume(); } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFilterComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListFilterComp.java similarity index 88% rename from app/src/main/java/io/xpipe/app/browser/BrowserFilterComp.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserFileListFilterComp.java index 2aeb38bd5..15bc918bb 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFilterComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListFilterComp.java @@ -1,10 +1,9 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; -import io.xpipe.app.browser.fs.OpenFileSystemModel; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.CompStructure; -import io.xpipe.app.fxcomps.impl.TextFieldComp; -import io.xpipe.app.fxcomps.impl.TooltipAugment; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.CompStructure; +import io.xpipe.app.comp.base.TextFieldComp; +import io.xpipe.app.comp.base.TooltipAugment; import io.xpipe.app.util.InputHelper; import javafx.beans.property.Property; @@ -20,12 +19,12 @@ import javafx.scene.layout.HBox; import atlantafx.base.theme.Styles; import org.kordamp.ikonli.javafx.FontIcon; -public class BrowserFilterComp extends Comp { +public class BrowserFileListFilterComp extends Comp { - private final OpenFileSystemModel model; + private final BrowserFileSystemTabModel model; private final Property filterString; - public BrowserFilterComp(OpenFileSystemModel model, Property filterString) { + public BrowserFileListFilterComp(BrowserFileSystemTabModel model, Property filterString) { this.model = model; this.filterString = filterString; } @@ -38,6 +37,10 @@ public class BrowserFilterComp extends Comp { button.minWidthProperty().bind(button.heightProperty()); button.setFocusTraversable(true); InputHelper.onExactKeyCode(text, KeyCode.ESCAPE, true, keyEvent -> { + if (!expanded.get()) { + return; + } + text.clear(); button.fire(); keyEvent.consume(); diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java index c4cb612c1..cfcdb5999 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListModel.java @@ -1,6 +1,5 @@ package io.xpipe.app.browser.file; -import io.xpipe.app.browser.fs.OpenFileSystemModel; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.core.process.OsType; import io.xpipe.core.store.FileEntry; @@ -27,9 +26,9 @@ public final class BrowserFileListModel { static final Comparator FILE_TYPE_COMPARATOR = Comparator.comparing(path -> path.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY); - private final OpenFileSystemModel.SelectionMode selectionMode; + private final BrowserFileSystemTabModel.SelectionMode selectionMode; - private final OpenFileSystemModel fileSystemModel; + private final BrowserFileSystemTabModel fileSystemModel; private final Property> comparatorProperty = new SimpleObjectProperty<>(FILE_TYPE_COMPARATOR); private final Property> all = new SimpleObjectProperty<>(new ArrayList<>()); @@ -40,7 +39,8 @@ public final class BrowserFileListModel { private final Property draggedOverEmpty = new SimpleBooleanProperty(); private final Property editing = new SimpleObjectProperty<>(); - public BrowserFileListModel(OpenFileSystemModel.SelectionMode selectionMode, OpenFileSystemModel fileSystemModel) { + public BrowserFileListModel( + BrowserFileSystemTabModel.SelectionMode selectionMode, BrowserFileSystemTabModel fileSystemModel) { this.selectionMode = selectionMode; this.fileSystemModel = fileSystemModel; diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListNameCell.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListNameCell.java new file mode 100644 index 000000000..865cc099e --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileListNameCell.java @@ -0,0 +1,202 @@ +package io.xpipe.app.browser.file; + +import io.xpipe.app.comp.base.LazyTextFieldComp; +import io.xpipe.app.comp.base.PrettyImageHelper; +import io.xpipe.app.util.BooleanScope; +import io.xpipe.app.util.ContextMenuHelper; +import io.xpipe.app.util.InputHelper; +import io.xpipe.app.util.PlatformThread; +import io.xpipe.app.util.ThreadHelper; +import io.xpipe.core.store.FileKind; + +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.property.*; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableStringValue; +import javafx.css.PseudoClass; +import javafx.geometry.Pos; +import javafx.geometry.Side; +import javafx.scene.AccessibleRole; +import javafx.scene.Node; +import javafx.scene.control.ButtonBase; +import javafx.scene.control.TableCell; +import javafx.scene.control.TableView; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.scene.layout.Region; + +import atlantafx.base.controls.Spacer; + +class BrowserFileListNameCell extends TableCell { + + private final BrowserFileListModel fileList; + private final ObservableStringValue typedSelection; + private final StringProperty img = new SimpleStringProperty(); + private final StringProperty text = new SimpleStringProperty(); + + private final BooleanProperty updating = new SimpleBooleanProperty(); + + public BrowserFileListNameCell( + BrowserFileListModel fileList, + ObservableStringValue typedSelection, + Property editing, + TableView tableView) { + this.fileList = fileList; + this.typedSelection = typedSelection; + + accessibleTextProperty() + .bind(Bindings.createStringBinding( + () -> { + return getItem() != null ? getItem() : null; + }, + itemProperty())); + setAccessibleRole(AccessibleRole.TEXT); + + var textField = new LazyTextFieldComp(text) + .minWidth(USE_PREF_SIZE) + .createStructure() + .get(); + var quickAccess = createQuickAccessButton(); + setupShortcuts(tableView, (ButtonBase) quickAccess); + setupRename(fileList, textField, editing); + + Node imageView = PrettyImageHelper.ofFixedSize(img, 24, 24).createRegion(); + HBox graphic = new HBox(imageView, new Spacer(5), quickAccess, new Spacer(1), textField); + quickAccess.prefHeightProperty().bind(graphic.heightProperty()); + graphic.setAlignment(Pos.CENTER_LEFT); + graphic.setPrefHeight(34); + HBox.setHgrow(textField, Priority.ALWAYS); + graphic.setAlignment(Pos.CENTER_LEFT); + setGraphic(graphic); + } + + private Region createQuickAccessButton() { + var quickAccess = new BrowserQuickAccessButtonComp(() -> getTableRow().getItem(), fileList.getFileSystemModel()) + .hide(Bindings.createBooleanBinding( + () -> { + if (getTableRow() == null) { + return true; + } + + var item = getTableRow().getItem(); + var notDir = item.getRawFileEntry().resolved().getKind() != FileKind.DIRECTORY; + var isParentLink = item.getRawFileEntry() + .equals(fileList.getFileSystemModel().getCurrentParentDirectory()); + return notDir || isParentLink; + }, + itemProperty())) + .focusTraversable(false) + .createRegion(); + return quickAccess; + } + + private void setupShortcuts(TableView tableView, ButtonBase quickAccess) { + InputHelper.onExactKeyCode(tableView, KeyCode.RIGHT, false, event -> { + var selected = fileList.getSelection(); + if (selected.size() == 1 && selected.getFirst() == getTableRow().getItem()) { + quickAccess.fire(); + event.consume(); + } + }); + InputHelper.onExactKeyCode(tableView, KeyCode.SPACE, true, event -> { + var selection = typedSelection.get() + " "; + var found = fileList.getShown().getValue().stream() + .filter(browserEntry -> + browserEntry.getFileName().toLowerCase().startsWith(selection)) + .findFirst(); + // Ugly fix to prevent space from showing the menu when there is a file matching + // Due to the table view input map, these events always get sent and consumed, not allowing us to + // differentiate between these cases + if (found.isPresent()) { + return; + } + + var selected = fileList.getSelection(); + // Only show one menu across all selected entries + if (selected.size() > 0 && selected.getLast() == getTableRow().getItem()) { + var cm = new BrowserContextMenu( + fileList.getFileSystemModel(), getTableRow().getItem(), false); + ContextMenuHelper.toggleShow(cm, this, Side.RIGHT); + event.consume(); + } + }); + } + + private void setupRename(BrowserFileListModel fileList, TextField textField, Property editing) { + ChangeListener listener = (observable, oldValue, newValue) -> { + if (updating.get()) { + return; + } + + getTableRow().requestFocus(); + var it = getTableRow().getItem(); + editing.setValue(null); + ThreadHelper.runAsync(() -> { + if (it == null) { + return; + } + + var r = fileList.rename(it, newValue); + Platform.runLater(() -> { + updateItem(getItem(), isEmpty()); + fileList.getSelection().setAll(r); + getTableView().scrollTo(r); + }); + }); + }; + text.addListener(listener); + + editing.addListener((observable, oldValue, newValue) -> { + if (getTableRow().getItem() != null && getTableRow().getItem().equals(newValue)) { + PlatformThread.runLaterIfNeeded(() -> { + textField.setDisable(false); + textField.requestFocus(); + }); + } + }); + } + + @Override + protected void updateItem(String newName, boolean empty) { + if (updating.get()) { + super.updateItem(newName, empty); + return; + } + + try (var ignored = new BooleanScope(updating).start()) { + super.updateItem(newName, empty); + if (empty || newName == null || getTableRow().getItem() == null) { + // Don't set image as that would trigger image comp update + // and cells are emptied on each change, leading to unnecessary changes + // img.set(null); + + // Visibility seems to be bugged, so use opacity + setOpacity(0.0); + } else { + img.set(getTableRow().getItem().getIcon()); + + var isDirectory = getTableRow().getItem().getRawFileEntry().getKind() == FileKind.DIRECTORY; + pseudoClassStateChanged(PseudoClass.getPseudoClass("folder"), isDirectory); + + var normalName = getTableRow().getItem().getRawFileEntry().getKind() == FileKind.LINK + ? getTableRow().getItem().getFileName() + " -> " + + getTableRow() + .getItem() + .getRawFileEntry() + .resolved() + .getPath() + : getTableRow().getItem().getFileName(); + var fileName = normalName; + var hidden = getTableRow().getItem().getRawFileEntry().getInfo().explicitlyHidden() + || fileName.startsWith("."); + getTableRow().pseudoClassStateChanged(PseudoClass.getPseudoClass("hidden"), hidden); + text.set(fileName); + // Visibility seems to be bugged, so use opacity + setOpacity(1.0); + } + } + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserFileOpener.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOpener.java similarity index 91% rename from app/src/main/java/io/xpipe/app/browser/BrowserFileOpener.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserFileOpener.java index c66cc2ff3..80a31116a 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserFileOpener.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOpener.java @@ -1,6 +1,5 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; -import io.xpipe.app.browser.fs.OpenFileSystemModel; import io.xpipe.app.core.window.AppWindowHelper; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.util.BooleanScope; @@ -20,7 +19,7 @@ import java.util.Objects; public class BrowserFileOpener { - private static OutputStream openFileOutput(OpenFileSystemModel model, FileEntry file, long totalBytes) + private static OutputStream openFileOutput(BrowserFileSystemTabModel model, FileEntry file, long totalBytes) throws Exception { var fileSystem = model.getFileSystem(); if (model.isClosed() || fileSystem.getShell().isEmpty()) { @@ -68,7 +67,7 @@ public class BrowserFileOpener { return Objects.hash(entry.getPath(), entry.getFileSystem(), entry.getKind(), entry.getInfo()); } - public static void openWithAnyApplication(OpenFileSystemModel model, FileEntry entry) { + public static void openWithAnyApplication(BrowserFileSystemTabModel model, FileEntry entry) { var file = entry.getPath(); var key = calculateKey(entry); FileBridge.get() @@ -89,7 +88,7 @@ public class BrowserFileOpener { s -> FileOpener.openWithAnyApplication(s)); } - public static void openInDefaultApplication(OpenFileSystemModel model, FileEntry entry) { + public static void openInDefaultApplication(BrowserFileSystemTabModel model, FileEntry entry) { var file = entry.getPath(); var key = calculateKey(entry); FileBridge.get() @@ -110,7 +109,7 @@ public class BrowserFileOpener { s -> FileOpener.openInDefaultApplication(s)); } - public static void openInTextEditor(OpenFileSystemModel model, FileEntry entry) { + public static void openInTextEditor(BrowserFileSystemTabModel model, FileEntry entry) { var editor = AppPrefs.get().externalEditor().getValue(); if (editor == null) { return; diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java index 5fa28b999..e6bba8efa 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileOverviewComp.java @@ -1,13 +1,12 @@ package io.xpipe.app.browser.file; -import io.xpipe.app.browser.fs.OpenFileSystemModel; import io.xpipe.app.browser.icon.BrowserIcons; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.SimpleComp; +import io.xpipe.app.comp.augment.GrowAugment; +import io.xpipe.app.comp.base.HorizontalComp; import io.xpipe.app.comp.base.ListBoxViewComp; import io.xpipe.app.comp.base.VBoxViewComp; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.augment.GrowAugment; -import io.xpipe.app.fxcomps.impl.HorizontalComp; import io.xpipe.core.store.FileEntry; import javafx.collections.ObservableList; @@ -25,7 +24,7 @@ import java.util.function.Function; @EqualsAndHashCode(callSuper = true) public class BrowserFileOverviewComp extends SimpleComp { - OpenFileSystemModel model; + BrowserFileSystemTabModel model; ObservableList list; boolean grow; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserSelectionListComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSelectionListComp.java similarity index 83% rename from app/src/main/java/io/xpipe/app/browser/BrowserSelectionListComp.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserFileSelectionListComp.java index 624d9f618..d87ca9a4a 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserSelectionListComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSelectionListComp.java @@ -1,14 +1,13 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; -import io.xpipe.app.browser.file.BrowserEntry; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.SimpleComp; import io.xpipe.app.comp.base.ListBoxViewComp; +import io.xpipe.app.comp.base.PrettyImageHelper; import io.xpipe.app.core.AppStyle; import io.xpipe.app.core.window.AppWindowHelper; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.impl.PrettyImageHelper; -import io.xpipe.app.fxcomps.util.BindingsHelper; -import io.xpipe.app.fxcomps.util.PlatformThread; +import io.xpipe.app.util.BindingsHelper; +import io.xpipe.app.util.PlatformThread; import javafx.beans.binding.Bindings; import javafx.beans.property.SimpleStringProperty; @@ -31,17 +30,17 @@ import java.util.function.Function; @Value @EqualsAndHashCode(callSuper = true) @AllArgsConstructor -public class BrowserSelectionListComp extends SimpleComp { +public class BrowserFileSelectionListComp extends SimpleComp { ObservableList list; Function> nameTransformation; - public BrowserSelectionListComp(ObservableList list) { + public BrowserFileSelectionListComp(ObservableList list) { this(list, entry -> new SimpleStringProperty(entry.getFileName())); } public static Image snapshot(ObservableList list) { - var r = new BrowserSelectionListComp(list).styleClass("drag").createRegion(); + var r = new BrowserFileSelectionListComp(list).styleClass("drag").createRegion(); var scene = new Scene(r); AppWindowHelper.setupStylesheets(scene); AppStyle.addStylesheets(scene); diff --git a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemCache.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemCache.java similarity index 92% rename from app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemCache.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemCache.java index 9d9037af9..746f588c0 100644 --- a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemCache.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemCache.java @@ -1,4 +1,4 @@ -package io.xpipe.app.browser.fs; +package io.xpipe.app.browser.file; import io.xpipe.app.util.ShellControlCache; import io.xpipe.core.process.CommandBuilder; @@ -12,14 +12,14 @@ import java.util.LinkedHashMap; import java.util.Map; @Getter -public class OpenFileSystemCache extends ShellControlCache { +public class BrowserFileSystemCache extends ShellControlCache { - private final OpenFileSystemModel model; + private final BrowserFileSystemTabModel model; private final String username; private final Map users = new LinkedHashMap<>(); private final Map groups = new LinkedHashMap<>(); - public OpenFileSystemCache(OpenFileSystemModel model) throws Exception { + public BrowserFileSystemCache(BrowserFileSystemTabModel model) throws Exception { super(model.getFileSystem().getShell().orElseThrow()); this.model = model; diff --git a/app/src/main/java/io/xpipe/app/browser/file/FileSystemHelper.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemHelper.java similarity index 89% rename from app/src/main/java/io/xpipe/app/browser/file/FileSystemHelper.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemHelper.java index 8359799e3..9f289e7e2 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/FileSystemHelper.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemHelper.java @@ -1,6 +1,5 @@ package io.xpipe.app.browser.file; -import io.xpipe.app.browser.fs.OpenFileSystemModel; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.core.process.OsType; import io.xpipe.core.store.FileEntry; @@ -11,9 +10,9 @@ import io.xpipe.core.store.FileSystem; import java.time.Instant; import java.util.List; -public class FileSystemHelper { +public class BrowserFileSystemHelper { - public static String adjustPath(OpenFileSystemModel model, String path) { + public static String adjustPath(BrowserFileSystemTabModel model, String path) { if (path == null) { return null; } @@ -46,7 +45,7 @@ public class FileSystemHelper { return path; } - public static String evaluatePath(OpenFileSystemModel model, String path) throws Exception { + public static String evaluatePath(BrowserFileSystemTabModel model, String path) throws Exception { if (path == null) { return null; } @@ -67,7 +66,7 @@ public class FileSystemHelper { } } - public static String resolveDirectoryPath(OpenFileSystemModel model, String path, boolean allowRewrite) + public static String resolveDirectoryPath(BrowserFileSystemTabModel model, String path, boolean allowRewrite) throws Exception { if (path == null) { return null; @@ -98,7 +97,7 @@ public class FileSystemHelper { return FileNames.toDirectory(resolved); } - public static void validateDirectoryPath(OpenFileSystemModel model, String path, boolean verifyExists) + public static void validateDirectoryPath(BrowserFileSystemTabModel model, String path, boolean verifyExists) throws Exception { if (path == null) { return; diff --git a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemHistory.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemHistory.java similarity index 96% rename from app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemHistory.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemHistory.java index ede908c1b..7cf61f6b2 100644 --- a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemHistory.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemHistory.java @@ -1,4 +1,4 @@ -package io.xpipe.app.browser.fs; +package io.xpipe.app.browser.file; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanBinding; @@ -9,7 +9,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -public final class OpenFileSystemHistory { +public final class BrowserFileSystemHistory { private final IntegerProperty cursor = new SimpleIntegerProperty(-1); private final List history = new ArrayList<>(); diff --git a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemSavedState.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemSavedState.java similarity index 78% rename from app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemSavedState.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemSavedState.java index fa3efa929..9040040c0 100644 --- a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemSavedState.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemSavedState.java @@ -1,4 +1,4 @@ -package io.xpipe.app.browser.fs; +package io.xpipe.app.browser.file; import io.xpipe.app.core.AppCache; import io.xpipe.core.store.FileNames; @@ -31,35 +31,36 @@ import java.util.stream.Collectors; @AllArgsConstructor @Getter -@JsonSerialize(using = OpenFileSystemSavedState.Serializer.class) -@JsonDeserialize(using = OpenFileSystemSavedState.Deserializer.class) -public class OpenFileSystemSavedState { +@JsonSerialize(using = BrowserFileSystemSavedState.Serializer.class) +@JsonDeserialize(using = BrowserFileSystemSavedState.Deserializer.class) +public class BrowserFileSystemSavedState { private static final Timer TIMEOUT_TIMER = new Timer(true); - private static final int STORED = 10; + private static final int STORED = 15; @Setter - private OpenFileSystemModel model; + private BrowserFileSystemTabModel model; private String lastDirectory; @NonNull private ObservableList recentDirectories; - public OpenFileSystemSavedState(String lastDirectory, @NonNull ObservableList recentDirectories) { + public BrowserFileSystemSavedState(String lastDirectory, @NonNull ObservableList recentDirectories) { this.lastDirectory = lastDirectory; this.recentDirectories = recentDirectories; } - public OpenFileSystemSavedState() { + public BrowserFileSystemSavedState() { lastDirectory = null; recentDirectories = FXCollections.observableList(new ArrayList<>(STORED)); } - static OpenFileSystemSavedState loadForStore(OpenFileSystemModel model) { - var state = AppCache.get("fs-state-" + model.getEntry().get().getUuid(), OpenFileSystemSavedState.class, () -> { - return new OpenFileSystemSavedState(); - }); + static BrowserFileSystemSavedState loadForStore(BrowserFileSystemTabModel model) { + var state = AppCache.getNonNull( + "fs-state-" + model.getEntry().get().getUuid(), BrowserFileSystemSavedState.class, () -> { + return new BrowserFileSystemSavedState(); + }); state.setModel(model); return state; } @@ -121,14 +122,14 @@ public class OpenFileSystemSavedState { } } - public static class Serializer extends StdSerializer { + public static class Serializer extends StdSerializer { protected Serializer() { - super(OpenFileSystemSavedState.class); + super(BrowserFileSystemSavedState.class); } @Override - public void serialize(OpenFileSystemSavedState value, JsonGenerator gen, SerializerProvider provider) + public void serialize(BrowserFileSystemSavedState value, JsonGenerator gen, SerializerProvider provider) throws IOException { var node = JsonNodeFactory.instance.objectNode(); node.set("recentDirectories", JacksonMapper.getDefault().valueToTree(value.getRecentDirectories())); @@ -136,10 +137,10 @@ public class OpenFileSystemSavedState { } } - public static class Deserializer extends StdDeserializer { + public static class Deserializer extends StdDeserializer { protected Deserializer() { - super(OpenFileSystemSavedState.class); + super(BrowserFileSystemSavedState.class); } private static Predicate distinctBy(Function f) { @@ -149,7 +150,7 @@ public class OpenFileSystemSavedState { @Override @SneakyThrows - public OpenFileSystemSavedState deserialize(JsonParser p, DeserializationContext ctxt) { + public BrowserFileSystemSavedState deserialize(JsonParser p, DeserializationContext ctxt) { var tree = (ObjectNode) JacksonMapper.getDefault().readTree(p); JavaType javaType = JacksonMapper.getDefault() .getTypeFactory() @@ -163,7 +164,7 @@ public class OpenFileSystemSavedState { .map(recentEntry -> new RecentEntry(FileNames.toDirectory(recentEntry.directory), recentEntry.time)) .filter(distinctBy(recentEntry -> recentEntry.getDirectory())) .collect(Collectors.toCollection(ArrayList::new)); - return new OpenFileSystemSavedState(null, FXCollections.observableList(cleaned)); + return new BrowserFileSystemSavedState(null, FXCollections.observableList(cleaned)); } } diff --git a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabComp.java similarity index 88% rename from app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabComp.java index c982416fd..d603faa4e 100644 --- a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabComp.java @@ -1,21 +1,15 @@ -package io.xpipe.app.browser.fs; +package io.xpipe.app.browser.file; -import io.xpipe.app.browser.BrowserFilterComp; -import io.xpipe.app.browser.BrowserNavBar; -import io.xpipe.app.browser.BrowserOverviewComp; -import io.xpipe.app.browser.BrowserStatusBarComp; import io.xpipe.app.browser.action.BrowserAction; -import io.xpipe.app.browser.file.BrowserContextMenu; -import io.xpipe.app.browser.file.BrowserFileListComp; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.SimpleComp; +import io.xpipe.app.comp.SimpleCompStructure; +import io.xpipe.app.comp.augment.ContextMenuAugment; import io.xpipe.app.comp.base.ModalOverlayComp; import io.xpipe.app.comp.base.MultiContentComp; +import io.xpipe.app.comp.base.TooltipAugment; +import io.xpipe.app.comp.base.VerticalComp; import io.xpipe.app.core.AppFont; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.SimpleCompStructure; -import io.xpipe.app.fxcomps.augment.ContextMenuAugment; -import io.xpipe.app.fxcomps.impl.TooltipAugment; -import io.xpipe.app.fxcomps.impl.VerticalComp; import io.xpipe.app.util.InputHelper; import javafx.geometry.Pos; @@ -37,12 +31,12 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -public class OpenFileSystemComp extends SimpleComp { +public class BrowserFileSystemTabComp extends SimpleComp { - private final OpenFileSystemModel model; + private final BrowserFileSystemTabModel model; private final boolean showStatusBar; - public OpenFileSystemComp(OpenFileSystemModel model, boolean showStatusBar) { + public BrowserFileSystemTabComp(BrowserFileSystemTabModel model, boolean showStatusBar) { this.model = model; this.showStatusBar = showStatusBar; } @@ -82,12 +76,12 @@ public class OpenFileSystemComp extends SimpleComp { menuButton.disableProperty().bind(model.getInOverview()); menuButton.setAccessibleText("Directory options"); - var filter = new BrowserFilterComp(model, model.getFilter()).createStructure(); + var filter = new BrowserFileListFilterComp(model, model.getFilter()).createStructure(); var topBar = new HBox(); topBar.setAlignment(Pos.CENTER); topBar.getStyleClass().add("top-bar"); - var navBar = new BrowserNavBar(model).createStructure(); + var navBar = new BrowserNavBarComp(model).createStructure(); filter.textField().prefHeightProperty().bind(navBar.get().heightProperty()); AppFont.medium(navBar.get()); topBar.getChildren() @@ -182,7 +176,7 @@ public class OpenFileSystemComp extends SimpleComp { }); }); - var home = new BrowserOverviewComp(model).styleClass("browser-content"); + var home = new BrowserOverviewComp(model).styleClass("browser-overview"); var stack = new MultiContentComp(Map.of( home, model.getCurrentPath().isNull(), diff --git a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java similarity index 80% rename from app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java index 1accf26f4..864f3a8de 100644 --- a/app/src/main/java/io/xpipe/app/browser/fs/OpenFileSystemModel.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileSystemTabModel.java @@ -1,34 +1,30 @@ -package io.xpipe.app.browser.fs; +package io.xpipe.app.browser.file; -import io.xpipe.app.browser.BrowserSavedState; -import io.xpipe.app.browser.BrowserSavedStateImpl; -import io.xpipe.app.browser.BrowserTransferProgress; +import io.xpipe.app.browser.BrowserAbstractSessionModel; +import io.xpipe.app.browser.BrowserFullSessionModel; +import io.xpipe.app.browser.BrowserStoreSessionTab; import io.xpipe.app.browser.action.BrowserAction; -import io.xpipe.app.browser.file.BrowserFileListModel; -import io.xpipe.app.browser.file.BrowserFileTransferMode; -import io.xpipe.app.browser.file.BrowserFileTransferOperation; -import io.xpipe.app.browser.file.FileSystemHelper; -import io.xpipe.app.browser.session.BrowserAbstractSessionModel; -import io.xpipe.app.browser.session.BrowserSessionTab; +import io.xpipe.app.comp.Comp; import io.xpipe.app.comp.base.ModalOverlayComp; +import io.xpipe.app.core.window.AppMainWindow; import io.xpipe.app.ext.ProcessControlProvider; -import io.xpipe.app.fxcomps.Comp; +import io.xpipe.app.ext.ShellStore; import io.xpipe.app.issue.ErrorEvent; +import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.storage.DataStorage; import io.xpipe.app.storage.DataStoreEntryRef; +import io.xpipe.app.terminal.*; import io.xpipe.app.util.BooleanScope; -import io.xpipe.app.util.TerminalLauncher; import io.xpipe.app.util.ThreadHelper; -import io.xpipe.core.process.CommandBuilder; -import io.xpipe.core.process.ShellControl; -import io.xpipe.core.process.ShellDialects; -import io.xpipe.core.process.ShellOpenFunction; +import io.xpipe.core.process.*; import io.xpipe.core.store.*; import io.xpipe.core.util.FailableConsumer; import io.xpipe.core.util.FailableRunnable; import javafx.beans.binding.Bindings; import javafx.beans.property.*; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import lombok.Getter; import lombok.SneakyThrows; @@ -38,23 +34,25 @@ import java.nio.file.Path; import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.UUID; import java.util.stream.Stream; @Getter -public final class OpenFileSystemModel extends BrowserSessionTab { +public final class BrowserFileSystemTabModel extends BrowserStoreSessionTab { private final Property filter = new SimpleStringProperty(); private final BrowserFileListModel fileList; private final ReadOnlyObjectWrapper currentPath = new ReadOnlyObjectWrapper<>(); - private final OpenFileSystemHistory history = new OpenFileSystemHistory(); + private final BrowserFileSystemHistory history = new BrowserFileSystemHistory(); private final Property overlay = new SimpleObjectProperty<>(); private final BooleanProperty inOverview = new SimpleBooleanProperty(); private final Property progress = new SimpleObjectProperty<>(); + private final ObservableList terminalRequests = FXCollections.observableArrayList(); private FileSystem fileSystem; - private OpenFileSystemSavedState savedState; - private OpenFileSystemCache cache; + private BrowserFileSystemSavedState savedState; + private BrowserFileSystemCache cache; - public OpenFileSystemModel( + public BrowserFileSystemTabModel( BrowserAbstractSessionModel model, DataStoreEntryRef entry, SelectionMode selectionMode) { @@ -69,7 +67,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab comp() { - return new OpenFileSystemComp(this, true); + return new BrowserFileSystemTabComp(this, true); } @Override @@ -99,12 +97,12 @@ public final class OpenFileSystemModel extends BrowserSessionTab cdSyncOrRetry(s, false)); } + private boolean shouldLaunchSplitTerminal() { + if (!AppPrefs.get().enableTerminalDocking().get()) { + return false; + } + + if (OsType.getLocal() != OsType.WINDOWS) { + return false; + } + + if (AppMainWindow.getInstance().getStage().getWidth() <= 1280) { + return false; + } + + var term = AppPrefs.get().terminalType().getValue(); + if (term == null || term.getOpenFormat() == TerminalOpenFormat.TABBED) { + return false; + } + + if (!(browserModel instanceof BrowserFullSessionModel f)) { + return false; + } + + // Check if the right side is already occupied + var existingSplit = f.getEffectiveRightTab().getValue(); + if (existingSplit != null && !(existingSplit instanceof BrowserTerminalDockTabModel)) { + return false; + } + + return true; + } + public Optional cdSyncOrRetry(String path, boolean customInput) { if (Objects.equals(path, currentPath.get())) { return Optional.empty(); @@ -226,7 +255,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab adjustedPath .toLowerCase() .startsWith(dialect.getExecutableName().toLowerCase()))) { - TerminalLauncher.open( - entry.getEntry(), - name, - directory, - fileSystem - .getShell() - .get() - .singularSubShell( - ShellOpenFunction.of(CommandBuilder.ofString(adjustedPath), false))); + var cc = fileSystem + .getShell() + .get() + .singularSubShell(ShellOpenFunction.of(CommandBuilder.ofString(adjustedPath), false)); + openTerminalAsync(name, directory, cc, true); } else { - TerminalLauncher.open( - entry.getEntry(), - name, - directory, - fileSystem.getShell().get().command(adjustedPath)); + var cc = fileSystem.getShell().get().command(adjustedPath); + openTerminalAsync(name, directory, cc, true); } }); return Optional.ofNullable(currentPath.get()); @@ -275,7 +297,7 @@ public final class OpenFileSystemModel extends BrowserSessionTab { if (fileSystem == null) { return; @@ -529,10 +552,16 @@ public final class OpenFileSystemModel extends BrowserSessionTab { if (fileSystem.getShell().isPresent()) { - var connection = fileSystem.getShell().get(); - var name = (directory != null ? directory + " - " : "") - + entry.get().getName(); - TerminalLauncher.open(entry.getEntry(), name, directory, connection); + var dock = shouldLaunchSplitTerminal() && dockIfPossible; + var uuid = UUID.randomUUID(); + terminalRequests.add(uuid); + if (dock + && browserModel instanceof BrowserFullSessionModel fullSessionModel + && !(fullSessionModel.getSplits().get(this) instanceof BrowserTerminalDockTabModel)) { + fullSessionModel.splitTab( + this, new BrowserTerminalDockTabModel(browserModel, this, terminalRequests)); + } + TerminalLauncher.open(entry.getEntry(), name, directory, processControl, uuid, !dock); // Restart connection as we will have to start it anyway, so we speed it up by doing it preemptively startIfNeeded(); diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java index 83f8bf4a4..b753464eb 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserFileTransferOperation.java @@ -1,6 +1,5 @@ package io.xpipe.app.browser.file; -import io.xpipe.app.browser.BrowserTransferProgress; import io.xpipe.app.issue.ErrorEvent; import io.xpipe.core.store.*; @@ -50,7 +49,7 @@ public class BrowserFileTransferOperation { } try { - return LocalFileSystem.getLocalFileEntry(path); + return BrowserLocalFileSystem.getLocalFileEntry(path); } catch (Exception e) { throw new RuntimeException(e); } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserGreetingComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserGreetingComp.java similarity index 90% rename from app/src/main/java/io/xpipe/app/browser/BrowserGreetingComp.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserGreetingComp.java index b527746c2..280c730e7 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserGreetingComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserGreetingComp.java @@ -1,10 +1,10 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; +import io.xpipe.app.comp.SimpleComp; import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppI18n; import io.xpipe.app.core.AppLayoutModel; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.util.PlatformThread; +import io.xpipe.app.util.PlatformThread; import io.xpipe.core.process.OsType; import javafx.scene.control.Label; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserSavedState.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserHistorySavedState.java similarity index 83% rename from app/src/main/java/io/xpipe/app/browser/BrowserSavedState.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserHistorySavedState.java index 3328f780b..c4b0a4fd4 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserSavedState.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserHistorySavedState.java @@ -1,4 +1,4 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; import javafx.collections.ObservableList; @@ -9,7 +9,7 @@ import lombok.extern.jackson.Jacksonized; import java.util.UUID; -public interface BrowserSavedState { +public interface BrowserHistorySavedState { void add(Entry entry); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserHistorySavedStateImpl.java similarity index 66% rename from app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserHistorySavedStateImpl.java index cbbd14545..f8278f349 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserSavedStateImpl.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserHistorySavedStateImpl.java @@ -1,4 +1,4 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; import io.xpipe.app.core.AppCache; import io.xpipe.core.util.JacksonMapper; @@ -19,36 +19,36 @@ import lombok.Value; import java.util.List; @Value -@JsonDeserialize(using = BrowserSavedStateImpl.Deserializer.class) -public class BrowserSavedStateImpl implements BrowserSavedState { +@JsonDeserialize(using = BrowserHistorySavedStateImpl.Deserializer.class) +public class BrowserHistorySavedStateImpl implements BrowserHistorySavedState { @JsonSerialize(as = List.class) ObservableList lastSystems; - public BrowserSavedStateImpl(List lastSystems) { + public BrowserHistorySavedStateImpl(List lastSystems) { this.lastSystems = FXCollections.observableArrayList(lastSystems); } - private static BrowserSavedStateImpl INSTANCE; + private static BrowserHistorySavedStateImpl INSTANCE; - public static BrowserSavedState get() { + public static BrowserHistorySavedState get() { if (INSTANCE == null) { INSTANCE = load(); } return INSTANCE; } - private static BrowserSavedStateImpl load() { - return AppCache.get("browser-state", BrowserSavedStateImpl.class, () -> { - return new BrowserSavedStateImpl(FXCollections.observableArrayList()); + private static BrowserHistorySavedStateImpl load() { + return AppCache.getNonNull("browser-state", BrowserHistorySavedStateImpl.class, () -> { + return new BrowserHistorySavedStateImpl(FXCollections.observableArrayList()); }); } @Override - public synchronized void add(BrowserSavedState.Entry entry) { + public synchronized void add(BrowserHistorySavedState.Entry entry) { lastSystems.removeIf(s -> s.getUuid().equals(entry.getUuid())); lastSystems.addFirst(entry); - if (lastSystems.size() > 10) { + if (lastSystems.size() > 15) { lastSystems.removeLast(); } } @@ -63,15 +63,15 @@ public class BrowserSavedStateImpl implements BrowserSavedState { return lastSystems; } - public static class Deserializer extends StdDeserializer { + public static class Deserializer extends StdDeserializer { protected Deserializer() { - super(BrowserSavedStateImpl.class); + super(BrowserHistorySavedStateImpl.class); } @Override @SneakyThrows - public BrowserSavedStateImpl deserialize(JsonParser p, DeserializationContext ctxt) { + public BrowserHistorySavedStateImpl deserialize(JsonParser p, DeserializationContext ctxt) { var tree = (ObjectNode) JacksonMapper.getDefault().readTree(p); JavaType javaType = JacksonMapper.getDefault().getTypeFactory().constructCollectionLikeType(List.class, Entry.class); @@ -79,7 +79,7 @@ public class BrowserSavedStateImpl implements BrowserSavedState { if (ls == null) { ls = List.of(); } - return new BrowserSavedStateImpl(ls); + return new BrowserHistorySavedStateImpl(ls); } } } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserHistoryTabComp.java similarity index 86% rename from app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserHistoryTabComp.java index 87a1b186f..57aa3fce3 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserWelcomeComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserHistoryTabComp.java @@ -1,20 +1,20 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; -import io.xpipe.app.browser.session.BrowserSessionModel; +import io.xpipe.app.browser.BrowserFullSessionModel; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.SimpleComp; import io.xpipe.app.comp.base.ButtonComp; +import io.xpipe.app.comp.base.HorizontalComp; +import io.xpipe.app.comp.base.LabelComp; import io.xpipe.app.comp.base.ListBoxViewComp; +import io.xpipe.app.comp.base.PrettyImageHelper; +import io.xpipe.app.comp.base.PrettySvgComp; import io.xpipe.app.comp.base.TileButtonComp; import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppI18n; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.impl.HorizontalComp; -import io.xpipe.app.fxcomps.impl.LabelComp; -import io.xpipe.app.fxcomps.impl.PrettyImageHelper; -import io.xpipe.app.fxcomps.impl.PrettySvgComp; -import io.xpipe.app.fxcomps.util.BindingsHelper; -import io.xpipe.app.fxcomps.util.DerivedObservableList; import io.xpipe.app.storage.DataStorage; +import io.xpipe.app.util.BindingsHelper; +import io.xpipe.app.util.DerivedObservableList; import io.xpipe.app.util.ThreadHelper; import javafx.beans.binding.Bindings; @@ -35,17 +35,17 @@ import atlantafx.base.theme.Styles; import java.util.List; -public class BrowserWelcomeComp extends SimpleComp { +public class BrowserHistoryTabComp extends SimpleComp { - private final BrowserSessionModel model; + private final BrowserFullSessionModel model; - public BrowserWelcomeComp(BrowserSessionModel model) { + public BrowserHistoryTabComp(BrowserFullSessionModel model) { this.model = model; } @Override protected Region createSimple() { - var state = BrowserSavedStateImpl.get(); + var state = BrowserHistorySavedStateImpl.get(); var welcome = new BrowserGreetingComp().createSimple(); @@ -124,7 +124,7 @@ public class BrowserWelcomeComp extends SimpleComp { var layout = new VBox(); layout.getStyleClass().add("welcome"); - layout.setPadding(new Insets(60, 40, 40, 50)); + layout.setPadding(new Insets(25, 40, 40, 40)); layout.setSpacing(18); layout.getChildren().add(hbox); layout.getChildren().add(Comp.separator().hide(empty).createRegion()); @@ -140,13 +140,14 @@ public class BrowserWelcomeComp extends SimpleComp { .hide(empty) .accessibleTextKey("restoreAllSessions"); layout.getChildren().add(tile.createRegion()); + AppFont.medium(layout); return layout; } - private Comp entryButton(BrowserSavedState.Entry e, BooleanProperty disable) { + private Comp entryButton(BrowserHistorySavedState.Entry e, BooleanProperty disable) { var entry = DataStorage.get().getStoreEntryIfPresent(e.getUuid()); var graphic = entry.get().getEffectiveIconFile(); - var view = PrettyImageHelper.ofFixedSize(graphic, 30, 24); + var view = PrettyImageHelper.ofFixedSize(graphic, 22, 16); return new ButtonComp( new SimpleStringProperty(DataStorage.get().getStoreEntryDisplayName(entry.get())), view.createRegion(), @@ -158,7 +159,7 @@ public class BrowserWelcomeComp extends SimpleComp { } }); }) - .minWidth(250) + .minWidth(300) .accessibleText(DataStorage.get().getStoreEntryDisplayName(entry.get())) .disable(disable) .styleClass("entry-button") @@ -166,7 +167,7 @@ public class BrowserWelcomeComp extends SimpleComp { .apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT)); } - private Comp dirButton(BrowserSavedState.Entry e, BooleanProperty disable) { + private Comp dirButton(BrowserHistorySavedState.Entry e, BooleanProperty disable) { return new ButtonComp(new SimpleStringProperty(e.getPath()), null, () -> { ThreadHelper.runAsync(() -> { model.restoreStateAsync(e, disable); @@ -177,7 +178,7 @@ public class BrowserWelcomeComp extends SimpleComp { .styleClass("directory-button") .apply(struc -> struc.get().setMaxWidth(2000)) .styleClass(Styles.RIGHT_PILL) - .grow(true, false) + .hgrow() .apply(struc -> struc.get().setAlignment(Pos.CENTER_LEFT)); } } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserHistoryTabModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserHistoryTabModel.java new file mode 100644 index 000000000..0a585eee1 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserHistoryTabModel.java @@ -0,0 +1,46 @@ +package io.xpipe.app.browser.file; + +import io.xpipe.app.browser.BrowserAbstractSessionModel; +import io.xpipe.app.browser.BrowserFullSessionModel; +import io.xpipe.app.browser.BrowserSessionTab; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.storage.DataColor; + +public final class BrowserHistoryTabModel extends BrowserSessionTab { + + public BrowserHistoryTabModel(BrowserAbstractSessionModel browserModel) { + super(browserModel, " " + AppI18n.get("history") + " "); + } + + @Override + public Comp comp() { + return new BrowserHistoryTabComp((BrowserFullSessionModel) browserModel); + } + + @Override + public boolean canImmediatelyClose() { + return true; + } + + @Override + public void init() throws Exception {} + + @Override + public void close() {} + + @Override + public String getIcon() { + return null; + } + + @Override + public DataColor getColor() { + return null; + } + + @Override + public boolean isCloseable() { + return false; + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/file/LocalFileSystem.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserLocalFileSystem.java similarity index 97% rename from app/src/main/java/io/xpipe/app/browser/file/LocalFileSystem.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserLocalFileSystem.java index 48b74d368..be37b6c40 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/LocalFileSystem.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserLocalFileSystem.java @@ -8,7 +8,7 @@ import io.xpipe.core.store.FileSystem; import java.nio.file.Files; import java.nio.file.Path; -public class LocalFileSystem { +public class BrowserLocalFileSystem { private static FileSystem localFileSystem; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserNavBarComp.java similarity index 89% rename from app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserNavBarComp.java index 06fb8cd32..67fcbd893 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserNavBar.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserNavBarComp.java @@ -1,15 +1,13 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; -import io.xpipe.app.browser.file.BrowserContextMenu; -import io.xpipe.app.browser.fs.OpenFileSystemModel; -import io.xpipe.app.browser.icon.FileIconManager; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.CompStructure; -import io.xpipe.app.fxcomps.SimpleCompStructure; -import io.xpipe.app.fxcomps.augment.ContextMenuAugment; -import io.xpipe.app.fxcomps.impl.PrettyImageHelper; -import io.xpipe.app.fxcomps.impl.TextFieldComp; -import io.xpipe.app.fxcomps.impl.TooltipAugment; +import io.xpipe.app.browser.icon.BrowserIconManager; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.CompStructure; +import io.xpipe.app.comp.SimpleCompStructure; +import io.xpipe.app.comp.augment.ContextMenuAugment; +import io.xpipe.app.comp.base.PrettyImageHelper; +import io.xpipe.app.comp.base.TextFieldComp; +import io.xpipe.app.comp.base.TooltipAugment; import io.xpipe.app.util.BooleanScope; import io.xpipe.app.util.ThreadHelper; @@ -33,58 +31,16 @@ import javafx.scene.shape.Rectangle; import atlantafx.base.theme.Styles; import org.kordamp.ikonli.javafx.FontIcon; -public class BrowserNavBar extends Comp { +public class BrowserNavBarComp extends Comp { @Override public Structure createBase() { - var path = new SimpleStringProperty(model.getCurrentPath().get()); - model.getCurrentPath().subscribe((newValue) -> { - path.set(newValue); - }); - path.addListener((observable, oldValue, newValue) -> { - ThreadHelper.runFailableAsync(() -> { - BooleanScope.executeExclusive(model.getBusy(), () -> { - var changed = model.cdSyncOrRetry(newValue, true); - changed.ifPresent(s -> Platform.runLater(() -> path.set(s))); - }); - }); - }); - - var pathBar = new TextFieldComp(path, true) - .styleClass(Styles.CENTER_PILL) - .styleClass("path-text") - .apply(struc -> { - struc.get().focusedProperty().subscribe(val -> { - struc.get() - .pseudoClassStateChanged( - INVISIBLE, - !val && !model.getInOverview().get()); - - if (val) { - Platform.runLater(() -> { - struc.get().end(); - }); - } - }); - - model.getInOverview().subscribe(val -> { - // Pseudo classes do not apply if set instantly before shown - // If we start a new tab with a directory set, we have to set the pseudo class one pulse later - Platform.runLater(() -> { - struc.get() - .pseudoClassStateChanged( - INVISIBLE, !val && !struc.get().isFocused()); - }); - }); - - struc.get().setPromptText("Overview of " + model.getName()); - }) - .accessibleText("Current path"); + var pathBar = createPathBar(); var graphic = Bindings.createStringBinding( () -> { return model.getCurrentDirectory() != null - ? FileIconManager.getFileIcon(model.getCurrentDirectory()) + ? BrowserIconManager.getFileIcon(model.getCurrentDirectory()) : null; }, model.getCurrentPath()); @@ -154,6 +110,51 @@ public class BrowserNavBar extends Comp { return new Structure(topBox, pathRegion, historyButton); } + private Comp> createPathBar() { + var path = new SimpleStringProperty(model.getCurrentPath().get()); + model.getCurrentPath().subscribe((newValue) -> { + path.set(newValue); + }); + path.addListener((observable, oldValue, newValue) -> { + ThreadHelper.runFailableAsync(() -> { + BooleanScope.executeExclusive(model.getBusy(), () -> { + var changed = model.cdSyncOrRetry(newValue, true); + changed.ifPresent(s -> Platform.runLater(() -> path.set(s))); + }); + }); + }); + var pathBar = + new TextFieldComp(path, true).styleClass(Styles.CENTER_PILL).styleClass("path-text"); + pathBar.apply(struc -> { + struc.get().focusedProperty().subscribe(val -> { + struc.get() + .pseudoClassStateChanged( + INVISIBLE, + !val && !model.getInOverview().get()); + + if (val) { + Platform.runLater(() -> { + struc.get().end(); + }); + } + }); + + model.getInOverview().subscribe(val -> { + // Pseudo classes do not apply if set instantly before shown + // If we start a new tab with a directory set, we have to set the pseudo class one pulse later + Platform.runLater(() -> { + struc.get() + .pseudoClassStateChanged( + INVISIBLE, !val && !struc.get().isFocused()); + }); + }); + + struc.get().setPromptText("Overview of " + model.getName()); + }) + .accessibleText("Current path"); + return pathBar; + } + public record Structure(HBox box, TextField textField, Button historyButton) implements CompStructure { @Override @@ -164,9 +165,9 @@ public class BrowserNavBar extends Comp { private static final PseudoClass INVISIBLE = PseudoClass.getPseudoClass("invisible"); - private final OpenFileSystemModel model; + private final BrowserFileSystemTabModel model; - public BrowserNavBar(OpenFileSystemModel model) { + public BrowserNavBarComp(BrowserFileSystemTabModel model) { this.model = model; } diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserOverviewComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserOverviewComp.java similarity index 87% rename from app/src/main/java/io/xpipe/app/browser/BrowserOverviewComp.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserOverviewComp.java index f0ea684c9..3b9091fa2 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserOverviewComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserOverviewComp.java @@ -1,13 +1,12 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; -import io.xpipe.app.browser.file.BrowserFileOverviewComp; -import io.xpipe.app.browser.fs.OpenFileSystemModel; +import io.xpipe.app.comp.SimpleComp; import io.xpipe.app.comp.base.SimpleTitledPaneComp; +import io.xpipe.app.comp.base.VerticalComp; +import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppI18n; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.impl.VerticalComp; -import io.xpipe.app.fxcomps.util.DerivedObservableList; import io.xpipe.app.issue.ErrorEvent; +import io.xpipe.app.util.DerivedObservableList; import io.xpipe.app.util.ThreadHelper; import io.xpipe.core.process.ShellControl; import io.xpipe.core.store.FileEntry; @@ -24,9 +23,9 @@ import java.util.List; public class BrowserOverviewComp extends SimpleComp { - private final OpenFileSystemModel model; + private final BrowserFileSystemTabModel model; - public BrowserOverviewComp(OpenFileSystemModel model) { + public BrowserOverviewComp(BrowserFileSystemTabModel model) { this.model = model; } @@ -77,6 +76,8 @@ public class BrowserOverviewComp extends SimpleComp { var recentPane = new SimpleTitledPaneComp(AppI18n.observable("recent"), recentOverview); var vbox = new VerticalComp(List.of(recentPane, commonPane, rootsPane)).styleClass("overview"); - return vbox.createRegion(); + var r = vbox.createRegion(); + AppFont.medium(r); + return r; } } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessButtonComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessButtonComp.java index 77b756e1e..d8382b5aa 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessButtonComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessButtonComp.java @@ -1,8 +1,7 @@ package io.xpipe.app.browser.file; -import io.xpipe.app.browser.fs.OpenFileSystemModel; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.impl.IconButtonComp; +import io.xpipe.app.comp.SimpleComp; +import io.xpipe.app.comp.base.IconButtonComp; import io.xpipe.app.util.InputHelper; import javafx.scene.layout.Region; @@ -12,9 +11,9 @@ import java.util.function.Supplier; public class BrowserQuickAccessButtonComp extends SimpleComp { private final Supplier base; - private final OpenFileSystemModel model; + private final BrowserFileSystemTabModel model; - public BrowserQuickAccessButtonComp(Supplier base, OpenFileSystemModel model) { + public BrowserQuickAccessButtonComp(Supplier base, BrowserFileSystemTabModel model) { this.base = base; this.model = model; } diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java index 6f5735534..5d4c028f1 100644 --- a/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserQuickAccessContextMenu.java @@ -1,8 +1,7 @@ package io.xpipe.app.browser.file; -import io.xpipe.app.browser.fs.OpenFileSystemModel; -import io.xpipe.app.browser.icon.FileIconManager; -import io.xpipe.app.fxcomps.impl.PrettyImageHelper; +import io.xpipe.app.browser.icon.BrowserIconManager; +import io.xpipe.app.comp.base.PrettyImageHelper; import io.xpipe.app.util.BooleanAnimationTimer; import io.xpipe.app.util.InputHelper; import io.xpipe.app.util.ThreadHelper; @@ -32,13 +31,13 @@ import java.util.stream.Collectors; public class BrowserQuickAccessContextMenu extends ContextMenu { private final Supplier base; - private final OpenFileSystemModel model; + private final BrowserFileSystemTabModel model; private ContextMenu shownBrowserActionsMenu; private boolean expandBrowserActionMenuKey; private boolean keyBasedNavigation; private boolean closeBrowserActionMenuKey; - public BrowserQuickAccessContextMenu(Supplier base, OpenFileSystemModel model) { + public BrowserQuickAccessContextMenu(Supplier base, BrowserFileSystemTabModel model) { this.base = base; this.model = model; @@ -142,7 +141,8 @@ public class BrowserQuickAccessContextMenu extends ContextMenu { this.menu = new Menu( // Use original name, not the link target browserEntry.getRawFileEntry().getName(), - PrettyImageHelper.ofFixedSize(FileIconManager.getFileIcon(browserEntry.getRawFileEntry()), 24, 24) + PrettyImageHelper.ofFixedSize( + BrowserIconManager.getFileIcon(browserEntry.getRawFileEntry()), 24, 24) .createRegion()); createMenu(); addInputListeners(); diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserStatusBarComp.java similarity index 91% rename from app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserStatusBarComp.java index 43edfb8e3..f61f79963 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserStatusBarComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserStatusBarComp.java @@ -1,16 +1,13 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; -import io.xpipe.app.browser.file.BrowserContextMenu; -import io.xpipe.app.browser.file.BrowserFileListCompEntry; -import io.xpipe.app.browser.fs.OpenFileSystemModel; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.SimpleComp; +import io.xpipe.app.comp.SimpleCompStructure; +import io.xpipe.app.comp.augment.ContextMenuAugment; +import io.xpipe.app.comp.base.HorizontalComp; +import io.xpipe.app.comp.base.LabelComp; import io.xpipe.app.core.AppFont; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.SimpleCompStructure; -import io.xpipe.app.fxcomps.augment.ContextMenuAugment; -import io.xpipe.app.fxcomps.impl.HorizontalComp; -import io.xpipe.app.fxcomps.impl.LabelComp; -import io.xpipe.app.fxcomps.util.BindingsHelper; +import io.xpipe.app.util.BindingsHelper; import io.xpipe.app.util.HumanReadableFormat; import javafx.beans.binding.Bindings; @@ -29,7 +26,7 @@ import java.util.List; @EqualsAndHashCode(callSuper = true) public class BrowserStatusBarComp extends SimpleComp { - OpenFileSystemModel model; + BrowserFileSystemTabModel model; @Override protected Region createSimple() { diff --git a/app/src/main/java/io/xpipe/app/browser/file/BrowserTerminalDockTabModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserTerminalDockTabModel.java new file mode 100644 index 000000000..81bc02ee8 --- /dev/null +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserTerminalDockTabModel.java @@ -0,0 +1,164 @@ +package io.xpipe.app.browser.file; + +import io.xpipe.app.browser.BrowserAbstractSessionModel; +import io.xpipe.app.browser.BrowserFullSessionModel; +import io.xpipe.app.browser.BrowserSessionTab; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.core.AppI18n; +import io.xpipe.app.core.AppLayoutModel; +import io.xpipe.app.prefs.AppPrefs; +import io.xpipe.app.storage.DataColor; +import io.xpipe.app.terminal.TerminalDockComp; +import io.xpipe.app.terminal.TerminalDockModel; +import io.xpipe.app.terminal.TerminalView; + +import io.xpipe.app.terminal.WindowsTerminalType; +import io.xpipe.app.util.ThreadHelper; +import javafx.application.Platform; +import javafx.beans.binding.Bindings; +import javafx.beans.value.ObservableBooleanValue; +import javafx.collections.ObservableList; + +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; + +public final class BrowserTerminalDockTabModel extends BrowserSessionTab { + + private final BrowserSessionTab origin; + private final ObservableList terminalRequests; + private final TerminalDockModel dockModel = new TerminalDockModel(); + private TerminalView.Listener listener; + private ObservableBooleanValue viewActive; + + public BrowserTerminalDockTabModel( + BrowserAbstractSessionModel browserModel, + BrowserSessionTab origin, + ObservableList terminalRequests) { + super(browserModel, AppI18n.get("terminal")); + this.origin = origin; + this.terminalRequests = terminalRequests; + } + + @Override + public Comp comp() { + return new TerminalDockComp(dockModel); + } + + @Override + public boolean canImmediatelyClose() { + return true; + } + + @Override + public void init() throws Exception { + var hasOpened = new AtomicBoolean(); + listener = new TerminalView.Listener() { + @Override + public void onSessionOpened(TerminalView.ShellSession session) { + if (!terminalRequests.contains(session.getRequest())) { + return; + } + + hasOpened.set(true); + var sessions = TerminalView.get().getSessions(); + var tv = sessions.stream() + .filter(s -> terminalRequests.contains(s.getRequest()) + && s.getTerminal().isRunning()) + .map(s -> s.getTerminal().controllable()) + .flatMap(Optional::stream) + .toList(); + for (int i = 0; i < tv.size() - 1; i++) { + dockModel.closeTerminal(tv.get(i)); + } + + // Closing and opening windows at the same time might be problematic for some bad implementations + if (tv.size() > 1) { + ThreadHelper.sleep(250); + } + + var toTrack = tv.getLast(); + dockModel.trackTerminal(toTrack); + } + + @Override + public void onSessionClosed(TerminalView.ShellSession session) { + if (!terminalRequests.contains(session.getRequest())) { + return; + } + + // Ugly fix for Windows Terminal instances not closing properly if multiple windows exist + if (AppPrefs.get().terminalType().getValue() instanceof WindowsTerminalType) { + var sessions = TerminalView.get().getSessions(); + var others = sessions.stream().filter(shellSession -> shellSession.getTerminal().equals(session.getTerminal())).count(); + if (others == 0) { + session.getTerminal().controllable().ifPresent(controllableTerminalSession -> { + controllableTerminalSession.close(); + }); + } + } + } + + @Override + public void onTerminalClosed(TerminalView.TerminalSession instance) { + refreshShowingState(); + } + }; + TerminalView.get().addListener(listener); + + // If the terminal launch fails + ThreadHelper.runAsync(() -> { + ThreadHelper.sleep(5000); + if (!hasOpened.get()) { + refreshShowingState(); + } + }); + + viewActive = Bindings.createBooleanBinding( + () -> { + return this.browserModel.getSelectedEntry().getValue() == origin + && AppLayoutModel.get() + .getEntries() + .indexOf(AppLayoutModel.get() + .getSelected() + .getValue()) + == 1; + }, + this.browserModel.getSelectedEntry(), + AppLayoutModel.get().getSelected()); + viewActive.subscribe(aBoolean -> { + Platform.runLater(() -> { + dockModel.toggleView(aBoolean); + }); + }); + } + + private void refreshShowingState() { + var sessions = TerminalView.get().getSessions(); + var remaining = sessions.stream() + .filter(s -> terminalRequests.contains(s.getRequest()) + && s.getTerminal().isRunning()) + .toList(); + if (remaining.isEmpty()) { + ((BrowserFullSessionModel) browserModel).unsplitTab(BrowserTerminalDockTabModel.this); + } + } + + @Override + public void close() { + if (listener != null) { + TerminalView.get().removeListener(listener); + } + dockModel.onClose(); + } + + @Override + public String getIcon() { + return null; + } + + @Override + public DataColor getColor() { + return null; + } +} diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferComp.java similarity index 51% rename from app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserTransferComp.java index 5c3334684..88b192ff2 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTransferComp.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferComp.java @@ -1,13 +1,11 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; -import io.xpipe.app.browser.fs.OpenFileSystemModel; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.SimpleComp; +import io.xpipe.app.comp.base.*; import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppI18n; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.fxcomps.augment.DragOverPseudoClassAugment; -import io.xpipe.app.fxcomps.impl.*; -import io.xpipe.app.fxcomps.util.DerivedObservableList; +import io.xpipe.app.util.DerivedObservableList; import io.xpipe.app.util.ThreadHelper; import javafx.beans.binding.Bindings; @@ -17,6 +15,7 @@ import javafx.css.PseudoClass; import javafx.geometry.Insets; import javafx.scene.image.Image; import javafx.scene.input.ClipboardContent; +import javafx.scene.input.DragEvent; import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; import javafx.scene.layout.Region; @@ -43,13 +42,16 @@ public class BrowserTransferComp extends SimpleComp { .apply(struc -> struc.get().setGraphic(new FontIcon("mdi2d-download-outline"))) .apply(struc -> struc.get().setWrapText(true)) .visible(model.getEmpty()); - var backgroundStack = - new StackComp(List.of(background)).grow(true, true).styleClass("download-background"); + var backgroundStack = new StackComp(List.of(background)) + .grow(true, true) + .styleClass("color-box") + .styleClass("gray") + .styleClass("download-background"); var binding = new DerivedObservableList<>(model.getItems(), true) .mapped(item -> item.getBrowserEntry()) .getList(); - var list = new BrowserSelectionListComp(binding, entry -> { + var list = new BrowserFileSelectionListComp(binding, entry -> { var sourceItem = model.getCurrentItems().stream() .filter(item -> item.getBrowserEntry() == entry) .findAny(); @@ -102,84 +104,84 @@ public class BrowserTransferComp extends SimpleComp { .padding(new Insets(10, 10, 5, 10)) .apply(struc -> struc.get().setMinHeight(200)) .apply(struc -> struc.get().setMaxHeight(200)); - var stack = new StackComp(List.of(backgroundStack, listBox)) - .apply(DragOverPseudoClassAugment.create()) - .apply(struc -> { - struc.get().setOnDragOver(event -> { - // Accept drops from inside the app window - if (event.getGestureSource() != null && event.getGestureSource() != struc.get()) { - event.acceptTransferModes(TransferMode.ANY); - event.consume(); - } - }); - struc.get().setOnDragDropped(event -> { - // Accept drops from inside the app window - if (event.getGestureSource() != null) { - var drag = BrowserClipboard.retrieveDrag(event.getDragboard()); - if (drag == null) { - return; + var stack = new StackComp(List.of(backgroundStack, listBox)).apply(struc -> { + struc.get().addEventFilter(DragEvent.DRAG_ENTERED, event -> { + struc.get().pseudoClassStateChanged(PseudoClass.getPseudoClass("drag-over"), true); + }); + struc.get().addEventFilter(DragEvent.DRAG_EXITED, event -> struc.get() + .pseudoClassStateChanged(PseudoClass.getPseudoClass("drag-over"), false)); + struc.get().setOnDragOver(event -> { + // Accept drops from inside the app window + if (event.getGestureSource() != null && event.getGestureSource() != struc.get()) { + event.acceptTransferModes(TransferMode.ANY); + event.consume(); + } + }); + struc.get().setOnDragDropped(event -> { + // Accept drops from inside the app window + if (event.getGestureSource() != null) { + var drag = BrowserClipboard.retrieveDrag(event.getDragboard()); + if (drag == null) { + return; + } + + if (!(model.getBrowserSessionModel().getSelectedEntry().getValue() + instanceof BrowserFileSystemTabModel fileSystemModel)) { + return; + } + + var files = drag.getEntries(); + model.drop(fileSystemModel, files); + event.setDropCompleted(true); + event.consume(); + } + }); + struc.get().setOnDragDetected(event -> { + var items = model.getCurrentItems(); + var selected = + items.stream().map(item -> item.getBrowserEntry()).toList(); + var files = items.stream() + .filter(item -> item.downloadFinished().get()) + .map(item -> { + try { + var file = item.getLocalFile(); + if (!Files.exists(file)) { + return Optional.empty(); + } + + return Optional.of(file.toRealPath().toFile()); + } catch (IOException e) { + throw new RuntimeException(e); } + }) + .flatMap(Optional::stream) + .toList(); + if (files.isEmpty()) { + return; + } - if (!(model.getBrowserSessionModel() - .getSelectedEntry() - .getValue() - instanceof OpenFileSystemModel fileSystemModel)) { - return; - } + var cc = new ClipboardContent(); + cc.putFiles(files); + Dragboard db = struc.get().startDragAndDrop(TransferMode.COPY); + db.setContent(cc); - var files = drag.getEntries(); - model.drop(fileSystemModel, files); - event.setDropCompleted(true); - event.consume(); - } - }); - struc.get().setOnDragDetected(event -> { - var items = model.getCurrentItems(); - var selected = items.stream() - .map(item -> item.getBrowserEntry()) - .toList(); - var files = items.stream() - .filter(item -> item.downloadFinished().get()) - .map(item -> { - try { - var file = item.getLocalFile(); - if (!Files.exists(file)) { - return Optional.empty(); - } + Image image = BrowserFileSelectionListComp.snapshot(FXCollections.observableList(selected)); + db.setDragView(image, -20, 15); - return Optional.of(file.toRealPath().toFile()); - } catch (IOException e) { - throw new RuntimeException(e); - } - }) - .flatMap(Optional::stream) - .toList(); - if (files.isEmpty()) { - return; - } + event.setDragDetect(true); + event.consume(); + }); + struc.get().setOnDragDone(event -> { + if (!event.isAccepted()) { + return; + } - var cc = new ClipboardContent(); - cc.putFiles(files); - Dragboard db = struc.get().startDragAndDrop(TransferMode.COPY); - db.setContent(cc); - - Image image = BrowserSelectionListComp.snapshot(FXCollections.observableList(selected)); - db.setDragView(image, -20, 15); - - event.setDragDetect(true); - event.consume(); - }); - struc.get().setOnDragDone(event -> { - if (!event.isAccepted()) { - return; - } - - // The files might not have been transferred yet - // We can't listen to this, so just don't delete them - model.clear(false); - event.consume(); - }); - }); + // The files might not have been transferred yet + // We can't listen to this, so just don't delete them + model.clear(false); + event.consume(); + }); + }); stack.apply(struc -> { model.getBrowserSessionModel().getDraggingFiles().addListener((observable, oldValue, newValue) -> { diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferModel.java similarity index 84% rename from app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserTransferModel.java index 7e568ba7f..1e5cf1f2f 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTransferModel.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferModel.java @@ -1,12 +1,8 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; -import io.xpipe.app.browser.file.BrowserEntry; -import io.xpipe.app.browser.file.BrowserFileTransferMode; -import io.xpipe.app.browser.file.BrowserFileTransferOperation; -import io.xpipe.app.browser.file.LocalFileSystem; -import io.xpipe.app.browser.fs.OpenFileSystemModel; -import io.xpipe.app.browser.session.BrowserSessionModel; +import io.xpipe.app.browser.BrowserFullSessionModel; import io.xpipe.app.issue.ErrorEvent; +import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.util.DesktopHelper; import io.xpipe.app.util.ShellTemp; import io.xpipe.app.util.ThreadHelper; @@ -23,6 +19,7 @@ import org.apache.commons.io.FileUtils; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.ArrayList; @@ -34,11 +31,11 @@ public class BrowserTransferModel { private static final Path TEMP = ShellTemp.getLocalTempDataDirectory("download"); - BrowserSessionModel browserSessionModel; + BrowserFullSessionModel browserSessionModel; ObservableList items = FXCollections.observableArrayList(); ObservableBooleanValue empty = Bindings.createBooleanBinding(() -> items.isEmpty(), items); - public BrowserTransferModel(BrowserSessionModel browserSessionModel) { + public BrowserTransferModel(BrowserFullSessionModel browserSessionModel) { this.browserSessionModel = browserSessionModel; var thread = ThreadHelper.createPlatformThread("file downloader", true, () -> { while (true) { @@ -96,7 +93,7 @@ public class BrowserTransferModel { } } - public void drop(OpenFileSystemModel model, List entries) { + public void drop(BrowserFileSystemTabModel model, List entries) { synchronized (items) { entries.forEach(entry -> { var name = entry.getFileName(); @@ -130,7 +127,7 @@ public class BrowserTransferModel { try { var op = new BrowserFileTransferOperation( - LocalFileSystem.getLocalFileEntry(TEMP), + BrowserLocalFileSystem.getLocalFileEntry(TEMP), List.of(item.getBrowserEntry().getRawFileEntry()), BrowserFileTransferMode.COPY, false, @@ -167,7 +164,7 @@ public class BrowserTransferModel { } var files = toMove.stream().map(item -> item.getLocalFile()).toList(); - var downloads = DesktopHelper.getDownloadsDirectory(); + var downloads = getDownloadsTargetDirectory(); Files.createDirectories(downloads); for (Path file : files) { if (!Files.exists(file)) { @@ -188,15 +185,33 @@ public class BrowserTransferModel { DesktopHelper.browseFileInDirectory(downloads.resolve(files.getFirst().getFileName())); } + private Path getDownloadsTargetDirectory() throws Exception { + var def = DesktopHelper.getDownloadsDirectory(); + var custom = AppPrefs.get().downloadsDirectory().getValue(); + if (custom == null || custom.isBlank()) { + return def; + } + + try { + var path = Path.of(custom); + if (Files.isDirectory(path)) { + return path; + } + } catch (InvalidPathException ignored) { + } + return def; + } + @Value public static class Item { - OpenFileSystemModel openFileSystemModel; + BrowserFileSystemTabModel openFileSystemModel; String name; BrowserEntry browserEntry; Path localFile; Property progress; - public Item(OpenFileSystemModel openFileSystemModel, String name, BrowserEntry browserEntry, Path localFile) { + public Item( + BrowserFileSystemTabModel openFileSystemModel, String name, BrowserEntry browserEntry, Path localFile) { this.openFileSystemModel = openFileSystemModel; this.name = name; this.browserEntry = browserEntry; diff --git a/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java b/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferProgress.java similarity index 96% rename from app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java rename to app/src/main/java/io/xpipe/app/browser/file/BrowserTransferProgress.java index 7486afdb6..99b7e291d 100644 --- a/app/src/main/java/io/xpipe/app/browser/BrowserTransferProgress.java +++ b/app/src/main/java/io/xpipe/app/browser/file/BrowserTransferProgress.java @@ -1,4 +1,4 @@ -package io.xpipe.app.browser; +package io.xpipe.app.browser.file; import lombok.Value; diff --git a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconDirectoryType.java b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconDirectoryType.java index b417c92d1..9a89cd3a2 100644 --- a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconDirectoryType.java +++ b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconDirectoryType.java @@ -63,7 +63,7 @@ public abstract class BrowserIconDirectoryType { var closedIcon = "browser/" + split[2].trim(); var lightClosedIcon = split.length > 4 ? "browser/" + split[4].trim() : closedIcon; - ALL.add(new Simple(id, new IconVariant(lightClosedIcon, closedIcon), filter)); + ALL.add(new Simple(id, new BrowserIconVariant(lightClosedIcon, closedIcon), filter)); } } }); @@ -84,10 +84,10 @@ public abstract class BrowserIconDirectoryType { @Getter private final String id; - private final IconVariant closed; + private final BrowserIconVariant closed; private final Set names; - public Simple(String id, IconVariant closed, Set names) { + public Simple(String id, BrowserIconVariant closed, Set names) { this.id = id; this.closed = closed; this.names = names; diff --git a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconFileType.java b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconFileType.java index 4293aa2b6..0c8306405 100644 --- a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconFileType.java +++ b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconFileType.java @@ -69,11 +69,11 @@ public abstract class BrowserIconFileType { public static class Simple extends BrowserIconFileType { private final String id; - private final IconVariant icon; + private final BrowserIconVariant icon; private final Set endings; public Simple(String id, String lightIcon, String darkIcon, Set endings) { - this.icon = new IconVariant(lightIcon, darkIcon); + this.icon = new BrowserIconVariant(lightIcon, darkIcon); this.id = id; this.endings = endings; } diff --git a/app/src/main/java/io/xpipe/app/browser/icon/FileIconManager.java b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconManager.java similarity index 96% rename from app/src/main/java/io/xpipe/app/browser/icon/FileIconManager.java rename to app/src/main/java/io/xpipe/app/browser/icon/BrowserIconManager.java index 1b28618df..6e7a4ebb9 100644 --- a/app/src/main/java/io/xpipe/app/browser/icon/FileIconManager.java +++ b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconManager.java @@ -3,7 +3,7 @@ package io.xpipe.app.browser.icon; import io.xpipe.core.store.FileEntry; import io.xpipe.core.store.FileKind; -public class FileIconManager { +public class BrowserIconManager { private static boolean loaded; diff --git a/app/src/main/java/io/xpipe/app/browser/icon/IconVariant.java b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconVariant.java similarity index 77% rename from app/src/main/java/io/xpipe/app/browser/icon/IconVariant.java rename to app/src/main/java/io/xpipe/app/browser/icon/BrowserIconVariant.java index 96eef3fa2..820ed2f1e 100644 --- a/app/src/main/java/io/xpipe/app/browser/icon/IconVariant.java +++ b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIconVariant.java @@ -2,16 +2,16 @@ package io.xpipe.app.browser.icon; import io.xpipe.app.prefs.AppPrefs; -public class IconVariant { +public class BrowserIconVariant { private final String lightIcon; private final String darkIcon; - public IconVariant(String icon) { + public BrowserIconVariant(String icon) { this(icon, icon); } - public IconVariant(String lightIcon, String darkIcon) { + public BrowserIconVariant(String lightIcon, String darkIcon) { this.lightIcon = lightIcon; this.darkIcon = darkIcon; } diff --git a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIcons.java b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIcons.java index 18fc39bde..1d771981d 100644 --- a/app/src/main/java/io/xpipe/app/browser/icon/BrowserIcons.java +++ b/app/src/main/java/io/xpipe/app/browser/icon/BrowserIcons.java @@ -1,7 +1,7 @@ package io.xpipe.app.browser.icon; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.impl.PrettyImageHelper; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.base.PrettyImageHelper; import io.xpipe.core.store.FileEntry; public class BrowserIcons { @@ -19,6 +19,6 @@ public class BrowserIcons { } public static Comp createIcon(FileEntry entry) { - return PrettyImageHelper.ofFixedSizeSquare(FileIconManager.getFileIcon(entry), 24); + return PrettyImageHelper.ofFixedSizeSquare(BrowserIconManager.getFileIcon(entry), 24); } } diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java deleted file mode 100644 index 3fdc6fa43..000000000 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionModel.java +++ /dev/null @@ -1,106 +0,0 @@ -package io.xpipe.app.browser.session; - -import io.xpipe.app.browser.BrowserSavedState; -import io.xpipe.app.browser.BrowserSavedStateImpl; -import io.xpipe.app.browser.BrowserTransferModel; -import io.xpipe.app.browser.fs.OpenFileSystemModel; -import io.xpipe.app.storage.DataStorage; -import io.xpipe.app.storage.DataStoreEntryRef; -import io.xpipe.app.util.BooleanScope; -import io.xpipe.app.util.ThreadHelper; -import io.xpipe.core.store.FileNames; -import io.xpipe.core.store.FileSystemStore; -import io.xpipe.core.util.FailableFunction; - -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.Property; -import javafx.beans.property.SimpleBooleanProperty; - -import lombok.Getter; - -import java.util.ArrayList; - -@Getter -public class BrowserSessionModel extends BrowserAbstractSessionModel> { - - public static final BrowserSessionModel DEFAULT = new BrowserSessionModel(); - - private final BrowserTransferModel localTransfersStage = new BrowserTransferModel(this); - private final Property draggingFiles = new SimpleBooleanProperty(); - - public void restoreState(BrowserSavedState state) { - ThreadHelper.runAsync(() -> { - var l = new ArrayList<>(state.getEntries()); - l.forEach(e -> { - restoreStateAsync(e, null); - // Don't try to run everything in parallel as that can be taxing - ThreadHelper.sleep(1000); - }); - }); - } - - public void restoreStateAsync(BrowserSavedState.Entry e, BooleanProperty busy) { - var storageEntry = DataStorage.get().getStoreEntryIfPresent(e.getUuid()); - storageEntry.ifPresent(entry -> { - openFileSystemAsync(entry.ref(), model -> e.getPath(), busy); - }); - } - - public void reset() { - synchronized (BrowserSessionModel.this) { - for (var o : new ArrayList<>(sessionEntries)) { - // Don't close busy connections gracefully - // as we otherwise might lock up - if (!o.canImmediatelyClose()) { - continue; - } - - // Prevent blocking of shutdown - closeAsync(o); - } - BrowserSavedStateImpl.get().save(); - } - - // Delete all files - localTransfersStage.clear(true); - } - - public void openFileSystemAsync( - DataStoreEntryRef store, - FailableFunction path, - BooleanProperty externalBusy) { - if (store == null) { - return; - } - - ThreadHelper.runFailableAsync(() -> { - openFileSystemSync(store, path, externalBusy); - }); - } - - public OpenFileSystemModel openFileSystemSync( - DataStoreEntryRef store, - FailableFunction path, - BooleanProperty externalBusy) - throws Exception { - OpenFileSystemModel model; - try (var b = new BooleanScope(externalBusy != null ? externalBusy : new SimpleBooleanProperty()).start()) { - try (var sessionBusy = new BooleanScope(busy).exclusive().start()) { - model = new OpenFileSystemModel(this, store, OpenFileSystemModel.SelectionMode.ALL); - model.init(); - // Prevent multiple calls from interfering with each other - synchronized (BrowserSessionModel.this) { - sessionEntries.add(model); - // The tab pane doesn't automatically select new tabs - selectedEntry.setValue(model); - } - } - } - if (path != null) { - model.initWithGivenDirectory(FileNames.toDirectory(path.apply(model))); - } else { - model.initWithDefaultDirectory(); - } - return model; - } -} diff --git a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTab.java b/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTab.java deleted file mode 100644 index 09bda38fa..000000000 --- a/app/src/main/java/io/xpipe/app/browser/session/BrowserSessionTab.java +++ /dev/null @@ -1,36 +0,0 @@ -package io.xpipe.app.browser.session; - -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.storage.DataStorage; -import io.xpipe.app.storage.DataStoreEntryRef; -import io.xpipe.core.store.DataStore; - -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; - -import lombok.Getter; - -@Getter -public abstract class BrowserSessionTab { - - protected final DataStoreEntryRef entry; - protected final BooleanProperty busy = new SimpleBooleanProperty(); - protected final BrowserAbstractSessionModel browserModel; - protected final String name; - protected final String tooltip; - - public BrowserSessionTab(BrowserAbstractSessionModel browserModel, DataStoreEntryRef entry) { - this.browserModel = browserModel; - this.entry = entry; - this.name = DataStorage.get().getStoreEntryDisplayName(entry.get()); - this.tooltip = DataStorage.get().getStorePath(entry.getEntry()).toString(); - } - - public abstract Comp comp(); - - public abstract boolean canImmediatelyClose(); - - public abstract void init() throws Exception; - - public abstract void close(); -} diff --git a/app/src/main/java/io/xpipe/app/fxcomps/Comp.java b/app/src/main/java/io/xpipe/app/comp/Comp.java similarity index 94% rename from app/src/main/java/io/xpipe/app/fxcomps/Comp.java rename to app/src/main/java/io/xpipe/app/comp/Comp.java index 090a52d68..6d02a00ce 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/Comp.java +++ b/app/src/main/java/io/xpipe/app/comp/Comp.java @@ -1,11 +1,11 @@ -package io.xpipe.app.fxcomps; +package io.xpipe.app.comp; +import io.xpipe.app.comp.augment.Augment; +import io.xpipe.app.comp.augment.GrowAugment; +import io.xpipe.app.comp.base.TooltipAugment; import io.xpipe.app.core.AppI18n; -import io.xpipe.app.fxcomps.augment.Augment; -import io.xpipe.app.fxcomps.augment.GrowAugment; -import io.xpipe.app.fxcomps.impl.TooltipAugment; -import io.xpipe.app.fxcomps.util.BindingsHelper; -import io.xpipe.app.fxcomps.util.PlatformThread; +import io.xpipe.app.util.BindingsHelper; +import io.xpipe.app.util.PlatformThread; import javafx.application.Platform; import javafx.beans.value.ObservableValue; @@ -97,8 +97,8 @@ public abstract class Comp> { return apply(struc -> struc.get().setMinWidth(width)); } - public Comp minHeight(double width) { - return apply(struc -> struc.get().setMinHeight(width)); + public Comp minHeight(double height) { + return apply(struc -> struc.get().setMinHeight(height)); } public Comp maxWidth(int width) { diff --git a/app/src/main/java/io/xpipe/app/fxcomps/CompStructure.java b/app/src/main/java/io/xpipe/app/comp/CompStructure.java similarity index 77% rename from app/src/main/java/io/xpipe/app/fxcomps/CompStructure.java rename to app/src/main/java/io/xpipe/app/comp/CompStructure.java index 0b625f479..bd4b29feb 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/CompStructure.java +++ b/app/src/main/java/io/xpipe/app/comp/CompStructure.java @@ -1,4 +1,4 @@ -package io.xpipe.app.fxcomps; +package io.xpipe.app.comp; import javafx.scene.layout.Region; diff --git a/app/src/main/java/io/xpipe/app/comp/DeveloperTabComp.java b/app/src/main/java/io/xpipe/app/comp/DeveloperTabComp.java deleted file mode 100644 index a6d4d12e5..000000000 --- a/app/src/main/java/io/xpipe/app/comp/DeveloperTabComp.java +++ /dev/null @@ -1,59 +0,0 @@ -package io.xpipe.app.comp; - -import io.xpipe.app.comp.base.ButtonComp; -import io.xpipe.app.core.AppI18n; -import io.xpipe.app.core.mode.OperationMode; -import io.xpipe.app.fxcomps.SimpleComp; -import io.xpipe.app.issue.ErrorEvent; - -import javafx.scene.layout.HBox; -import javafx.scene.layout.Region; - -import java.nio.file.Path; - -public class DeveloperTabComp extends SimpleComp { - - @Override - protected Region createSimple() { - var button = new ButtonComp(AppI18n.observable("Throw exception"), null, () -> { - throw new IllegalStateException(); - }); - - var button2 = new ButtonComp(AppI18n.observable("Throw exception with file"), null, () -> { - try { - throw new IllegalStateException(); - } catch (Exception ex) { - ErrorEvent.fromThrowable(ex) - .attachment(Path.of("extensions.txt")) - .build() - .handle(); - } - }); - - var button3 = new ButtonComp(AppI18n.observable("Exit"), null, () -> { - System.exit(0); - }); - - var button6 = new ButtonComp(AppI18n.observable("Restart"), null, () -> { - OperationMode.restart(); - }); - - var button4 = new ButtonComp(AppI18n.observable("Throw terminal exception"), null, () -> { - try { - throw new IllegalStateException(); - } catch (Exception ex) { - ErrorEvent.fromThrowable(ex).terminal(true).build().handle(); - } - }); - - var button5 = new ButtonComp(AppI18n.observable("Operation mode null"), null, OperationMode::close); - - return new HBox( - button.createRegion(), - button2.createRegion(), - button3.createRegion(), - button4.createRegion(), - button5.createRegion(), - button6.createRegion()); - } -} diff --git a/app/src/main/java/io/xpipe/app/fxcomps/README.md b/app/src/main/java/io/xpipe/app/comp/README.md similarity index 100% rename from app/src/main/java/io/xpipe/app/fxcomps/README.md rename to app/src/main/java/io/xpipe/app/comp/README.md diff --git a/app/src/main/java/io/xpipe/app/fxcomps/SimpleComp.java b/app/src/main/java/io/xpipe/app/comp/SimpleComp.java similarity index 90% rename from app/src/main/java/io/xpipe/app/fxcomps/SimpleComp.java rename to app/src/main/java/io/xpipe/app/comp/SimpleComp.java index 9a3b41371..63eb8a85e 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/SimpleComp.java +++ b/app/src/main/java/io/xpipe/app/comp/SimpleComp.java @@ -1,4 +1,4 @@ -package io.xpipe.app.fxcomps; +package io.xpipe.app.comp; import javafx.scene.layout.Region; diff --git a/app/src/main/java/io/xpipe/app/fxcomps/SimpleCompStructure.java b/app/src/main/java/io/xpipe/app/comp/SimpleCompStructure.java similarity index 90% rename from app/src/main/java/io/xpipe/app/fxcomps/SimpleCompStructure.java rename to app/src/main/java/io/xpipe/app/comp/SimpleCompStructure.java index 460d6625a..2315e5588 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/SimpleCompStructure.java +++ b/app/src/main/java/io/xpipe/app/comp/SimpleCompStructure.java @@ -1,4 +1,4 @@ -package io.xpipe.app.fxcomps; +package io.xpipe.app.comp; import javafx.scene.layout.Region; diff --git a/app/src/main/java/io/xpipe/app/fxcomps/augment/Augment.java b/app/src/main/java/io/xpipe/app/comp/augment/Augment.java similarity index 68% rename from app/src/main/java/io/xpipe/app/fxcomps/augment/Augment.java rename to app/src/main/java/io/xpipe/app/comp/augment/Augment.java index 82e39b0a8..93426827a 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/augment/Augment.java +++ b/app/src/main/java/io/xpipe/app/comp/augment/Augment.java @@ -1,7 +1,7 @@ -package io.xpipe.app.fxcomps.augment; +package io.xpipe.app.comp.augment; -import io.xpipe.app.fxcomps.CompStructure; -import io.xpipe.app.fxcomps.SimpleCompStructure; +import io.xpipe.app.comp.CompStructure; +import io.xpipe.app.comp.SimpleCompStructure; import javafx.scene.Node; import javafx.scene.layout.Region; diff --git a/app/src/main/java/io/xpipe/app/fxcomps/augment/ContextMenuAugment.java b/app/src/main/java/io/xpipe/app/comp/augment/ContextMenuAugment.java similarity index 97% rename from app/src/main/java/io/xpipe/app/fxcomps/augment/ContextMenuAugment.java rename to app/src/main/java/io/xpipe/app/comp/augment/ContextMenuAugment.java index 87b86d4e2..c2e0f3bce 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/augment/ContextMenuAugment.java +++ b/app/src/main/java/io/xpipe/app/comp/augment/ContextMenuAugment.java @@ -1,6 +1,6 @@ -package io.xpipe.app.fxcomps.augment; +package io.xpipe.app.comp.augment; -import io.xpipe.app.fxcomps.CompStructure; +import io.xpipe.app.comp.CompStructure; import javafx.event.ActionEvent; import javafx.geometry.Side; diff --git a/app/src/main/java/io/xpipe/app/fxcomps/augment/GrowAugment.java b/app/src/main/java/io/xpipe/app/comp/augment/GrowAugment.java similarity index 97% rename from app/src/main/java/io/xpipe/app/fxcomps/augment/GrowAugment.java rename to app/src/main/java/io/xpipe/app/comp/augment/GrowAugment.java index 7dbac0fa5..cafdb5349 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/augment/GrowAugment.java +++ b/app/src/main/java/io/xpipe/app/comp/augment/GrowAugment.java @@ -1,6 +1,6 @@ -package io.xpipe.app.fxcomps.augment; +package io.xpipe.app.comp.augment; -import io.xpipe.app.fxcomps.CompStructure; +import io.xpipe.app.comp.CompStructure; import javafx.beans.binding.Bindings; import javafx.scene.Node; diff --git a/app/src/main/java/io/xpipe/app/fxcomps/impl/AnchorComp.java b/app/src/main/java/io/xpipe/app/comp/base/AnchorComp.java similarity index 77% rename from app/src/main/java/io/xpipe/app/fxcomps/impl/AnchorComp.java rename to app/src/main/java/io/xpipe/app/comp/base/AnchorComp.java index 393d30935..7959d47ec 100644 --- a/app/src/main/java/io/xpipe/app/fxcomps/impl/AnchorComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/AnchorComp.java @@ -1,8 +1,8 @@ -package io.xpipe.app.fxcomps.impl; +package io.xpipe.app.comp.base; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.CompStructure; -import io.xpipe.app.fxcomps.SimpleCompStructure; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.CompStructure; +import io.xpipe.app.comp.SimpleCompStructure; import javafx.scene.layout.AnchorPane; diff --git a/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java b/app/src/main/java/io/xpipe/app/comp/base/AppLayoutComp.java similarity index 89% rename from app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java rename to app/src/main/java/io/xpipe/app/comp/base/AppLayoutComp.java index 650948036..6a350f010 100644 --- a/app/src/main/java/io/xpipe/app/comp/AppLayoutComp.java +++ b/app/src/main/java/io/xpipe/app/comp/base/AppLayoutComp.java @@ -1,13 +1,11 @@ -package io.xpipe.app.comp; +package io.xpipe.app.comp.base; -import io.xpipe.app.comp.base.MultiContentComp; -import io.xpipe.app.comp.base.SideMenuBarComp; +import io.xpipe.app.comp.Comp; +import io.xpipe.app.comp.CompStructure; +import io.xpipe.app.comp.SimpleCompStructure; import io.xpipe.app.comp.store.StoreViewState; import io.xpipe.app.core.AppFont; import io.xpipe.app.core.AppLayoutModel; -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.CompStructure; -import io.xpipe.app.fxcomps.SimpleCompStructure; import io.xpipe.app.prefs.AppPrefs; import io.xpipe.app.storage.DataStorage; @@ -54,7 +52,7 @@ public class AppLayoutComp extends Comp> { DataStorage.get().saveAsync(); } - if (o != null && o.equals(model.getEntries().get(1))) { + if (o != null && o.equals(model.getEntries().get(0))) { StoreViewState.get().updateDisplay(); } }); diff --git a/app/src/main/java/io/xpipe/app/comp/base/BackgroundImageComp.java b/app/src/main/java/io/xpipe/app/comp/base/BackgroundImageComp.java deleted file mode 100644 index 571a4f376..000000000 --- a/app/src/main/java/io/xpipe/app/comp/base/BackgroundImageComp.java +++ /dev/null @@ -1,62 +0,0 @@ -package io.xpipe.app.comp.base; - -import io.xpipe.app.fxcomps.Comp; -import io.xpipe.app.fxcomps.CompStructure; -import io.xpipe.app.fxcomps.SimpleCompStructure; - -import javafx.beans.value.ChangeListener; -import javafx.geometry.Rectangle2D; -import javafx.scene.image.Image; -import javafx.scene.image.ImageView; -import javafx.scene.layout.Pane; - -public class BackgroundImageComp extends Comp> { - - private final Image image; - - public BackgroundImageComp(Image image) { - this.image = image; - } - - @Override - public CompStructure createBase() { - ImageView v = new ImageView(image); - Pane pane = new Pane(v); - v.fitWidthProperty().bind(pane.widthProperty()); - v.fitHeightProperty().bind(pane.heightProperty()); - if (image == null) { - return new SimpleCompStructure<>(pane); - } - - double imageAspect = image.getWidth() / image.getHeight(); - ChangeListener cl = (c, o, n) -> { - double paneAspect = pane.getWidth() / pane.getHeight(); - - double relViewportWidth; - double relViewportHeight; - - // Pane width too big for image - if (paneAspect > imageAspect) { - relViewportWidth = 1; - double newImageHeight = pane.getWidth() / imageAspect; - relViewportHeight = Math.min(1, pane.getHeight() / newImageHeight); - } - - // Height too big - else { - relViewportHeight = 1; - double newImageWidth = pane.getHeight() * imageAspect; - relViewportWidth = Math.min(1, pane.getWidth() / newImageWidth); - } - - v.setViewport(new Rectangle2D( - ((1 - relViewportWidth) / 2.0) * image.getWidth(), - ((1 - relViewportHeight) / 2.0) * image.getHeight(), - image.getWidth() * relViewportWidth, - image.getHeight() * relViewportHeight)); - }; - pane.widthProperty().addListener(cl); - pane.heightProperty().addListener(cl); - return new SimpleCompStructure<>(pane); - } -} diff --git a/app/src/main/java/io/xpipe/app/comp/base/BigIconButton.java b/app/src/main/java/io/xpipe/app/comp/base/BigIconButton.java deleted file mode 100644 index ded5b634b..000000000 --- a/app/src/main/java/io/xpipe/app/comp/base/BigIconButton.java +++ /dev/null @@ -1,66 +0,0 @@ -package io.xpipe.app.comp.base; - -import io.xpipe.app.fxcomps.CompStructure; - -import javafx.beans.value.ObservableValue; -import javafx.geometry.Pos; -import javafx.scene.Node; -import javafx.scene.control.Button; -import javafx.scene.control.Label; -import javafx.scene.layout.StackPane; -import javafx.scene.layout.VBox; - -import lombok.Builder; -import lombok.Value; - -public class BigIconButton extends ButtonComp { - - public BigIconButton(ObservableValue name, Node graphic, Runnable listener) { - super(name, graphic, listener); - } - - @Override - public Structure createBase() { - var vbox = new VBox(); - vbox.getStyleClass().add("vbox"); - vbox.setAlignment(Pos.CENTER); - - var icon = new StackPane(getGraphic()); - icon.setAlignment(Pos.CENTER); - icon.getStyleClass().add("icon"); - vbox.getChildren().add(icon); - - var label = new Label(); - label.textProperty().bind(getName()); - label.getStyleClass().add("name"); - vbox.getChildren().add(label); - - var b = new Button(null); - b.accessibleTextProperty().bind(getName()); - b.setGraphic(vbox); - b.setOnAction(e -> getListener().run()); - b.getStyleClass().add("big-icon-button-comp"); - return Structure.builder() - .stack(vbox) - .graphic(getGraphic()) - .graphicPane(icon) - .text(label) - .button(b) - .build(); - } - - @Value - @Builder - public static class Structure implements CompStructure