Skip to content

Commit 309cc90

Browse files
committed
Issue #8135 [Feature Request]: Add quick file creation to the Hop file browser
1 parent f6d9225 commit 309cc90

14 files changed

Lines changed: 769 additions & 7 deletions

File tree

docs/hop-user-manual/modules/ROOT/pages/hop-gui/perspective-file-explorer.adoc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ image::hop-gui/perspective-explorer-toolbar-items.png[File explorer toolbar item
4848
* Refresh: Refresh the file list.
4949
* Show or hide files: Show or hide files or directories.
5050

51+
New pipelines, workflows and files are created from the file explorer's right-click menu rather than the toolbar; see the "Right-click menu options" section below.
52+
5153
== Toggling the File Explorer Panel
5254

5355
When the File Explorer perspective is already active, clicking the File Explorer button in the perspectives toolbar (left side) will toggle the visibility of the file explorer panel (project tree). This allows you to maximize the workspace area when you don't need to see the file tree.
@@ -95,7 +97,10 @@ Small information icons will be added to the various transforms, actions and hop
9597
=== Right-click menu options
9698
The right-click menu in Apache Hop Git integration offers several useful options to help manage your files and Git operations directly from the interface. Here’s a breakdown of each option:
9799

98-
* Create folder: Create a new folder within the selected directory. This is useful for organizing workflows, pipelines, and other project resources.
100+
* Create pipeline: Create a new, empty pipeline inside the selected folder and open it. Only available when a folder is selected.
101+
* Create workflow: Create a new, empty workflow inside the selected folder and open it. Only available when a folder is selected.
102+
* Create file: Create a new, empty file inside the selected folder, choosing its name and file type, and open it. Only available when a folder is selected.
103+
* Create folder: Create a new folder within the selected directory. This is useful for organizing workflows, pipelines, and other project resources. Only available when a folder is selected.
99104
* Expand all folders: Reveals all nested folders in the project directory tree. Useful for getting a full overview of your project structure without opening folders one by one.
100105
* Collapse all folders: Closes all expanded folders, showing only the top-level directory. Helps reset the view or reduce visual clutter.
101106
* Open: Opens the selected file or folder. For workflows and pipelines, it launches them in the Data Orchestration perspective, while other file types will open in a new tab.

plugins/transforms/json/src/main/java/org/apache/hop/pipeline/transforms/types/JsonExplorerFileType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public JsonExplorerFileType() {
4343
new String[] {"*.json"},
4444
new String[] {"JSON files"},
4545
FileTypeCapabilities.getCapabilities(
46+
IHopFileType.CAPABILITY_NEW,
4647
IHopFileType.CAPABILITY_SAVE,
4748
IHopFileType.CAPABILITY_SAVE_AS,
4849
IHopFileType.CAPABILITY_CLOSE,

plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/PythonExplorerFileType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public PythonExplorerFileType() {
4444
new String[] {"*.py"},
4545
new String[] {"Python scripts"},
4646
FileTypeCapabilities.getCapabilities(
47+
IHopFileType.CAPABILITY_NEW,
4748
IHopFileType.CAPABILITY_SAVE,
4849
IHopFileType.CAPABILITY_SAVE_AS,
4950
IHopFileType.CAPABILITY_CLOSE,

plugins/transforms/textfile/src/main/java/org/apache/hop/pipeline/transforms/types/TextExplorerFileType.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public TextExplorerFileType() {
4343
new String[] {"*.txt"},
4444
new String[] {"TXT files"},
4545
FileTypeCapabilities.getCapabilities(
46+
IHopFileType.CAPABILITY_NEW,
4647
IHopFileType.CAPABILITY_SAVE,
4748
IHopFileType.CAPABILITY_SAVE_AS,
4849
IHopFileType.CAPABILITY_CLOSE,

plugins/transforms/textfile/src/test/java/org/apache/hop/pipeline/transforms/types/PythonExplorerFileTypeTest.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
package org.apache.hop.pipeline.transforms.types;
1919

2020
import static org.junit.jupiter.api.Assertions.assertEquals;
21-
import static org.junit.jupiter.api.Assertions.assertFalse;
2221
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
2322
import static org.junit.jupiter.api.Assertions.assertNotNull;
2423
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -72,7 +71,7 @@ void testHasExpectedCapabilities() {
7271
assertTrue(fileType.hasCapability(IHopFileType.CAPABILITY_FILE_HISTORY));
7372
assertTrue(fileType.hasCapability(IHopFileType.CAPABILITY_COPY));
7473
assertTrue(fileType.hasCapability(IHopFileType.CAPABILITY_SELECT));
75-
assertFalse(fileType.hasCapability(IHopFileType.CAPABILITY_NEW));
74+
assertTrue(fileType.hasCapability(IHopFileType.CAPABILITY_NEW));
7675
}
7776

7877
@Test
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.hop.ui.hopgui.perspective.explorer;
19+
20+
import java.util.List;
21+
import org.apache.hop.i18n.BaseMessages;
22+
import org.apache.hop.ui.core.PropsUi;
23+
import org.apache.hop.ui.core.dialog.BaseDialog;
24+
import org.apache.hop.ui.core.gui.GuiResource;
25+
import org.apache.hop.ui.core.gui.WindowProperty;
26+
import org.apache.hop.ui.hopgui.file.IHopFileType;
27+
import org.apache.hop.ui.pipeline.transform.BaseTransformDialog;
28+
import org.eclipse.swt.SWT;
29+
import org.eclipse.swt.layout.FormAttachment;
30+
import org.eclipse.swt.layout.FormData;
31+
import org.eclipse.swt.layout.FormLayout;
32+
import org.eclipse.swt.widgets.Button;
33+
import org.eclipse.swt.widgets.Combo;
34+
import org.eclipse.swt.widgets.Dialog;
35+
import org.eclipse.swt.widgets.Label;
36+
import org.eclipse.swt.widgets.Shell;
37+
import org.eclipse.swt.widgets.Text;
38+
39+
/** Asks for a file name and a file type, and reports the full path of the file to create. */
40+
public class CreateFileDialog extends Dialog {
41+
private static final Class<?> PKG = ExplorerPerspective.class;
42+
43+
private final String folderPath;
44+
private final List<IHopFileType> fileTypes;
45+
private final PropsUi props;
46+
47+
private Shell shell;
48+
private Text wName;
49+
private Combo wType;
50+
private Label wPreview;
51+
private Button wOk;
52+
53+
private String filePath;
54+
private IHopFileType selectedFileType;
55+
56+
public CreateFileDialog(Shell parent, String folderPath, List<IHopFileType> fileTypes) {
57+
super(parent, SWT.NONE);
58+
this.folderPath = folderPath;
59+
this.fileTypes = fileTypes;
60+
this.props = PropsUi.getInstance();
61+
}
62+
63+
/** Opens the dialog. Returns the full path of the file to create, or null when cancelled. */
64+
public String open() {
65+
Shell parent = getParent();
66+
67+
shell = new Shell(parent, SWT.DIALOG_TRIM | SWT.RESIZE | SWT.APPLICATION_MODAL | SWT.SHEET);
68+
PropsUi.setLook(shell);
69+
shell.setImage(GuiResource.getInstance().getImageHopUi());
70+
shell.setText(BaseMessages.getString(PKG, "ExplorerPerspective.CreateFile.Header"));
71+
72+
FormLayout formLayout = new FormLayout();
73+
formLayout.marginWidth = PropsUi.getFormMargin();
74+
formLayout.marginHeight = PropsUi.getFormMargin();
75+
shell.setLayout(formLayout);
76+
77+
int margin = PropsUi.getMargin();
78+
79+
Label wlName = new Label(shell, SWT.NONE);
80+
wlName.setText(BaseMessages.getString(PKG, "ExplorerPerspective.CreateFile.Name.Label"));
81+
PropsUi.setLook(wlName);
82+
FormData fdlName = new FormData();
83+
fdlName.left = new FormAttachment(0, 0);
84+
fdlName.top = new FormAttachment(0, margin);
85+
wlName.setLayoutData(fdlName);
86+
87+
wName = new Text(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER);
88+
PropsUi.setLook(wName);
89+
FormData fdName = new FormData();
90+
fdName.left = new FormAttachment(0, 0);
91+
fdName.top = new FormAttachment(wlName, margin);
92+
fdName.right = new FormAttachment(100, -margin);
93+
wName.setLayoutData(fdName);
94+
wName.addModifyListener(e -> updateState());
95+
96+
Label wlType = new Label(shell, SWT.NONE);
97+
wlType.setText(BaseMessages.getString(PKG, "ExplorerPerspective.CreateFile.Type.Label"));
98+
PropsUi.setLook(wlType);
99+
FormData fdlType = new FormData();
100+
fdlType.left = new FormAttachment(0, 0);
101+
fdlType.top = new FormAttachment(wName, margin);
102+
wlType.setLayoutData(fdlType);
103+
104+
wType = new Combo(shell, SWT.SINGLE | SWT.LEFT | SWT.BORDER | SWT.READ_ONLY);
105+
PropsUi.setLook(wType);
106+
for (IHopFileType fileType : fileTypes) {
107+
wType.add(fileType.getName());
108+
}
109+
if (!fileTypes.isEmpty()) {
110+
wType.select(0);
111+
}
112+
FormData fdType = new FormData();
113+
fdType.left = new FormAttachment(0, 0);
114+
fdType.top = new FormAttachment(wlType, margin);
115+
fdType.right = new FormAttachment(100, -margin);
116+
wType.setLayoutData(fdType);
117+
wType.addListener(SWT.Selection, e -> updateState());
118+
119+
Label wlPreview = new Label(shell, SWT.NONE);
120+
wlPreview.setText(BaseMessages.getString(PKG, "ExplorerPerspective.CreateFile.Preview.Label"));
121+
PropsUi.setLook(wlPreview);
122+
FormData fdlPreview = new FormData();
123+
fdlPreview.left = new FormAttachment(0, 0);
124+
fdlPreview.top = new FormAttachment(wType, margin);
125+
wlPreview.setLayoutData(fdlPreview);
126+
127+
wPreview = new Label(shell, SWT.NONE);
128+
PropsUi.setLook(wPreview);
129+
FormData fdPreview = new FormData();
130+
fdPreview.left = new FormAttachment(0, 0);
131+
fdPreview.top = new FormAttachment(wlPreview, margin);
132+
fdPreview.right = new FormAttachment(100, -margin);
133+
wPreview.setLayoutData(fdPreview);
134+
135+
wOk = new Button(shell, SWT.PUSH);
136+
wOk.setText(BaseMessages.getString(PKG, "System.Button.OK"));
137+
Button wCancel = new Button(shell, SWT.PUSH);
138+
wCancel.setText(BaseMessages.getString(PKG, "System.Button.Cancel"));
139+
140+
BaseTransformDialog.positionBottomButtons(shell, new Button[] {wOk, wCancel}, margin, wPreview);
141+
142+
wOk.addListener(SWT.Selection, e -> ok());
143+
wCancel.addListener(SWT.Selection, e -> cancel());
144+
145+
updateState();
146+
147+
BaseDialog.defaultShellHandling(shell, c -> ok(), c -> cancel());
148+
149+
return filePath;
150+
}
151+
152+
/** The file type picked in the combo. Only meaningful when {@link #open()} returned a path. */
153+
public IHopFileType getSelectedFileType() {
154+
return selectedFileType;
155+
}
156+
157+
private IHopFileType currentFileType() {
158+
int index = wType.getSelectionIndex();
159+
if (index < 0 || index >= fileTypes.size()) {
160+
return null;
161+
}
162+
return fileTypes.get(index);
163+
}
164+
165+
private String currentPath() {
166+
IHopFileType fileType = currentFileType();
167+
if (fileType == null || !ExplorerCreateUtils.isSimpleFileName(wName.getText())) {
168+
return null;
169+
}
170+
String fileName =
171+
ExplorerCreateUtils.applyExtension(wName.getText(), fileType.getDefaultFileExtension());
172+
String path = ExplorerCreateUtils.childPath(folderPath, fileName);
173+
if (!ExplorerCreateUtils.resolvesInsideFolder(folderPath, path)) {
174+
return null;
175+
}
176+
return path;
177+
}
178+
179+
private void updateState() {
180+
String path = currentPath();
181+
wPreview.setText(path == null ? "" : path);
182+
wOk.setEnabled(path != null);
183+
}
184+
185+
private void ok() {
186+
String path = currentPath();
187+
if (path == null) {
188+
return;
189+
}
190+
filePath = path;
191+
selectedFileType = currentFileType();
192+
dispose();
193+
}
194+
195+
private void cancel() {
196+
filePath = null;
197+
selectedFileType = null;
198+
dispose();
199+
}
200+
201+
private void dispose() {
202+
props.setScreen(new WindowProperty(shell));
203+
shell.dispose();
204+
}
205+
}

0 commit comments

Comments
 (0)