diff --git a/designer-base/src/main/java/com/fr/design/actions/file/PreferencePane.java b/designer-base/src/main/java/com/fr/design/actions/file/PreferencePane.java index 288513231f..fcaaccfc20 100644 --- a/designer-base/src/main/java/com/fr/design/actions/file/PreferencePane.java +++ b/designer-base/src/main/java/com/fr/design/actions/file/PreferencePane.java @@ -119,6 +119,7 @@ public class PreferencePane extends BasicPane { private static final int PREFERENCE_LABEL_MAX_WIDTH = 460; private static final int OFFSET_HEIGHT = 60; + private static final int VCS_FILL_TOTAL = 5; private static final String TYPE = "pressed"; private static final String DISPLAY_TYPE = "+"; private static final String BACK_SLASH = "BACK_SLASH"; @@ -399,9 +400,11 @@ public class PreferencePane extends BasicPane { generalPane.add(movePanel, BorderLayout.NORTH); JPanel savePane = FRGUIPaneFactory.createTopVerticalTitledBorderPane(i18nText("Fine-Design_Vcs_Save_Setting")); JPanel vcsPane = FRGUIPaneFactory.createTopVerticalTitledBorderPane(i18nText("Fine-Design_Vcs_Clean_Setting")); - JPanel containPane = new JPanel(new GridLayout(10,1,0,8)); + JPanel containPane = FRGUIPaneFactory.createY_AXISBoxInnerContainer_L_Pane(); containPane.add(savePane); containPane.add(vcsPane); + //填充一下面板 + fillPane(containPane, VCS_FILL_TOTAL); generalPane.add(containPane, BorderLayout.CENTER); remindVcsLabel = new UILabel(i18nText("Fine-Design_Vcs_Remind")); remindVcsLabel.setVisible(!VcsHelper.getInstance().needInit()); @@ -458,6 +461,12 @@ public class PreferencePane extends BasicPane { } } + private void fillPane(JPanel containPane, int total) { + for (int i = 0; i < total; i++) { + containPane.add(new JPanel()); + } + } + private VcsMovePanel createMovePane(CardLayout cardLayout, JPanel parentPane) { return new VcsMovePanel(cardLayout, parentPane, new VcsMovePanel.MoveCallBack(){ @Override diff --git a/designer-base/src/main/java/com/fr/design/file/TemplateTreePane.java b/designer-base/src/main/java/com/fr/design/file/TemplateTreePane.java index d985595f5f..74f26aaf80 100644 --- a/designer-base/src/main/java/com/fr/design/file/TemplateTreePane.java +++ b/designer-base/src/main/java/com/fr/design/file/TemplateTreePane.java @@ -18,6 +18,8 @@ import com.fr.design.lock.LockInfoDialog; import com.fr.design.mainframe.JTemplate; import com.fr.design.mainframe.manager.search.TemplateTreeSearchManager; import com.fr.design.mainframe.manager.search.searcher.control.pane.TemplateSearchRemindPane; +import com.fr.design.mainframe.vcs.VcsService; +import com.fr.design.mainframe.vcs.common.VcsHelper; import com.fr.file.FILE; import com.fr.file.FileNodeFILE; import com.fr.file.filetree.FileNode; @@ -38,6 +40,7 @@ import java.util.UUID; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.SwingUtilities; +import javax.swing.SwingWorker; import javax.swing.ToolTipManager; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreeNode; @@ -59,6 +62,7 @@ import java.util.Objects; import java.util.Observable; import java.util.Observer; import java.util.Set; +import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; import org.jetbrains.annotations.Nullable; @@ -346,13 +350,7 @@ public class TemplateTreePane extends JPanel implements FileOperations { YES_NO_OPTION) == JOptionPane.YES_OPTION) { // 删除所有选中的即可 - if (!deleteNodes(Arrays.asList(treeNodes))) { - FineJOptionPane.showConfirmDialog(null, - Toolkit.i18nText("Fine-Design_Basic_Delete_Failure"), - Toolkit.i18nText("Fine-Design_Basic_Error"), - JOptionPane.DEFAULT_OPTION, - JOptionPane.ERROR_MESSAGE); - } + deleteNodes(Arrays.asList(treeNodes)); } } else { @@ -377,13 +375,7 @@ public class TemplateTreePane extends JPanel implements FileOperations { YES_NO_OPTION) == JOptionPane.YES_OPTION) { // 删除其他 - if (!deleteNodes(deletableNodes)) { - FineJOptionPane.showConfirmDialog(null, - Toolkit.i18nText("Fine-Design_Basic_Delete_Failure"), - Toolkit.i18nText("Fine-Design_Basic_Error"), - JOptionPane.DEFAULT_OPTION, - JOptionPane.ERROR_MESSAGE); - } + deleteNodes(deletableNodes); } } Set deletedFileNode = deletableNodes.stream().map(treeNode -> (FileNode) treeNode.getUserObject()).collect(Collectors.toSet()); @@ -407,23 +399,46 @@ public class TemplateTreePane extends JPanel implements FileOperations { } } - private boolean deleteNodes(Collection nodes) { - - boolean success = true; - for (ExpandMutableTreeNode treeNode : nodes) { - Object node = treeNode.getUserObject(); - if (node instanceof FileNode) { - FileNodeFILE nodeFILE = new FileNodeFILE((FileNode) node); - if (nodeFILE.exists()) { - if (TemplateResourceManager.getResource().delete(nodeFILE)) { - HistoryTemplateListCache.getInstance().deleteFile(nodeFILE); - } else { - success = false; + private void deleteNodes(Collection nodes) { + new SwingWorker(){ + @Override + protected Boolean doInBackground() throws Exception { + boolean success = true; + for (ExpandMutableTreeNode treeNode : nodes) { + Object node = treeNode.getUserObject(); + if (node instanceof FileNode) { + FileNodeFILE nodeFILE = new FileNodeFILE((FileNode) node); + if (nodeFILE.exists()) { + VcsService.getInstance().doRecycle(VcsHelper.getInstance().dealWithFilePath(((FileNode) node).getEnvPath())); + if (TemplateResourceManager.getResource().delete(nodeFILE)) { + HistoryTemplateListCache.getInstance().deleteFile(nodeFILE); + } else { + success = false; + } + } } } + return success; } - } - return success; + @Override + protected void done() { + try { + if (!get()) { + showErrorDialog(); + } + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + }.execute(); + } + + private void showErrorDialog() { + FineJOptionPane.showConfirmDialog(DesignerContext.getDesignerFrame(), + Toolkit.i18nText("Fine-Design_Basic_Delete_Failure"), + Toolkit.i18nText("Fine-Design_Basic_Error"), + JOptionPane.DEFAULT_OPTION, + JOptionPane.ERROR_MESSAGE); } diff --git a/designer-base/src/main/java/com/fr/design/mainframe/DesignerFrameFileDealerPane.java b/designer-base/src/main/java/com/fr/design/mainframe/DesignerFrameFileDealerPane.java index 174655127c..6ef68afe7e 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/DesignerFrameFileDealerPane.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/DesignerFrameFileDealerPane.java @@ -16,6 +16,7 @@ import com.fr.design.data.DesignTableDataManager; import com.fr.design.data.datapane.TableDataTreePane; import com.fr.design.data.datapane.management.search.TableDataTreeSearchManager; import com.fr.design.data.tabledata.ResponseDataSourceChange; +import com.fr.design.dialog.BasicDialog; import com.fr.design.dialog.FineJOptionPane; import com.fr.design.file.FileOperations; import com.fr.design.file.FileToolbarStateChangeListener; @@ -34,8 +35,10 @@ import com.fr.design.layout.TableLayout; import com.fr.design.layout.TableLayoutHelper; import com.fr.design.mainframe.manager.search.TemplateTreeSearchManager; import com.fr.design.mainframe.manager.search.searcher.control.pane.TemplateTreeSearchToolbarPane; +import com.fr.design.mainframe.vcs.RecycleAction; import com.fr.design.mainframe.vcs.common.VcsHelper; import com.fr.design.mainframe.vcs.ui.FileVersionsPanel; +import com.fr.design.mainframe.vcs.ui.VcsNewPane; import com.fr.design.menu.KeySetUtils; import com.fr.design.menu.ShortCut; import com.fr.design.menu.ToolBarDef; @@ -150,6 +153,8 @@ public class DesignerFrameFileDealerPane extends JPanel implements FileToolbarSt private VcsAction vcsAction = new VcsAction(); + private RecycleAction recycleAction = new RecycleAction(); + //搜索 private SwitchAction switchAction = new SwitchAction(); private TemplateTreeSearchToolbarPane searchToolbarPane; @@ -331,7 +336,11 @@ public class DesignerFrameFileDealerPane extends JPanel implements FileToolbarSt vcsAction.setName(Toolkit.i18nText("Fine-Design_Vcs_NotSupportRemote")); } toolbarDef.addShortCut(vcsAction); - + //11.0.19及其之后加入回收站逻辑 + if (!VcsHelper.getInstance().isLegacyMode()) { + recycleAction = new RecycleAction(); + toolbarDef.addShortCut(recycleAction); + } } } @@ -492,14 +501,19 @@ public class DesignerFrameFileDealerPane extends JPanel implements FileToolbarSt public void actionPerformed(ActionEvent e) { String path = DesignerFrameFileDealerPane.getInstance().getSelectedOperation().getFilePath(); path = StableUtils.pathJoin(ProjectConstants.REPORTLETS_NAME, path); + if (VcsHelper.getInstance().isLegacyMode()) { + boolean currentEditing = isCurrentEditing(path); + // 如果模板已经打开了,关掉,避免出现2个同名tab(1个是模板,1个是版本) + closeOpenedTemplate(path, currentEditing); + FileVersionsPanel fileVersionTablePanel = FileVersionsPanel.getInstance(); + fileVersionTablePanel.showFileVersionsPane(); + stateChange(); + } else { + VcsNewPane panel = new VcsNewPane(path); + BasicDialog dialog = panel.showWindow(DesignerContext.getDesignerFrame(), false); + dialog.setVisible(true); + } - boolean isCurrentEditing = isCurrentEditing(path); - - // 如果模板已经打开了,关掉,避免出现2个同名tab(1个是模板,1个是版本) - closeOpenedTemplate(path, isCurrentEditing); - FileVersionsPanel fileVersionTablePanel = FileVersionsPanel.getInstance(); - fileVersionTablePanel.showFileVersionsPane(); - stateChange(); } diff --git a/designer-base/src/main/java/com/fr/design/mainframe/vcs/RecycleAction.java b/designer-base/src/main/java/com/fr/design/mainframe/vcs/RecycleAction.java new file mode 100644 index 0000000000..aa43ed5b91 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/vcs/RecycleAction.java @@ -0,0 +1,31 @@ +package com.fr.design.mainframe.vcs; + +import com.fr.design.actions.UpdateAction; +import com.fr.design.dialog.BasicDialog; +import com.fr.design.i18n.Toolkit; +import com.fr.design.mainframe.DesignerContext; +import com.fr.design.mainframe.vcs.ui.RecyclePane; + +import java.awt.event.ActionEvent; + +/** + * 回收站 + * + * @author Destiny.Lin + * @since 11.0 + * Created on 2023/7/4 + */ +public class RecycleAction extends UpdateAction { + + public RecycleAction() { + this.setSmallIcon("/com/fr/design/standard/vcslist/vcs_recycle"); + this.setName(Toolkit.i18nText("Fine-Design_Vcs_Recycle")); + } + + @Override + public void actionPerformed(ActionEvent e) { + RecyclePane pane = new RecyclePane(); + BasicDialog dialog = pane.showWindow(DesignerContext.getDesignerFrame(), false); + dialog.setVisible(true); + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/vcs/TableEntity.java b/designer-base/src/main/java/com/fr/design/mainframe/vcs/TableEntity.java new file mode 100644 index 0000000000..a143025ce0 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/vcs/TableEntity.java @@ -0,0 +1,25 @@ +package com.fr.design.mainframe.vcs; + +/** + * 表格选中包装类 + * + * @author Destiny.Lin + * @since 11.0 + * Created on 2023/7/11 + */ +public interface TableEntity { + + /** + * 是否选中 + * + * @return + */ + boolean isSelect(); + + /** + * 设置选中属性 + * + * @param select + */ + void setSelect(boolean select); +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/vcs/TableValueOperator.java b/designer-base/src/main/java/com/fr/design/mainframe/vcs/TableValueOperator.java new file mode 100644 index 0000000000..3fcc57cd02 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/vcs/TableValueOperator.java @@ -0,0 +1,21 @@ +package com.fr.design.mainframe.vcs; + + +/** + * 表格操作 + * + * @author Destiny.Lin + * @since 11.0 + * Created on 2023/7/11 + */ +public interface TableValueOperator { + + /** + * 获取对应列的值 + * + * @param o 表格内容 + * @param columnIndex 列数 + * @return 对应列的值 + */ + Object getValue(T o, int columnIndex); +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/vcs/VcsService.java b/designer-base/src/main/java/com/fr/design/mainframe/vcs/VcsService.java new file mode 100644 index 0000000000..e83b0212e9 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/vcs/VcsService.java @@ -0,0 +1,162 @@ +package com.fr.design.mainframe.vcs; + +import com.fr.concurrent.NamedThreadFactory; +import com.fr.design.mainframe.vcs.common.VcsHelper; +import com.fr.log.FineLoggerFactory; +import com.fr.report.entity.VcsEntity; +import com.fr.workspace.WorkContext; +import com.fr.workspace.server.vcs.VcsOperator; + +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * 版本管理常用操作 + *

便于版本管理面板快捷实现版本管理相关操作 + * + * @author Destiny.Lin + * @since 11.0 + * Created on 2023/7/10 + */ +public class VcsService { + + private static final String SERVICE = "VcsService"; + private static final VcsService INSTANCE = new VcsService(); + + private static final ExecutorService executorService = Executors.newFixedThreadPool(5, new NamedThreadFactory(SERVICE)); + + /** + * 获取单例 + * + * @return + */ + public static VcsService getInstance() { + return INSTANCE; + } + + private VcsService() { + } + + /** + * 回收模板 + * + * @param filename + */ + public void doRecycle(String filename) { + try { + WorkContext.getCurrent().get(VcsOperator.class).recycleVersion(VcsHelper.getInstance().getCurrentUsername(), filename); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } + + + /** + * 从回收站还原版本 + * + * @param vcsEntities + */ + public void doRestore(List vcsEntities) { + try { + executorService.execute(new Runnable() { + @Override + public void run() { + VcsOperator operator = WorkContext.getCurrent().get(VcsOperator.class); + for (VcsEntity vcsEntity : vcsEntities) { + String fileName = vcsEntity.getFilename(); + operator.restoreVersion(fileName); + } + } + }); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } + + + /** + * 删除对应列表中所有模板的指定的历史版本 + * + * @param vcsEntities + */ + public void doDelete(List vcsEntities, boolean all) { + try { + executorService.execute(new Runnable() { + @Override + public void run() { + VcsOperator operator = WorkContext.getCurrent().get(VcsOperator.class); + for (VcsEntity vcsEntity : vcsEntities) { + String fileName = vcsEntity.getFilename(); + if (all) { + operator.deleteVersionForRecycle(fileName); + } else { + operator.deleteVersion(fileName, vcsEntity.getVersion()); + } + } + } + }); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } + + /** + * 删除指定模板的全部历史版本 + * + * @param entity VcsEntity + */ + public void deleteEntity(VcsEntity entity) { + try { + executorService.execute(new Runnable() { + @Override + public void run() { + VcsOperator vcsOperator = WorkContext.getCurrent().get(VcsOperator.class); + String fileName = entity.getFilename(); + vcsOperator.deleteVersionForRecycle(fileName); + } + }); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } + + /** + * 删除指定模板的指定版本 + * + * @param fileName 文件名 + * @param version 版本号 + */ + public void deleteEntity(String fileName, int version) { + try { + executorService.execute(new Runnable() { + @Override + public void run() { + VcsOperator vcsOperator = WorkContext.getCurrent().get(VcsOperator.class); + vcsOperator.deleteVersion(fileName, version); + } + }); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } + + /** + * 更新版本 + * + * @param entity 版本 + */ + public void updateEntityAnnotation(VcsEntity entity) { + try { + executorService.execute(new Runnable() { + @Override + public void run() { + VcsOperator vcsOperator = WorkContext.getCurrent().get(VcsOperator.class); + vcsOperator.updateVersion(entity); + } + }); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/vcs/VcsTableEntity.java b/designer-base/src/main/java/com/fr/design/mainframe/vcs/VcsTableEntity.java new file mode 100644 index 0000000000..200aa38e7a --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/vcs/VcsTableEntity.java @@ -0,0 +1,108 @@ +package com.fr.design.mainframe.vcs; + +import com.fr.report.entity.VcsEntity; + +/** + * 包装VcsEntity的用于表格展示与处理的类 + * + * @author Destiny.Lin + * @since 11.0 + * Created on 2023/7/10 + */ +public class VcsTableEntity implements TableEntity{ + + private boolean select = false; + + private static final String MB = "MB"; + + private static final String VERSION = "V."; + private static final double MB_SIZE = 1024.0; + private VcsEntity entity; + + + public VcsTableEntity(VcsEntity entity) { + this.entity = entity; + } + + /** + * 获取模板名 + * + * @return 模板名 + */ + public String getFilename() { + return entity.getFilename(); + } + + /** + * 获取版本大小 + * + * @return 版本大小 + */ + public String getSize() { + return String.format("%.2f",entity.getSize()/MB_SIZE) + MB; + } + + /** + * 获取修改时间 + * + * @return 修改时间 + */ + public String getTime() { + return entity.getTime().toLocaleString(); + } + + /** + * 获取用户名 + * + * @return 用户名 + */ + public String getUserName() { + return entity.getUsername(); + } + + + /** + * 获取备注 + * + * @return 备注 + */ + public String getCommitMsg() { + return entity.getCommitMsg(); + } + + /** + * 获取版本号 + * + * @return 版本号 + */ + public String getVersion() { + return VERSION + entity.getVersion(); + } + + /** + * 获取删除时间 + * + * @return 删除时间 + */ + public String getDeleteTime() { + return entity.getDeleteTime().toLocaleString(); + } + + @Override + public boolean isSelect() { + return select; + } + + @Override + public void setSelect(boolean select) { + this.select = select; + } + + public VcsEntity getEntity() { + return entity; + } + + public void setEntity(VcsEntity entity) { + this.entity = entity; + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/vcs/common/VcsHelper.java b/designer-base/src/main/java/com/fr/design/mainframe/vcs/common/VcsHelper.java index b245eff66c..2a78159217 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/vcs/common/VcsHelper.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/vcs/common/VcsHelper.java @@ -155,18 +155,28 @@ public class VcsHelper implements JTemplateActionListener { } private String getEditingFilename() { - String vcsCacheDir = VcsFileSystem.getInstance().getVcsCacheRelativePath(); JTemplate jt = HistoryTemplateListCache.getInstance().getCurrentEditingTemplate(); String editingFilePath = jt.getEditingFILE().getPath(); - if (editingFilePath.startsWith(ProjectConstants.REPORTLETS_NAME)) { - editingFilePath = editingFilePath.replaceFirst(ProjectConstants.REPORTLETS_NAME, StringUtils.EMPTY); - } else if (editingFilePath.startsWith(vcsCacheDir)) { - editingFilePath = editingFilePath.replaceFirst(vcsCacheDir, StringUtils.EMPTY); + return dealWithFilePath(editingFilePath); + } + + /** + * 处理传入的文件名,使其符合Vcs规范 + * + * @param filePath 文件路径 + * @return 处理完的文件 + */ + public String dealWithFilePath(String filePath) { + String vcsCacheDir = VcsFileSystem.getInstance().getVcsCacheRelativePath(); + if (filePath.startsWith(ProjectConstants.REPORTLETS_NAME)) { + filePath = filePath.replaceFirst(ProjectConstants.REPORTLETS_NAME, StringUtils.EMPTY); + } else if (filePath.startsWith(vcsCacheDir)) { + filePath = filePath.replaceFirst(vcsCacheDir, StringUtils.EMPTY); } - if (editingFilePath.startsWith(VCS_FILE_SLASH)) { - editingFilePath = editingFilePath.substring(1); + if (filePath.startsWith(VCS_FILE_SLASH)) { + filePath = filePath.substring(1); } - return editingFilePath; + return filePath; } private boolean needDeleteVersion(VcsEntity entity) { @@ -369,7 +379,7 @@ public class VcsHelper implements JTemplateActionListener { * @return 支持返回true */ public boolean checkMoveFunctionSupport() { - return VcsHelper.getInstance().isLegacyMode() && (WorkContext.getCurrent().isLocal() || WorkContext.getCurrent().isRoot()); + return WorkContext.getCurrent().isLocal() || WorkContext.getCurrent().isRoot(); } /** diff --git a/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/AbstractSupportSelectTablePane.java b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/AbstractSupportSelectTablePane.java new file mode 100644 index 0000000000..eaa63b1ccd --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/AbstractSupportSelectTablePane.java @@ -0,0 +1,370 @@ +package com.fr.design.mainframe.vcs.ui; + +import com.fr.design.data.tabledata.tabledatapane.loading.TipsPane; +import com.fr.design.dialog.BasicPane; +import com.fr.design.gui.icheckbox.UICheckBox; +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.gui.itableeditorpane.UITableEditAction; +import com.fr.design.gui.itableeditorpane.UITableEditorPane; +import com.fr.design.gui.itableeditorpane.UITableModelAdapter; +import com.fr.design.gui.itextfield.UITextField; +import com.fr.design.layout.FRGUIPaneFactory; +import com.fr.design.mainframe.vcs.TableEntity; +import com.fr.design.mainframe.vcs.TableValueOperator; +import com.fr.stable.StringUtils; + +import javax.swing.BorderFactory; +import javax.swing.DefaultCellEditor; +import javax.swing.JComponent; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTable; +import javax.swing.SwingConstants; +import javax.swing.SwingWorker; +import javax.swing.UIManager; +import javax.swing.plaf.ColorUIResource; +import javax.swing.plaf.UIResource; +import javax.swing.table.JTableHeader; +import javax.swing.table.TableCellRenderer; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.List; +import java.util.concurrent.ExecutionException; + + +/** + * 比较通用的带全选功能的表格面板 + * + *

整体划分为north与center两个面板(参考BorderLayout的布局),center部分用于置放表格,表格部分支持全选,north部分支持自定义,用于展示各种标签按钮输入框

+ *

获取数据的时候不需要用SwingWorker(内部已经实现),考虑到大部分表格内容的展现需要去获取数据,而获取数据大部分都需要一定时间,因此为了防止UI冻结,在加载数据结束之前先展示进度条面板

+ *

使用该面板你可能需要:

+ *
  • 自己封装一个实现TableEntity接口(该接口用于提供(选中/全选)功能),内部存放你要放入表格的类,例如VcsEntity,参考VcsTableEntity
  • + *
  • 初始化的时候赋予变量model值(如果有需要的话),该变量用于控制表格的数据类型,默认DefaultModel——分为5列,第一列为勾选框
  • + *
  • (一定要干的)初始化的时候赋予变量model内operators值,用于确认表格每一列getValueAt返回的值
  • + *
  • 上面板也允许自定义,重写initTableTopPane即可
  • + *
  • 要使用该面板可以参考:RecyclePane
  • + * + * @author Destiny.Lin + * @since 11.0 + * Created on 2023/7/11 + */ +public abstract class AbstractSupportSelectTablePane extends BasicPane { + + public static final Color DEFAULT_HEADER_COLOR = new Color(232, 232, 233); + public static final Color DEFAULT_SELECT_TABLE_ROW_COLOR = new ColorUIResource(200, 221, 233); + private CardLayout layout; + + + protected JPanel contentPane; + + protected UITextField searchTextField = new UITextField();; + + protected UILabel deleteLabel = new UILabel(); + + /** + * 整体面板的center部分,用来放表格 + */ + protected UITableEditorPane tableContentPane; + + protected UITableModelAdapter model; + /** + * 整体面板的north部分 + */ + protected JPanel tableTopPane = new JPanel(); + /** + * 整体面板 + */ + protected JPanel tablePane = new JPanel(); + + private int selectCount = 0; + private static final String LOADING = "loading"; + private static final String TABLE ="table"; + + protected List entities; + + protected TableValueOperator operator; + + + public AbstractSupportSelectTablePane(String title, TableValueOperator operators, String[] tableNames, boolean needBorder) { + this.operator = operators; + this.model = new DefaultModel(tableNames, new Class[]{ + Boolean.class, + UILabel.class, + UILabel.class, + UILabel.class, + UILabel.class + + }); + init(title, needBorder); + } + + public AbstractSupportSelectTablePane(String title, TableValueOperator operators, boolean needBorder) { + this.operator = operators; + init(title, needBorder); + } + + /** + * 初始化 + * + */ + private void init(String title, boolean needBorder) { + this.setLayout(FRGUIPaneFactory.createBorderLayout()); + contentPane = new JPanel(); + layout = new CardLayout(); + contentPane.setLayout(layout); + contentPane.add(new TipsPane(true), LOADING); + this.add(contentPane); + new SwingWorker, Void>(){ + @Override + protected List doInBackground() { + return getTableList(); + } + @Override + protected void done() { + try { + entities = get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + contentPane.add(createTablePane(title, needBorder), TABLE); + layout.show(contentPane, TABLE); + } + }.execute(); + } + + /** + * 获取表格数据 + * + * @return 表格数据 + */ + abstract protected List getTableList(); + + private JPanel createTablePane(String title, boolean needBorder) { + tablePane = needBorder ? FRGUIPaneFactory.createTopVerticalTitledBorderPane(title) : new JPanel(); + if (isNeedTopPane()) { + initTableTopPane(); + initTopPaneListener(); + } + initTableContentPane(entities); + tablePane.setLayout(new BorderLayout()); + tablePane.add(tableTopPane, BorderLayout.NORTH); + tablePane.add(tableContentPane, BorderLayout.CENTER); + tableContentPane.getEditTable().getColumnModel().getColumn(0).setMaxWidth(50); + return tablePane; + } + + /** + * 初始化表格面板 + * + * @param entities 表格数据 + */ + protected void initTableContentPane(List entities) { + tableContentPane = new UITableEditorPane<>(model); + model.setList(entities); + JTable table = tableContentPane.getEditTable(); + table.getTableHeader().setBackground(DEFAULT_HEADER_COLOR); + table.getTableHeader().setDefaultRenderer(new HeaderRenderer(tableContentPane.getEditTable())); + table.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + int row = ((JTable) e.getSource()).rowAtPoint(e.getPoint()); + int col = ((JTable) e.getSource()).columnAtPoint(e.getPoint()); + if (col == 0) { + T entity = model.getSelectedValue(); + //改变面板的各个状态 + changeComponentStatus(entity, row, col, table); + } + } + }); + initExtraListener4Table(table); + } + + /** + * 设定额外的表格事件 + * + * @param table 表格 + */ + protected void initExtraListener4Table(JTable table) { + //do nothing + } + + /** + * 是否需要上面板 + * + * @return + */ + protected boolean isNeedTopPane() { + return true; + } + + private void changeComponentStatus(T entity, int row, int col, JTable table) { + boolean select = entity.isSelect(); + entity.setSelect(!select); + table.setValueAt(entity.isSelect(), row, col); + if (select) { + selectCount--; + } else { + selectCount++; + } + //更新表头的勾选框状态 + HeaderRenderer renderer = (HeaderRenderer) table.getTableHeader().getDefaultRenderer(); + renderer.refreshHeader(table, selectCount >= table.getRowCount()); + } + + + /** + * 初始化上面板 + */ + abstract protected void initTableTopPane(); + + /** + * 初始化上面板事件 + */ + abstract protected void initTopPaneListener(); + + + /** + * 表头渲染 + */ + public class HeaderRenderer implements TableCellRenderer { + JTableHeader tableHeader; + final UICheckBox selectBox; + + public HeaderRenderer(JTable table) { + this.tableHeader = table.getTableHeader(); + selectBox = new UICheckBox(); + selectBox.setSelected(false); + tableHeader.addMouseListener(new MouseAdapter() { + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() > 0) { + //获得选中列 + int selectColumn = tableHeader.columnAtPoint(e.getPoint()); + if (selectColumn == 0) { + boolean value = !selectBox.isSelected(); + selectBox.setSelected(value); + selectAllOrNull(value); + selectCount = value ? table.getRowCount() : 0; + tableHeader.repaint(); + model.fireTableDataChanged(); + } + } + } + + private void selectAllOrNull(boolean value) { + int len = table.getRowCount(); + for (T entity : model.getList()) { + entity.setSelect(value); + } + } + }); + } + + /** + * 刷新表头 + * + * @param table + */ + public void refreshHeader(JTable table, boolean value) { + selectBox.setSelected(value); + int rowHeight = table.getRowHeight(); + table.updateUI(); + table.setRowHeight(rowHeight); + } + + @Override + public Component getTableCellRendererComponent(JTable table, + Object value, + boolean isSelected, + boolean hasFocus, + int row, int column) { + tableHeader = table.getTableHeader(); + tableHeader.setReorderingAllowed(false); + String valueStr = (String) value; + JLabel label = new JLabel(valueStr); + label.setHorizontalAlignment(SwingConstants.LEFT); + selectBox.setHorizontalAlignment(SwingConstants.CENTER); + selectBox.setBorderPainted(true); + JComponent component = (column == 0) ? selectBox : label; + component.setForeground(tableHeader.getForeground()); + component.setBackground(tableHeader.getBackground()); + component.setFont(tableHeader.getFont()); + component.setBorder(UIManager.getBorder("TableHeader.cellBorder")); + return component; + } + + + } + + @Override + protected String title4PopupWindow() { + return StringUtils.EMPTY; + } + + /** + * 默认的数据model + */ + public class DefaultModel extends UITableModelAdapter { + + public DefaultModel(String[] tableNames, Class[] classes) { + super(tableNames); + setColumnClass(classes); + this.setDefaultEditor(Boolean.class, new BooleanEditor()); + this.setDefaultRenderer(Boolean.class, new BooleanRenderer()); + } + + @Override + public Object getValueAt(int rowIndex, int columnIndex) { + T vcsEntity = this.getList().get(rowIndex); + return operator.getValue(vcsEntity, columnIndex); + } + + @Override + public boolean isCellEditable(int row, int col) { + return col == 0; + } + + + @Override + public UITableEditAction[] createAction() { + return new UITableEditAction[0]; + } + + + } + + /** + * 用于展示指定风格的checkbox的Editor + */ + public class BooleanEditor extends DefaultCellEditor { + public BooleanEditor() { + super(new UICheckBox()); + UICheckBox checkBox = (UICheckBox) getComponent(); + checkBox.setHorizontalAlignment(UICheckBox.CENTER); + } + } + + /** + * 用于展示指定风格的checkbox的渲染器 + */ + public class BooleanRenderer extends UICheckBox implements TableCellRenderer, UIResource { + public BooleanRenderer() { + super(); + setHorizontalAlignment(JLabel.CENTER); + setBorderPainted(true); + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, + boolean isSelected, boolean hasFocus, int row, int column) { + setSelected((Boolean) table.getValueAt(row, 0)); + setUI(getUICheckBoxUI()); + setBorder(BorderFactory.createEmptyBorder()); + setBackground(isSelected ? DEFAULT_SELECT_TABLE_ROW_COLOR : Color.WHITE); + return this; + } + } + + +} + diff --git a/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/EditFileVersionDialog.java b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/EditFileVersionDialog.java index 105a9b872a..13d705754a 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/EditFileVersionDialog.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/EditFileVersionDialog.java @@ -81,12 +81,7 @@ public class EditFileVersionDialog extends UIDialog { ok.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { - entity.setCommitMsg(msgTestArea.getText()); - WorkContext.getCurrent().get(VcsOperator.class).updateVersion(entity); - setVisible(false); - String path = DesignerFrameFileDealerPane.getInstance().getSelectedOperation().getFilePath(); - FileVersionTable table = FileVersionTable.getInstance(); - table.updateModel(table.getSelectedRow(), WorkContext.getCurrent().get(VcsOperator.class).getVersions(path.replaceFirst("/", StringUtils.EMPTY))); + doOK(); } }); @@ -101,4 +96,23 @@ public class EditFileVersionDialog extends UIDialog { public void checkValid() throws Exception { } + + /** + * 确定事件 + * + */ + public void doOK() { + entity.setCommitMsg(msgTestArea.getText()); + WorkContext.getCurrent().get(VcsOperator.class).updateVersion(entity); + setVisible(false); + String path = DesignerFrameFileDealerPane.getInstance().getSelectedOperation().getFilePath(); + FileVersionTable table = FileVersionTable.getInstance(); + table.updateModel(table.getSelectedRow(), WorkContext.getCurrent().get(VcsOperator.class).getVersions(path.replaceFirst("/", StringUtils.EMPTY))); + } + + public UITextArea getMsgTestArea() { + return msgTestArea; + } + + } diff --git a/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/RecyclePane.java b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/RecyclePane.java new file mode 100644 index 0000000000..e829318174 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/RecyclePane.java @@ -0,0 +1,266 @@ +package com.fr.design.mainframe.vcs.ui; + +import com.fr.base.svg.IconUtils; +import com.fr.design.dialog.FineJOptionPane; +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.gui.itextfield.UITextField; +import com.fr.design.i18n.Toolkit; +import com.fr.design.mainframe.vcs.VcsService; +import com.fr.design.mainframe.vcs.TableEntity; +import com.fr.design.mainframe.vcs.TableValueOperator; +import com.fr.design.mainframe.vcs.VcsTableEntity; +import com.fr.report.entity.VcsEntity; +import com.fr.stable.StringUtils; +import com.fr.workspace.WorkContext; +import com.fr.workspace.server.vcs.VcsOperator; + +import javax.swing.Icon; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static com.fr.design.i18n.Toolkit.i18nText; + +/** + * 回收面板 + * + * @author Destiny.Lin + * @since 11.0 + * Created on 2023/7/5 + */ +public class RecyclePane extends AbstractSupportSelectTablePane { + public static final Icon ICON_SEARCH = IconUtils.readIcon("/com/fr/design/standard/vcslist/vcs_recycle_search"); + public static final Icon ICON_REFRESH = IconUtils.readIcon("/com/fr/design/standard/vcslist/vcs_recycle_restore"); + public static final Icon ICON_DELETE = IconUtils.readIcon("/com/fr/design/standard/vcslist/vcs_recycle_delete"); + + protected UITextField searchTextField; + + protected UILabel deleteLabel; + + protected UILabel restoreLabel; + + private static final int COLUMNS_COUNT = 15; + + public RecyclePane() { + super(i18nText("Fine-Design_Vcs_Recycle"), (o, columnIndex) -> { + switch (columnIndex) { + case 0: + return o.isSelect(); + case 1: + return o.getFilename(); + case 2: + return o.getSize(); + case 3: + return o.getDeleteTime(); + case 4: + return o.getTime(); + default: + return o; + + } + }, new String[]{ + StringUtils.EMPTY, + Toolkit.i18nText("Fine-Design_Vcs_Template"), + Toolkit.i18nText("Fine-Design_Vcs_Recycle_Size"), + Toolkit.i18nText("Fine-Design_Vcs_Delete_Time"), + Toolkit.i18nText("Fine-Design_Vcs_Time") + }, true); + } + + public RecyclePane(String title, TableValueOperator operators, boolean needBorder) { + super(title, operators, needBorder); + } + + + @Override + protected List getTableList() { + List entityList = WorkContext.getCurrent().get(VcsOperator.class).getRecycleEntities(); + List tableEntities = new ArrayList<>(); + for (VcsEntity entity : entityList) { + tableEntities.add(new VcsTableEntity(entity)); + } + return tableEntities; + } + + + @Override + protected void initTableTopPane() { + tableTopPane = new JPanel(); + tableTopPane.setLayout(new BorderLayout()); + JPanel leftPane = new JPanel(); + JPanel rightPane = new JPanel(); + //左边面板,包含搜索icon+搜索框 + if (isNeedSearch()) { + searchTextField = new UITextField(); + searchTextField.setPlaceholder(Toolkit.i18nText("Fine-Design_Vcs_Start_Search")); + searchTextField.setColumns(COLUMNS_COUNT); + leftPane.add(new UILabel(ICON_SEARCH)); + leftPane.add(searchTextField); + } + //右边面板,包括还原按钮+删除按钮 + if (isNeedRestore()) { + restoreLabel = new UILabel(ICON_REFRESH); + restoreLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); + rightPane.add(restoreLabel); + } + if (isNeedDelete()) { + deleteLabel = new UILabel(ICON_DELETE); + deleteLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); + rightPane.add(deleteLabel); + } + tableTopPane.add(leftPane, BorderLayout.EAST); + tableTopPane.add(rightPane, BorderLayout.WEST); + } + + @Override + protected void initTopPaneListener() { + initSearchTextFiledListener(); + initRestoreListener(); + initDeleteLabelListener(); + } + + private void initDeleteLabelListener() { + if (isNeedDelete()) { + deleteLabel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + fireListener(new VcsResponseListener() { + @Override + public void doAfterChooseYes(List selectList) { + VcsService.getInstance().doDelete(selectList, isNeedDeleteAllVersion()); + } + }, true); + } + }); + } + } + + private void initRestoreListener() { + if (isNeedRestore()) { + restoreLabel.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + fireListener(new VcsResponseListener() { + @Override + public void doAfterChooseYes(List selectList) { + VcsService.getInstance().doRestore(selectList); + } + }, false); + } + }); + } + } + + private void initSearchTextFiledListener() { + if (isNeedSearch()) { + searchTextField.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + String str = searchTextField.getText(); + List entityList = model.getList(); + model.setList(entityList.stream().filter(entity -> entity.getEntity().getFilename().contains(str)).collect(Collectors.toList())); + model.fireTableDataChanged(); + } + }); + } + + } + + private void fireListener(VcsResponseListener listener, boolean isDelete) { + List selectList = model.getList().stream().filter(TableEntity::isSelect).map(VcsTableEntity::getEntity).collect(Collectors.toList()); + if (selectList.size() > 0) { + int selVal = FineJOptionPane.showConfirmDialog( + RecyclePane.this, + isDelete ? getDeleteTip(selectList.size()) : getRestoreTip(selectList.size()), + Toolkit.i18nText("Fine-Design_Basic_Confirm"), + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (selVal == JOptionPane.YES_OPTION) { + model.setList(model.getList().stream().filter(tableEntity -> !tableEntity.isSelect()).collect(Collectors.toList())); + model.fireTableDataChanged(); + listener.doAfterChooseYes(selectList); + } + } + } + + + /** + * 删除范围 + * + * @return 默认删除全部版本 + */ + protected boolean isNeedDeleteAllVersion() { + return true; + } + + @Override + protected String title4PopupWindow() { + return i18nText("Fine-Design_Vcs_Recycle"); + } + + /** + * 删除提示 + * + * @return 默认需要 + */ + protected String getDeleteTip(int size) { + return Toolkit.i18nText("Fine-Design_Vcs_Delete_Select_All_Version_Forever", size); + } + + /** + * 还原提示 + * + * @return 默认需要 + */ + protected String getRestoreTip(int size) { + return Toolkit.i18nText("Fine-Design_Vcs_Restore_Select_All_Version", size); + } + + /** + * 是否需要还原功能 + * + * @return 默认需要 + */ + protected boolean isNeedRestore() { + return true; + } + + /** + * 是否需要搜索功能 + * + * @return 默认需要 + */ + protected boolean isNeedSearch() { + return true; + } + + /** + * 是否需要删除功能 + * + * @return 默认需要 + */ + protected boolean isNeedDelete() { + return true; + } + + /** + * 版本管理按钮事件响应 + */ + public interface VcsResponseListener { + + /** + * 选择确认之后要做的事情 + * + * @param selectList 所选上的内容 + */ + void doAfterChooseYes(List selectList); + + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/VcsCenterPane.java b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/VcsCenterPane.java new file mode 100644 index 0000000000..6d927b8376 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/VcsCenterPane.java @@ -0,0 +1,192 @@ +package com.fr.design.mainframe.vcs.ui; + +import com.fr.base.svg.IconUtils; +import com.fr.design.dialog.BasicDialog; +import com.fr.design.dialog.FineJOptionPane; +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.i18n.Toolkit; +import com.fr.design.mainframe.DesignerContext; +import com.fr.design.mainframe.vcs.VcsService; + +import com.fr.design.mainframe.vcs.VcsTableEntity; +import com.fr.file.FileNodeFILE; +import com.fr.file.filetree.FileNode; +import com.fr.report.entity.VcsEntity; +import com.fr.stable.StableUtils; +import com.fr.stable.StringUtils; +import com.fr.stable.project.ProjectConstants; +import com.fr.workspace.WorkContext; +import com.fr.workspace.server.vcs.VcsOperator; + +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.JTable; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.util.ArrayList; +import java.util.List; + + +/** + * 版本中心面板 + * + * @author Destiny.Lin + * @since 11.0 + * Created on 2023/7/13 + */ +public class VcsCenterPane extends VcsNewPane { + + private UILabel manager; + private UILabel open; + private UILabel delete; + + private static final Icon MANAGER_ICON = IconUtils.readIcon("/com/fr/design/standard/vcslist/vcs_center_manager"); + private static final Icon DELETE_ICON = IconUtils.readIcon("/com/fr/design/standard/vcslist/vcs_operator_delete"); + private static final Icon OPEN_ICON = IconUtils.readIcon("/com/fr/design/standard/vcslist/vcs_center_open"); + private static final Class[] CLASSES = new Class[]{ + Boolean.class, + UILabel.class, + UILabel.class, + UILabel.class, + VcsOperatorPane.class + }; + + private static final int OPERATOR_COL = 4; + private static final String[] COLUMN_NAMES = new String[]{ + StringUtils.EMPTY, + Toolkit.i18nText("Fine-Design_Vcs_Template"), + Toolkit.i18nText("Fine-Design_Vcs_Time"), + Toolkit.i18nText("Fine-Design_Vcs_Size"), + Toolkit.i18nText("Fine-Design_Vcs_Operator") + }; + + private static final String TITLE = Toolkit.i18nText("Fine-Design_Vcs_Center"); + + public VcsCenterPane() { + super(TITLE, (o, columnIndex) -> { + switch (columnIndex) { + case 0: + return o.isSelect(); + case 1: + return o.getFilename(); + case 2: + return o.getTime(); + case 3: + return o.getSize(); + default: + return o; + } + }, false, COLUMN_NAMES, CLASSES, OPERATOR_COL); + } + + @Override + public VcsOperatorPane createOperatorPane() { + manager = new UILabel(MANAGER_ICON); + open = new UILabel(OPEN_ICON); + delete = new UILabel(DELETE_ICON); + initManagerListener(); + initOpenListener(); + initDeleteListener(); + List components = new ArrayList<>(); + components.add(manager); + components.add(open); + components.add(delete); + return new VcsOperatorPane(components); + } + + private void initDeleteListener() { + delete.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + JTable table = tableContentPane.getEditTable(); + Object o = table.getValueAt(table.getEditingRow(), table.getEditingColumn()); + if (o instanceof VcsTableEntity) { + VcsEntity entity = ((VcsTableEntity) o).getEntity(); + String fileName = entity.getFilename(); + int selVal = FineJOptionPane.showConfirmDialog( + VcsCenterPane.this, + Toolkit.i18nText("Fine-Design_Vcs_Center_Delete", fileName), + Toolkit.i18nText("Fine-Design_Basic_Confirm"), + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (selVal == JOptionPane.YES_OPTION) { + VcsService.getInstance().deleteEntity(entity); + } + DesignerContext.getDesignerFrame().openTemplate(new FileNodeFILE(new FileNode(getTemplateTruePath(fileName), false))); + } + } + }); + } + + private void initOpenListener() { + open.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + JTable table = tableContentPane.getEditTable(); + Object o = table.getValueAt(table.getEditingRow(), table.getEditingColumn()); + if (o instanceof VcsTableEntity) { + VcsEntity entity = ((VcsTableEntity) o).getEntity(); + DesignerContext.getDesignerFrame().openTemplate(new FileNodeFILE(new FileNode(getTemplateTruePath(entity.getFilename()), false))); + } + } + }); + } + + private void initManagerListener() { + manager.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + JTable table = tableContentPane.getEditTable(); + Object o = table.getValueAt(table.getEditingRow(), table.getEditingColumn()); + if (o instanceof VcsTableEntity) { + VcsEntity entity = ((VcsTableEntity) o).getEntity(); + VcsNewPane pane = new VcsNewPane(getTemplateTruePath(entity.getFilename())) { + @Override + protected String title4PopupWindow() { + return entity.getFilename()+Toolkit.i18nText("Fine-Design_Vcs_Version_Tips"); + } + }; + BasicDialog dialog = pane.showWindow(DesignerContext.getDesignerFrame(), false); + dialog.setVisible(true); + } + } + }); + } + + @Override + protected List getTableList() { + List entities = WorkContext.getCurrent().get(VcsOperator.class).getEveryVersion(); + List tableEntities = new ArrayList<>(); + for (VcsEntity entity : entities) { + tableEntities.add(new VcsTableEntity(entity)); + } + return tableEntities; + } + + @Override + protected String title4PopupWindow() { + return TITLE; + } + + + private String getTemplateTruePath(String filename) { + return StableUtils.pathJoin(ProjectConstants.REPORTLETS_NAME, filename); + } + + + @Override + protected String getDeleteTip(int size) { + return Toolkit.i18nText("Fine-Design_Vcs_Delete_Select_All_Version"); + } + + @Override + protected boolean isNeedTopPane() { + return true; + } + + @Override + protected boolean isNeedRestore() { + return false; + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/VcsMovePanel.java b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/VcsMovePanel.java index af450d8250..a8a719a188 100644 --- a/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/VcsMovePanel.java +++ b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/VcsMovePanel.java @@ -13,6 +13,7 @@ import com.fr.design.gui.ispinner.UISpinner; import com.fr.design.i18n.Toolkit; import com.fr.design.layout.FRGUIPaneFactory; import com.fr.design.layout.VerticalFlowLayout; +import com.fr.design.mainframe.DesignerContext; import com.fr.design.mainframe.vcs.common.VcsHelper; import com.fr.design.utils.DesignUtils; import com.fr.design.widget.FRWidgetFactory; @@ -22,6 +23,7 @@ import com.fr.stable.StringUtils; import com.fr.workspace.server.vcs.v2.move.VcsMoveService; import com.fr.workspace.server.vcs.v2.move.VcsMoveStrategy; +import javax.swing.BorderFactory; import javax.swing.ButtonGroup; import javax.swing.JPanel; import javax.swing.JProgressBar; @@ -73,8 +75,11 @@ public class VcsMovePanel extends BasicPane { public static final String FAILED = "FAILED"; public static boolean moving = false; - private UILabel vcsUpdateExistLabel; - private UILabel vcsUpdateFireLabel; + private UILabel vcsUpdateExistLabel = new UILabel(); + + private UILabel vcsUpdateFireLabel = new UILabel(); + + private UIButton centerButton; private BasicPane choosePane; private CardLayout parentCard; private JPanel parentPane; @@ -113,8 +118,8 @@ public class VcsMovePanel extends BasicPane { this.callBack = callBack; this.setLayout(new BorderLayout()); updatePane = FRGUIPaneFactory.createBoxFlowInnerContainer_S_Pane(); - updatePane.setBackground(BACK_GROUND_COLOR); - //初始化迁移的面板 + updatePane.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 0)); + //初始化顶部的面板 initVcsLabel(updatePane); //initVcsChoosePane initVcsChoosePane(); @@ -227,13 +232,32 @@ public class VcsMovePanel extends BasicPane { } private void initVcsLabel(JPanel parent) { - vcsUpdateExistLabel = new UILabel(IconUtils.readIcon("/com/fr/design/vcs/vcs_move_icon.svg")); - vcsUpdateExistLabel.setText(Toolkit.i18nText("Fine-Design_Vcs_Can_Update")); - vcsUpdateFireLabel = new UILabel(Toolkit.i18nText("Fine-Design_Vcs_Update")); - vcsUpdateFireLabel.setForeground(LABEL_COLOR); - vcsUpdateFireLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); - parent.add(vcsUpdateExistLabel); - parent.add(vcsUpdateFireLabel); + if (!VcsHelper.getInstance().isLegacyMode()) { + centerButton = new UIButton(Toolkit.i18nText("Fine-Design_Vcs_Center")); + parent.add(centerButton); + initVcsCenterListener(); + } else { + parent.setBackground(BACK_GROUND_COLOR); + vcsUpdateExistLabel = new UILabel(IconUtils.readIcon("/com/fr/design/vcs/vcs_move_icon.svg")); + vcsUpdateExistLabel.setText(Toolkit.i18nText("Fine-Design_Vcs_Can_Update")); + vcsUpdateFireLabel = new UILabel(Toolkit.i18nText("Fine-Design_Vcs_Update")); + vcsUpdateFireLabel.setForeground(LABEL_COLOR); + vcsUpdateFireLabel.setCursor(new Cursor(Cursor.HAND_CURSOR)); + parent.add(vcsUpdateExistLabel); + parent.add(vcsUpdateFireLabel); + } + } + + private void initVcsCenterListener() { + centerButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + VcsCenterPane vcsCenterPane = new VcsCenterPane(); + BasicDialog dialog = vcsCenterPane.showWindow(DesignerContext.getDesignerFrame(), false); + dialog.setVisible(true); + + } + }); } private void initListener() { @@ -294,6 +318,10 @@ public class VcsMovePanel extends BasicPane { private void doAfterMove() { visible = !VcsHelper.getInstance().isLegacyMode(); + if (visible) { + updatePane = FRGUIPaneFactory.createBoxFlowInnerContainer_S_Pane(); + updatePane.add(new UIButton(Toolkit.i18nText("Fine-Design_Vcs_Center"))); + } updatePane.setVisible(!visible); callBack.doCallBack(visible); parentCard.show(parentPane, SETTING); diff --git a/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/VcsNewPane.java b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/VcsNewPane.java new file mode 100644 index 0000000000..0916062ccb --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/VcsNewPane.java @@ -0,0 +1,339 @@ +package com.fr.design.mainframe.vcs.ui; + +import com.fr.base.svg.IconUtils; +import com.fr.design.dialog.FineJOptionPane; +import com.fr.design.file.HistoryTemplateListCache; +import com.fr.design.file.MultiTemplateTabPane; +import com.fr.design.gui.ilable.UILabel; +import com.fr.design.i18n.Toolkit; +import com.fr.design.mainframe.DesignerContext; +import com.fr.design.mainframe.JTemplate; +import com.fr.design.mainframe.vcs.TableValueOperator; +import com.fr.design.mainframe.vcs.VcsService; +import com.fr.design.mainframe.vcs.VcsTableEntity; +import com.fr.design.mainframe.vcs.common.VcsCacheFileNodeFile; +import com.fr.design.mainframe.vcs.common.VcsHelper; +import com.fr.file.FileNodeFILE; +import com.fr.file.filetree.FileNode; +import com.fr.report.entity.VcsEntity; +import com.fr.stable.StringUtils; +import com.fr.workspace.WorkContext; +import com.fr.workspace.server.vcs.VcsOperator; + +import javax.swing.Icon; +import javax.swing.JComponent; +import javax.swing.JOptionPane; +import javax.swing.JTable; +import javax.swing.SwingWorker; +import java.awt.*; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseMotionAdapter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ExecutionException; + + +/** + * 版本管理交互优化新面板 + *
  • 采用表格的形式展现
  • + * + * @author Destiny.Lin + * @since 11.0 + * Created on 2023/7/11 + */ +public class VcsNewPane extends RecyclePane { + + private String filePath = StringUtils.EMPTY; + + protected static int OPERATOR_COL = 5; + + protected static int EDIT_COL = 4; + + + private static final int DOUBLE_CLICK_COUNT = 2; + protected VcsOperatorPane operatorPane; + private static final Icon PREVIEW_ICON = IconUtils.readIcon("/com/fr/design/standard/vcslist/vcs_operator_preview"); + private static final Icon DELETE_ICON = IconUtils.readIcon("/com/fr/design/standard/vcslist/vcs_operator_delete"); + private static final Icon RESTORE_ICON = IconUtils.readIcon("/com/fr/design/standard/vcslist/vcs_operator_restore"); + + private UILabel restore; + private UILabel delete; + private UILabel preview; + private static final String TITLE = Toolkit.i18nText("Fine-Design_Vcs_Title"); + private static final String[] COLUMN_NAMES = new String[]{ + StringUtils.EMPTY, + Toolkit.i18nText("Fine-Design_Vcs_Version"), + Toolkit.i18nText("Fine-Design_Vcs_Time"), + Toolkit.i18nText("Fine-Design_Vcs_User"), + Toolkit.i18nText("Fine-Design_Vcs_Annotation"), + Toolkit.i18nText("Fine-Design_Vcs_Operator") + + }; + + private static final Class[] CLASSES = new Class[]{ + Boolean.class, + UILabel.class, + UILabel.class, + UILabel.class, + UILabel.class, + VcsOperatorPane.class + }; + + + public VcsNewPane(String filePath) { + super(TITLE, (o, columnIndex) -> { + switch (columnIndex) { + case 0: + return o.isSelect(); + case 1: + return o.getVersion(); + case 2: + return o.getTime(); + case 3: + return o.getUserName(); + case 4: + return o.getCommitMsg(); + default: + return o; + } + }, false); + this.filePath = filePath; + initModel(COLUMN_NAMES, CLASSES, OPERATOR_COL); + } + + + private void initModel(String[] columns, Class[] classes, int operatorCol) { + this.model = new DefaultModel(columns, classes) { + @Override + public boolean isCellEditable(int row, int col) { + return col == 0 || col == operatorCol; + } + }; + this.operatorPane = createOperatorPane(); + this.model.setDefaultEditor(VcsOperatorPane.class, operatorPane); + this.model.setDefaultRenderer(VcsOperatorPane.class, operatorPane); + } + + + public VcsNewPane(String title, TableValueOperator operators, boolean needBorder, String[] columns, Class[] classes, int operatorCol) { + super(title, operators, needBorder); + initModel(columns, classes, operatorCol); + } + + /** + * 创建操作面板 + * + * @return 操作面板 + */ + public VcsOperatorPane createOperatorPane() { + restore = new UILabel(RESTORE_ICON); + delete = new UILabel(DELETE_ICON); + preview = new UILabel(PREVIEW_ICON); + initPreviewListener(); + initDeleteListener(); + initRestoreListener(); + List list = new ArrayList<>(); + list.add(restore); + list.add(delete); + list.add(preview); + return new VcsOperatorPane(list); + } + + private void initRestoreListener() { + restore.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + JTable table = tableContentPane.getEditTable(); + Object o = table.getValueAt(table.getEditingRow(), table.getEditingColumn()); + if (o instanceof VcsTableEntity) { + VcsEntity entity = ((VcsTableEntity) o).getEntity(); + int selVal = FineJOptionPane.showConfirmDialog( + VcsNewPane.this, + Toolkit.i18nText("Fine-Design_Vcs_Restore_This_Version_Tips"), + Toolkit.i18nText("Fine-Design_Basic_Confirm"), + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (selVal == JOptionPane.YES_OPTION) { + restoreEntity(entity); + } + } + } + }); + } + + + private void restoreEntity(VcsEntity entity) { + new SwingWorker() { + @Override + protected Void doInBackground() throws Exception { + //step1.设置还原的用户名 + entity.setUsername(VcsHelper.getInstance().getCurrentUsername()); + //step2.rollback到指定版本 + WorkContext.getCurrent().get(VcsOperator.class).rollbackTo(entity); + return null; + } + @Override + protected void done() { + //step3.如果原来原模板已经打开则关闭原模板,打开rollback后的新模板 + List> templateList = HistoryTemplateListCache.getInstance().getHistoryList(); + for (JTemplate template : templateList) { + if (StringUtils.equals(filePath, template.getPath())) { + MultiTemplateTabPane.getInstance().setIsCloseCurrent(HistoryTemplateListCache.getInstance().isCurrentEditingFile(filePath)); + MultiTemplateTabPane.getInstance().closeFormat(template); + MultiTemplateTabPane.getInstance().closeSpecifiedTemplate(template); + break; + } + } + DesignerContext.getDesignerFrame().openTemplate(new FileNodeFILE(new FileNode(filePath, false))); + } + }.execute(); + } + + + private void initDeleteListener() { + delete.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + JTable table = tableContentPane.getEditTable(); + int row = table.getEditingColumn(); + Object o = table.getValueAt(table.getEditingRow(), row); + if (o instanceof VcsTableEntity) { + VcsEntity entity = ((VcsTableEntity) o).getEntity(); + int selVal = FineJOptionPane.showConfirmDialog( + VcsNewPane.this, + Toolkit.i18nText("Fine-Design_Vcs_Delete_This_Version_Tips"), + Toolkit.i18nText("Fine-Design_Basic_Confirm"), + JOptionPane.OK_CANCEL_OPTION, + JOptionPane.QUESTION_MESSAGE); + if (selVal == JOptionPane.YES_OPTION) { + model.getList().remove(o); + model.fireTableDataChanged(); + VcsService.getInstance().deleteEntity(entity.getFilename(), entity.getVersion()); + } + } + } + }); + } + + private void initPreviewListener() { + preview.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + JTable table = tableContentPane.getEditTable(); + Object o = table.getValueAt(table.getEditingRow(), table.getEditingColumn()); + if (o instanceof VcsTableEntity) { + VcsEntity entity = ((VcsTableEntity) o).getEntity(); + previewEntity(entity); + } + } + }); + } + + private void previewEntity(VcsEntity entity) { + new SwingWorker() { + @Override + protected String doInBackground() throws Exception { + //step1.将指定版本的历史文件读取出来,加上前缀放到cache中 + VcsOperator vcsOperator = WorkContext.getCurrent().get(VcsOperator.class); + String fileOfVersion = vcsOperator.getFileOfFileVersion( + entity.getFilename(), + entity.getVersion(), + Toolkit.i18nText("Fine-Design_Vcs_Prefix", entity.getVersion())); + return fileOfVersion; + } + @Override + protected void done() { + try { + //step2.再打开cache中的模板 + DesignerContext.getDesignerFrame().openTemplate(new VcsCacheFileNodeFile(new FileNode(get(), false))); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + }.execute(); + } + + @Override + protected boolean isNeedDeleteAllVersion() { + return false; + } + + @Override + protected List getTableList() { + List entityList = WorkContext.getCurrent().get(VcsOperator.class).getVersions(VcsHelper.getInstance().dealWithFilePath(filePath)); + List tableEntities = new ArrayList<>(); + for (VcsEntity entity : entityList) { + tableEntities.add(new VcsTableEntity(entity)); + } + return tableEntities; + } + + @Override + protected String title4PopupWindow() { + return TITLE; + } + + @Override + protected String getDeleteTip(int size) { + return Toolkit.i18nText("Fine-Design_Vcs_Delete_Versions_Tips", size); + } + + @Override + protected boolean isNeedRestore() { + return false; + } + + @Override + protected boolean isNeedSearch() { + return false; + } + + @Override + protected void initExtraListener4Table(JTable table) { + table.addMouseMotionListener(new MouseMotionAdapter() { + @Override + public void mouseMoved(MouseEvent e) { + Point point = e.getPoint(); + int row = table.rowAtPoint(point); + int col = table.columnAtPoint(point); + if (col != 0) { + table.editCellAt(row, col); + } + } + }); + initEditListener(table); + } + + private void initEditListener(JTable table) { + table.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == DOUBLE_CLICK_COUNT) { + Point point = e.getPoint(); + int row = table.rowAtPoint(point); + int col = table.columnAtPoint(point); + if (col == EDIT_COL) { + Object o = table.getValueAt(row, OPERATOR_COL); + if (o instanceof VcsTableEntity) { + showDialog(((VcsTableEntity) o).getEntity()); + } + } + } + } + }); + } + + private void showDialog(VcsEntity entity) { + EditFileVersionDialog dialog = new EditFileVersionDialog(entity) { + @Override + public void doOK() { + entity.setCommitMsg(getMsgTestArea().getText()); + VcsService.getInstance().updateEntityAnnotation(entity); + setVisible(false); + model.fireTableDataChanged(); + } + }; + dialog.setVisible(true); + } +} diff --git a/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/VcsOperatorPane.java b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/VcsOperatorPane.java new file mode 100644 index 0000000000..9da20d8a54 --- /dev/null +++ b/designer-base/src/main/java/com/fr/design/mainframe/vcs/ui/VcsOperatorPane.java @@ -0,0 +1,63 @@ +package com.fr.design.mainframe.vcs.ui; + + +import com.fr.design.layout.FRGUIPaneFactory; + + +import javax.swing.AbstractCellEditor; + +import javax.swing.JComponent; +import javax.swing.JPanel; +import javax.swing.JTable; + +import javax.swing.table.TableCellEditor; +import javax.swing.table.TableCellRenderer; +import java.awt.*; +import java.util.List; + + +import static com.fr.design.mainframe.vcs.ui.AbstractSupportSelectTablePane.DEFAULT_SELECT_TABLE_ROW_COLOR; + +/** + * 操作面板,用于置放常用的操作label + *

    例如 删除、还原、预览、打开模板等

    + *
  • 负责表格渲染 与 操作按钮置蓝+设定cursor
  • + * + * @author Destiny.Lin + * @since 11.0 + * Created on 2023/7/13 + */ +public class VcsOperatorPane extends AbstractCellEditor implements TableCellEditor, TableCellRenderer { + private JPanel contentPane; + + private static final Color DETAIL_FONT_COLOR = new Color(65, 155, 249); + + public VcsOperatorPane(List iconJComponentMap) { + init(iconJComponentMap); + } + + private void init(List iconJComponentMap) { + contentPane = new JPanel(FRGUIPaneFactory.createLeftZeroVgapNormalHgapLayout()); + for (JComponent value : iconJComponentMap) { + value.setForeground(DETAIL_FONT_COLOR); + value.setCursor(new Cursor(Cursor.HAND_CURSOR)); + contentPane.add(value); + } + } + + @Override + public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) { + return contentPane; + } + + @Override + public Object getCellEditorValue() { + return contentPane; + } + + @Override + public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { + contentPane.setBackground(isSelected ? DEFAULT_SELECT_TABLE_ROW_COLOR : Color.WHITE); + return contentPane; + } +} diff --git a/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_center_manager_normal.svg b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_center_manager_normal.svg new file mode 100644 index 0000000000..8208a290c2 --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_center_manager_normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_center_open_normal.svg b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_center_open_normal.svg new file mode 100644 index 0000000000..7a3ed4b330 --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_center_open_normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_operator_delete_normal.svg b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_operator_delete_normal.svg new file mode 100644 index 0000000000..9ae3274c7e --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_operator_delete_normal.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_operator_preview_normal.svg b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_operator_preview_normal.svg new file mode 100644 index 0000000000..79d9883fd0 --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_operator_preview_normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_operator_restore_normal.svg b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_operator_restore_normal.svg new file mode 100644 index 0000000000..60cf06e303 --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_operator_restore_normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_delete_disabled.svg b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_delete_disabled.svg new file mode 100644 index 0000000000..1a96116130 --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_delete_disabled.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_delete_normal.svg b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_delete_normal.svg new file mode 100644 index 0000000000..730e3493da --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_delete_normal.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_normal.svg b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_normal.svg new file mode 100644 index 0000000000..ab3b97e703 --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_normal.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_restore_disabled.svg b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_restore_disabled.svg new file mode 100644 index 0000000000..54afcb1273 --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_restore_disabled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_restore_normal.svg b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_restore_normal.svg new file mode 100644 index 0000000000..f1b4b30359 --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_restore_normal.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_search_normal.svg b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_search_normal.svg new file mode 100644 index 0000000000..a557ec1aae --- /dev/null +++ b/designer-base/src/main/resources/com/fr/design/standard/vcslist/vcs_recycle_search_normal.svg @@ -0,0 +1,3 @@ + + +