]> source.dussan.org Git - gitblit.git/commitdiff
Merge remote-tracking branch 'collapsible/ticket/527' into 725_nested_repos
authorMartin Spielmann <mail@martinspielmann.de>
Sat, 4 Nov 2017 12:19:08 +0000 (13:19 +0100)
committerMartin Spielmann <mail@martinspielmann.de>
Sat, 4 Nov 2017 12:19:08 +0000 (13:19 +0100)
1  2 
src/main/java/com/gitblit/wicket/panels/RepositoriesPanel.java
src/main/java/com/gitblit/wicket/panels/TreeNodeModel.java
src/test/java/com/gitblit/wicket/panels/TreeNodeModelTest.java

index 804fb299ea487c933143b3c7c83b8fa23ab9368e,aab602e5c75d847ef466a4e82f0bacf9a82a072a..6d68022946be731c94185073238c2ca2128ea8c4
@@@ -59,406 -57,419 +59,444 @@@ import com.gitblit.wicket.pages.UserPag
  \r
  public class RepositoriesPanel extends BasePanel {\r
  \r
 -      private static final long serialVersionUID = 1L;\r
 -      \r
 -      private enum CollapsibleRepositorySetting {\r
 -              DISABLED,\r
 -\r
 -              EXPANDED,\r
 -\r
 -              COLLAPSED;\r
 -\r
 -              public static CollapsibleRepositorySetting get(String name) {\r
 -                      CollapsibleRepositorySetting returnVal = CollapsibleRepositorySetting.DISABLED;\r
 -                      for (CollapsibleRepositorySetting setting : values()) {\r
 -                              if (setting.name().equalsIgnoreCase(name)) {\r
 -                                      returnVal = setting;\r
 -                                      break;\r
 -                              }\r
 -                      }\r
 -                      return returnVal;\r
 -              }\r
 -      }\r
 -\r
 -      public RepositoriesPanel(String wicketId, final boolean showAdmin, final boolean showManagement,\r
 -                      List<RepositoryModel> models, boolean enableLinks,\r
 -                      final Map<AccessRestrictionType, String> accessRestrictionTranslations) {\r
 -              super(wicketId);\r
 -\r
 -              final boolean linksActive = enableLinks;\r
 -              final boolean showSize = app().settings().getBoolean(Keys.web.showRepositorySizes, true);\r
 -              final String collapsibleRespositorySetting = app().settings().getString(Keys.web.collapsibleRepositoryGroups, null);\r
 -              final CollapsibleRepositorySetting collapsibleRepoGroups = CollapsibleRepositorySetting.get(collapsibleRespositorySetting);\r
 -\r
 -              final UserModel user = GitBlitWebSession.get().getUser();\r
 -\r
 -              final IDataProvider<RepositoryModel> dp;\r
 -\r
 -              Fragment managementLinks;\r
 -              if (showAdmin) {\r
 -                      // user is admin\r
 -                      managementLinks = new Fragment("managementPanel", "adminLinks", this);\r
 -                      managementLinks.add(new Link<Void>("clearCache") {\r
 -\r
 -                              private static final long serialVersionUID = 1L;\r
 -\r
 -                              @Override\r
 -                              public void onClick() {\r
 -                                      app().repositories().resetRepositoryListCache();\r
 -                                      setResponsePage(RepositoriesPage.class);\r
 -                              }\r
 -                      }.setVisible(app().settings().getBoolean(Keys.git.cacheRepositoryList, true)));\r
 -                      managementLinks.add(new BookmarkablePageLink<Void>("newRepository", app().getNewRepositoryPage()));\r
 -                      add(managementLinks);\r
 -              } else if (showManagement && user != null && user.canCreate()) {\r
 -                      // user can create personal repositories\r
 -                      managementLinks = new Fragment("managementPanel", "personalLinks", this);\r
 -                      managementLinks.add(new BookmarkablePageLink<Void>("newRepository", app().getNewRepositoryPage()));\r
 -                      add(managementLinks);\r
 -              } else {\r
 -                      // user has no management permissions\r
 -                      add (new Label("managementPanel").setVisible(false));\r
 -              }\r
 -\r
 -              if (app().settings().getString(Keys.web.repositoryListType, "flat").equalsIgnoreCase("grouped")) {\r
 -                      List<RepositoryModel> rootRepositories = new ArrayList<RepositoryModel>();\r
 -                      Map<String, List<RepositoryModel>> groups = new HashMap<String, List<RepositoryModel>>();\r
 -                      for (RepositoryModel model : models) {\r
 -                              String rootPath = StringUtils.getRootPath(model.name);\r
 -                              if (StringUtils.isEmpty(rootPath)) {\r
 -                                      // root repository\r
 -                                      rootRepositories.add(model);\r
 -                              } else {\r
 -                                      // non-root, grouped repository\r
 -                                      if (!groups.containsKey(rootPath)) {\r
 -                                              groups.put(rootPath, new ArrayList<RepositoryModel>());\r
 -                                      }\r
 -                                      groups.get(rootPath).add(model);\r
 -                              }\r
 -                      }\r
 -                      List<String> roots = new ArrayList<String>(groups.keySet());\r
 -                      Collections.sort(roots);\r
 -\r
 -                      if (rootRepositories.size() > 0) {\r
 -                              // inject the root repositories at the top of the page\r
 -                              roots.add(0, "");\r
 -                              groups.put("", rootRepositories);\r
 -                      }\r
 -\r
 -                      List<RepositoryModel> groupedModels = new ArrayList<RepositoryModel>();\r
 -                      for (String root : roots) {\r
 -                              List<RepositoryModel> subModels = groups.get(root);\r
 -                              ProjectModel project = app().projects().getProjectModel(root);\r
 -                              GroupRepositoryModel group = new GroupRepositoryModel(project == null ? root : project.name, subModels.size());\r
 -                              if (project != null) {\r
 -                                      group.title = project.title;\r
 -                                      group.description = project.description;\r
 -                              }\r
 -                              groupedModels.add(group);\r
 -                              Collections.sort(subModels);\r
 -                              groupedModels.addAll(subModels);\r
 -                      }\r
 -                      dp = new ListDataProvider<RepositoryModel>(groupedModels);\r
 -              } else {\r
 -                      dp = new SortableRepositoriesProvider(models);\r
 -              }\r
 -\r
 -              final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true);\r
 -\r
 -              DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("row", dp) {\r
 -                      private static final long serialVersionUID = 1L;\r
 -                      int counter;\r
 -                      String currGroupName;\r
 -\r
 -                      @Override\r
 -                      protected void onBeforeRender() {\r
 -                              super.onBeforeRender();\r
 -                              counter = 0;\r
 -                      }\r
 -\r
 -                      @Override\r
 -                      public void populateItem(final Item<RepositoryModel> item) {\r
 -                              final RepositoryModel entry = item.getModelObject();\r
 -                              if (entry instanceof GroupRepositoryModel) {\r
 -                                      GroupRepositoryModel groupRow = (GroupRepositoryModel) entry;\r
 -                                      currGroupName = entry.name;\r
 -                                      Fragment row = new Fragment("rowContent", "groupRepositoryRow", this);\r
 -                                      if(collapsibleRepoGroups == CollapsibleRepositorySetting.EXPANDED) {\r
 -                                              Fragment groupCollapsible = new Fragment("groupCollapsible", "tableGroupMinusCollapsible", this);\r
 -                                              row.add(groupCollapsible);\r
 -                                      } else if(collapsibleRepoGroups == CollapsibleRepositorySetting.COLLAPSED) {\r
 -                                              Fragment groupCollapsible = new Fragment("groupCollapsible", "tableGroupPlusCollapsible", this);\r
 -                                              row.add(groupCollapsible);\r
 -                                      } else {\r
 -                                              Fragment groupCollapsible = new Fragment("groupCollapsible", "emptyFragment", this);\r
 -                                              row.add(groupCollapsible);\r
 -                                      }\r
 -                                      item.add(row);\r
 -\r
 -                                      String name = groupRow.name;\r
 -                                      if (name.startsWith(ModelUtils.getUserRepoPrefix())) {\r
 -                                              // user page\r
 -                                              String username = ModelUtils.getUserNameFromRepoPath(name);\r
 -                                              UserModel user = app().users().getUserModel(username);\r
 -                                              row.add(new LinkPanel("groupName", null, (user == null ? username : user.getDisplayName()) + " (" + groupRow.count + ")", UserPage.class, WicketUtils.newUsernameParameter(username)));\r
 -                                              row.add(new Label("groupDescription", getString("gb.personalRepositories")));\r
 -                                      } else {\r
 -                                              // project page\r
 -                                              row.add(new LinkPanel("groupName", null, groupRow.toString(), ProjectPage.class, WicketUtils.newProjectParameter(entry.name)));\r
 -                                              row.add(new Label("groupDescription", entry.description == null ? "":entry.description));\r
 -                                      }\r
 -                                      WicketUtils.setCssClass(item, "group collapsible");\r
 -                                      // reset counter so that first row is light background\r
 -                                      counter = 0;\r
 -                                      return;\r
 -                              }\r
 -                              Fragment row = new Fragment("rowContent", "repositoryRow", this);\r
 -                              item.add(row);\r
 -\r
 -                              // show colored repository type icon\r
 -                              Fragment iconFragment;\r
 -                              if (entry.isMirror) {\r
 -                                      iconFragment = new Fragment("repoIcon", "mirrorIconFragment", this);\r
 -                              } else if (entry.isFork()) {\r
 -                                      iconFragment = new Fragment("repoIcon", "forkIconFragment", this);\r
 -                              } else if (entry.isBare) {\r
 -                                      iconFragment = new Fragment("repoIcon", "repoIconFragment", this);\r
 -                              } else {\r
 -                                      iconFragment = new Fragment("repoIcon", "cloneIconFragment", this);\r
 -                              }\r
 -                              if (showSwatch) {\r
 -                                      WicketUtils.setCssStyle(iconFragment, "color:" + StringUtils.getColor(entry.toString()));\r
 -                              }\r
 -                              row.add(iconFragment);\r
 -\r
 -                              // try to strip group name for less cluttered list\r
 -                              String repoName = entry.toString();\r
 -                              if (!StringUtils.isEmpty(currGroupName) && (repoName.indexOf('/') > -1)) {\r
 -                                      repoName = repoName.substring(currGroupName.length() + 1);\r
 -                              }\r
 -\r
 -                              if (linksActive) {\r
 -                                      Class<? extends BasePage> linkPage = SummaryPage.class;\r
 -                                      PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);\r
 -                                      row.add(new LinkPanel("repositoryName", "list", repoName, linkPage, pp));\r
 -                                      row.add(new LinkPanel("repositoryDescription", "list", entry.description,\r
 -                                                      linkPage, pp));\r
 -                              } else {\r
 -                                      // no links like on a federation page\r
 -                                      row.add(new Label("repositoryName", repoName));\r
 -                                      row.add(new Label("repositoryDescription", entry.description));\r
 -                              }\r
 -                              if (entry.hasCommits) {\r
 -                                      // Existing repository\r
 -                                      row.add(new Label("repositorySize", entry.size).setVisible(showSize));\r
 -                              } else {\r
 -                                      // New repository\r
 -                                      row.add(new Label("repositorySize", "<span class='empty'>(" + getString("gb.empty") + ")</span>")\r
 -                                                      .setEscapeModelStrings(false));\r
 -                              }\r
 -\r
 -                              if (entry.isSparkleshared()) {\r
 -                                      row.add(WicketUtils.newImage("sparkleshareIcon", "star_16x16.png",\r
 -                                                      getString("gb.isSparkleshared")));\r
 -                              } else {\r
 -                                      row.add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));\r
 -                              }\r
 -\r
 -                              if (!entry.isMirror && entry.isFrozen) {\r
 -                                      row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png",\r
 -                                                      getString("gb.isFrozen")));\r
 -                              } else {\r
 -                                      row.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));\r
 -                              }\r
 -\r
 -                              if (entry.isFederated) {\r
 -                                      row.add(WicketUtils.newImage("federatedIcon", "federated_16x16.png",\r
 -                                                      getString("gb.isFederated")));\r
 -                              } else {\r
 -                                      row.add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));\r
 -                              }\r
 -\r
 -                              if (entry.isMirror) {\r
 -                                      row.add(WicketUtils.newImage("accessRestrictionIcon", "mirror_16x16.png",\r
 -                                                      getString("gb.isMirror")));\r
 -                              } else {\r
 -                                      switch (entry.accessRestriction) {\r
 -                                      case NONE:\r
 -                                              row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));\r
 -                                              break;\r
 -                                      case PUSH:\r
 -                                              row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",\r
 -                                                              accessRestrictionTranslations.get(entry.accessRestriction)));\r
 -                                              break;\r
 -                                      case CLONE:\r
 -                                              row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",\r
 -                                                              accessRestrictionTranslations.get(entry.accessRestriction)));\r
 -                                              break;\r
 -                                      case VIEW:\r
 -                                              row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",\r
 -                                                              accessRestrictionTranslations.get(entry.accessRestriction)));\r
 -                                              break;\r
 -                                      default:\r
 -                                              row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));\r
 -                                      }\r
 -                              }\r
 -\r
 -                              String owner = "";\r
 -                              if (!ArrayUtils.isEmpty(entry.owners)) {\r
 -                                      // display first owner\r
 -                                      for (String username : entry.owners) {\r
 -                                              UserModel ownerModel = app().users().getUserModel(username);\r
 -                                              if (ownerModel != null) {\r
 -                                                      owner = ownerModel.getDisplayName();\r
 -                                                      break;\r
 -                                              }\r
 -                                      }\r
 -                                      if (entry.owners.size() > 1) {\r
 -                                              owner += ", ...";\r
 -                                      }\r
 -                              }\r
 -                              Label ownerLabel = new Label("repositoryOwner", owner);\r
 -                              WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners));\r
 -                              row.add(ownerLabel);\r
 -\r
 -                              String lastChange;\r
 -                              if (entry.lastChange.getTime() == 0) {\r
 -                                      lastChange = "--";\r
 -                              } else {\r
 -                                      lastChange = getTimeUtils().timeAgo(entry.lastChange);\r
 -                              }\r
 -                              Label lastChangeLabel = new Label("repositoryLastChange", lastChange);\r
 -                              row.add(lastChangeLabel);\r
 -                              WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange));\r
 -                              if (!StringUtils.isEmpty(entry.lastChangeAuthor)) {\r
 -                                      WicketUtils.setHtmlTooltip(lastChangeLabel, getString("gb.author") + ": " + entry.lastChangeAuthor);\r
 -                              }\r
 -\r
 -                              WicketUtils.setAlternatingBackground(item, counter);\r
 -                              counter++;\r
 -                      }\r
 -              };\r
 -              add(dataView);\r
 -\r
 -              if (dp instanceof SortableDataProvider<?>) {\r
 -                      // add sortable header\r
 -                      SortableDataProvider<?> sdp = (SortableDataProvider<?>) dp;\r
 -                      Fragment fragment = new Fragment("headerContent", "flatRepositoryHeader", this);\r
 -                      fragment.add(newSort("orderByRepository", SortBy.repository, sdp, dataView));\r
 -                      fragment.add(newSort("orderByDescription", SortBy.description, sdp, dataView));\r
 -                      fragment.add(newSort("orderByOwner", SortBy.owner, sdp, dataView));\r
 -                      fragment.add(newSort("orderByDate", SortBy.date, sdp, dataView));\r
 -                      add(fragment);\r
 -              } else {\r
 -                      // not sortable\r
 -                      Fragment fragment = new Fragment("headerContent", "groupRepositoryHeader", this);\r
 -                      if(collapsibleRepoGroups == CollapsibleRepositorySetting.EXPANDED || \r
 -                                      collapsibleRepoGroups == CollapsibleRepositorySetting.COLLAPSED) {\r
 -                              Fragment allCollapsible = new Fragment("allCollapsible", "tableAllCollapsible", this);\r
 -                              fragment.add(allCollapsible);\r
 -                      } else {\r
 -                              Fragment allCollapsible = new Fragment("allCollapsible", "emptyFragment", this);\r
 -                              fragment.add(allCollapsible);\r
 -                      }\r
 -                      add(fragment);\r
 -              }\r
 -      }\r
 -\r
 -      private static class GroupRepositoryModel extends RepositoryModel {\r
 -\r
 -              private static final long serialVersionUID = 1L;\r
 -\r
 -              int count;\r
 -              String title;\r
 -\r
 -              GroupRepositoryModel(String name, int count) {\r
 -                      super(name, "", "", new Date(0));\r
 -                      this.count = count;\r
 -              }\r
 -\r
 -              @Override\r
 -              public String toString() {\r
 -                      return (StringUtils.isEmpty(title) ? name  : title) + " (" + count + ")";\r
 -              }\r
 -      }\r
 -\r
 -      protected enum SortBy {\r
 -              repository, description, owner, date;\r
 -      }\r
 -\r
 -      protected OrderByBorder newSort(String wicketId, SortBy field, SortableDataProvider<?> dp,\r
 -                      final DataView<?> dataView) {\r
 -              return new OrderByBorder(wicketId, field.name(), dp) {\r
 -                      private static final long serialVersionUID = 1L;\r
 -\r
 -                      @Override\r
 -                      protected void onSortChanged() {\r
 -                              dataView.setCurrentPage(0);\r
 -                      }\r
 -              };\r
 -      }\r
 -\r
 -      private static class SortableRepositoriesProvider extends SortableDataProvider<RepositoryModel> {\r
 -\r
 -              private static final long serialVersionUID = 1L;\r
 -\r
 -              private List<RepositoryModel> list;\r
 -\r
 -              protected SortableRepositoriesProvider(List<RepositoryModel> list) {\r
 -                      this.list = list;\r
 -                      setSort(SortBy.date.name(), false);\r
 -              }\r
 -\r
 -              @Override\r
 -              public int size() {\r
 -                      if (list == null) {\r
 -                              return 0;\r
 -                      }\r
 -                      return list.size();\r
 -              }\r
 -\r
 -              @Override\r
 -              public IModel<RepositoryModel> model(RepositoryModel header) {\r
 -                      return new Model<RepositoryModel>(header);\r
 -              }\r
 -\r
 -              @Override\r
 -              public Iterator<RepositoryModel> iterator(int first, int count) {\r
 -                      SortParam sp = getSort();\r
 -                      String prop = sp.getProperty();\r
 -                      final boolean asc = sp.isAscending();\r
 -\r
 -                      if (prop == null || prop.equals(SortBy.date.name())) {\r
 -                              Collections.sort(list, new Comparator<RepositoryModel>() {\r
 -                                      @Override\r
 -                                      public int compare(RepositoryModel o1, RepositoryModel o2) {\r
 -                                              if (asc) {\r
 -                                                      return o1.lastChange.compareTo(o2.lastChange);\r
 -                                              }\r
 -                                              return o2.lastChange.compareTo(o1.lastChange);\r
 -                                      }\r
 -                              });\r
 -                      } else if (prop.equals(SortBy.repository.name())) {\r
 -                              Collections.sort(list, new Comparator<RepositoryModel>() {\r
 -                                      @Override\r
 -                                      public int compare(RepositoryModel o1, RepositoryModel o2) {\r
 -                                              if (asc) {\r
 -                                                      return o1.name.compareTo(o2.name);\r
 -                                              }\r
 -                                              return o2.name.compareTo(o1.name);\r
 -                                      }\r
 -                              });\r
 -                      } else if (prop.equals(SortBy.owner.name())) {\r
 -                              Collections.sort(list, new Comparator<RepositoryModel>() {\r
 -                                      @Override\r
 -                                      public int compare(RepositoryModel o1, RepositoryModel o2) {\r
 -                                              String own1 = ArrayUtils.toString(o1.owners);\r
 -                                              String own2 = ArrayUtils.toString(o2.owners);\r
 -                                              if (asc) {\r
 -                                                      return own1.compareTo(own2);\r
 -                                              }\r
 -                                              return own2.compareTo(own1);\r
 -                                      }\r
 -                              });\r
 -                      } else if (prop.equals(SortBy.description.name())) {\r
 -                              Collections.sort(list, new Comparator<RepositoryModel>() {\r
 -                                      @Override\r
 -                                      public int compare(RepositoryModel o1, RepositoryModel o2) {\r
 -                                              if (asc) {\r
 -                                                      return o1.description.compareTo(o2.description);\r
 -                                              }\r
 -                                              return o2.description.compareTo(o1.description);\r
 -                                      }\r
 -                              });\r
 -                      }\r
 -                      return list.subList(first, first + count).iterator();\r
 -              }\r
 -      }\r
 +    private static final long serialVersionUID = 1L;\r
 +\r
++    private enum CollapsibleRepositorySetting {\r
++        DISABLED,\r
++\r
++        EXPANDED,\r
++\r
++        COLLAPSED;\r
++\r
++        public static CollapsibleRepositorySetting get(String name) {\r
++            CollapsibleRepositorySetting returnVal = CollapsibleRepositorySetting.DISABLED;\r
++            for (CollapsibleRepositorySetting setting : values()) {\r
++                if (setting.name().equalsIgnoreCase(name)) {\r
++                    returnVal = setting;\r
++                    break;\r
++                }\r
++            }\r
++            return returnVal;\r
++        }\r
++    }\r
 +    public RepositoriesPanel(String wicketId, final boolean showAdmin, final boolean showManagement, List<RepositoryModel> models, boolean enableLinks,\r
 +            final Map<AccessRestrictionType, String> accessRestrictionTranslations) {\r
 +        super(wicketId);\r
 +\r
 +        final boolean linksActive = enableLinks;\r
 +        final boolean showSize = app().settings().getBoolean(Keys.web.showRepositorySizes, true);\r
++        final String collapsibleRespositorySetting = app().settings().getString(Keys.web.collapsibleRepositoryGroups, null);\r
++        final CollapsibleRepositorySetting collapsibleRepoGroups = CollapsibleRepositorySetting.get(collapsibleRespositorySetting);\r
 +\r
 +        final UserModel user = GitBlitWebSession.get().getUser();\r
 +\r
 +        IDataProvider<RepositoryModel> dp = null;\r
 +\r
 +        Fragment managementLinks;\r
 +        if (showAdmin) {\r
 +            // user is admin\r
 +            managementLinks = new Fragment("managementPanel", "adminLinks", this);\r
 +            managementLinks.add(new Link<Void>("clearCache") {\r
 +\r
 +                private static final long serialVersionUID = 1L;\r
 +\r
 +                @Override\r
 +                public void onClick() {\r
 +                    app().repositories().resetRepositoryListCache();\r
 +                    setResponsePage(RepositoriesPage.class);\r
 +                }\r
 +            }.setVisible(app().settings().getBoolean(Keys.git.cacheRepositoryList, true)));\r
 +            managementLinks.add(new BookmarkablePageLink<Void>("newRepository", app().getNewRepositoryPage()));\r
 +            add(managementLinks);\r
 +        } else if (showManagement && user != null && user.canCreate()) {\r
 +            // user can create personal repositories\r
 +            managementLinks = new Fragment("managementPanel", "personalLinks", this);\r
 +            managementLinks.add(new BookmarkablePageLink<Void>("newRepository", app().getNewRepositoryPage()));\r
 +            add(managementLinks);\r
 +        } else {\r
 +            // user has no management permissions\r
 +            add(new Label("managementPanel").setVisible(false));\r
 +        }\r
 +\r
 +        if (true) {\r
 +            // if (app().settings().getString(Keys.web.repositoryListType,\r
 +            // "flat").equalsIgnoreCase("tree")) {\r
 +            TreeNodeModel tree = new TreeNodeModel();\r
 +            for (RepositoryModel model : models) {\r
 +                String rootPath = StringUtils.getRootPath(model.name);\r
 +                if (StringUtils.isEmpty(rootPath)) {\r
 +                    // root repository\r
 +                    // rootRepositories.add(model);\r
 +                    tree.add(model);\r
 +                } else {\r
 +                    // create folder structure\r
 +                    tree.add(rootPath, model);\r
 +                    // non-root, grouped repository\r
 +                    // if (!groups.containsKey(rootPath)) {\r
 +                    // groups.put(rootPath, new ArrayList<RepositoryModel>());\r
 +                    // }\r
 +                    // groups.get(rootPath).add(model);\r
 +                }\r
 +            }\r
 +\r
 +            WebMarkupContainer row = new WebMarkupContainer("row");\r
 +            add(row);\r
 +            row.add(new MultiLineLabel("rowContent", tree.toString()));\r
 +\r
 +\r
 +            Fragment fragment = new Fragment("headerContent", "groupRepositoryHeader", this);\r
 +            add(fragment);\r
 +\r
 +\r
 +\r
 +\r
 +        } else if (app().settings().getString(Keys.web.repositoryListType, "flat").equalsIgnoreCase("grouped")) {\r
 +            List<RepositoryModel> rootRepositories = new ArrayList<RepositoryModel>();\r
 +            Map<String, List<RepositoryModel>> groups = new HashMap<String, List<RepositoryModel>>();\r
 +            for (RepositoryModel model : models) {\r
 +                String rootPath = StringUtils.getRootPath(model.name);\r
 +                if (StringUtils.isEmpty(rootPath)) {\r
 +                    // root repository\r
 +                    rootRepositories.add(model);\r
 +                } else {\r
 +                    // non-root, grouped repository\r
 +                    if (!groups.containsKey(rootPath)) {\r
 +                        groups.put(rootPath, new ArrayList<RepositoryModel>());\r
 +                    }\r
 +                    groups.get(rootPath).add(model);\r
 +                }\r
 +            }\r
 +            List<String> roots = new ArrayList<String>(groups.keySet());\r
 +            Collections.sort(roots);\r
 +\r
 +            if (rootRepositories.size() > 0) {\r
 +                // inject the root repositories at the top of the page\r
 +                roots.add(0, "");\r
 +                groups.put("", rootRepositories);\r
 +            }\r
 +\r
 +            List<RepositoryModel> groupedModels = new ArrayList<RepositoryModel>();\r
 +            for (String root : roots) {\r
 +                List<RepositoryModel> subModels = groups.get(root);\r
 +                ProjectModel project = app().projects().getProjectModel(root);\r
 +                GroupRepositoryModel group = new GroupRepositoryModel(project == null ? root : project.name, subModels.size());\r
 +                if (project != null) {\r
 +                    group.title = project.title;\r
 +                    group.description = project.description;\r
 +                }\r
 +                groupedModels.add(group);\r
 +                Collections.sort(subModels);\r
 +                groupedModels.addAll(subModels);\r
 +            }\r
 +            dp = new ListDataProvider<RepositoryModel>(groupedModels);\r
 +        } else {\r
 +            dp = new SortableRepositoriesProvider(models);\r
 +        }\r
 +\r
 +        if (dp != null) {\r
 +            final boolean showSwatch = app().settings().getBoolean(Keys.web.repositoryListSwatches, true);\r
 +\r
 +            DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("row", dp) {\r
 +                private static final long serialVersionUID = 1L;\r
 +                int counter;\r
 +                String currGroupName;\r
 +\r
 +                @Override\r
 +                protected void onBeforeRender() {\r
 +                    super.onBeforeRender();\r
 +                    counter = 0;\r
 +                }\r
 +\r
 +                @Override\r
 +                public void populateItem(final Item<RepositoryModel> item) {\r
 +                    final RepositoryModel entry = item.getModelObject();\r
 +                    if (entry instanceof GroupRepositoryModel) {\r
 +                        GroupRepositoryModel groupRow = (GroupRepositoryModel) entry;\r
 +                        currGroupName = entry.name;\r
 +                        Fragment row = new Fragment("rowContent", "groupRepositoryRow", this);\r
++                        if(collapsibleRepoGroups == CollapsibleRepositorySetting.EXPANDED) {\r
++                            Fragment groupCollapsible = new Fragment("groupCollapsible", "tableGroupMinusCollapsible", this);\r
++                            row.add(groupCollapsible);\r
++                        } else if(collapsibleRepoGroups == CollapsibleRepositorySetting.COLLAPSED) {\r
++                            Fragment groupCollapsible = new Fragment("groupCollapsible", "tableGroupPlusCollapsible", this);\r
++                            row.add(groupCollapsible);\r
++                        } else {\r
++                            Fragment groupCollapsible = new Fragment("groupCollapsible", "emptyFragment", this);\r
++                            row.add(groupCollapsible);\r
++                        }\r
 +                        item.add(row);\r
 +\r
 +                        String name = groupRow.name;\r
 +                        if (name.startsWith(ModelUtils.getUserRepoPrefix())) {\r
 +                            // user page\r
 +                            String username = ModelUtils.getUserNameFromRepoPath(name);\r
 +                            UserModel user = app().users().getUserModel(username);\r
 +                            row.add(new LinkPanel("groupName", null, (user == null ? username : user.getDisplayName()) + " (" + groupRow.count + ")", UserPage.class,\r
 +                                    WicketUtils.newUsernameParameter(username)));\r
 +                            row.add(new Label("groupDescription", getString("gb.personalRepositories")));\r
 +                        } else {\r
 +                            // project page\r
 +                            row.add(new LinkPanel("groupName", null, groupRow.toString(), ProjectPage.class, WicketUtils.newProjectParameter(entry.name)));\r
 +                            row.add(new Label("groupDescription", entry.description == null ? "" : entry.description));\r
 +                        }\r
-                         WicketUtils.setCssClass(item, "group");\r
++                        WicketUtils.setCssClass(item, "group collapsible");\r
 +                        // reset counter so that first row is light background\r
 +                        counter = 0;\r
 +                        return;\r
 +                    }\r
 +                    Fragment row = new Fragment("rowContent", "repositoryRow", this);\r
 +                    item.add(row);\r
 +\r
 +                    // show colored repository type icon\r
 +                    Fragment iconFragment;\r
 +                    if (entry.isMirror) {\r
 +                        iconFragment = new Fragment("repoIcon", "mirrorIconFragment", this);\r
 +                    } else if (entry.isFork()) {\r
 +                        iconFragment = new Fragment("repoIcon", "forkIconFragment", this);\r
 +                    } else if (entry.isBare) {\r
 +                        iconFragment = new Fragment("repoIcon", "repoIconFragment", this);\r
 +                    } else {\r
 +                        iconFragment = new Fragment("repoIcon", "cloneIconFragment", this);\r
 +                    }\r
 +                    if (showSwatch) {\r
 +                        WicketUtils.setCssStyle(iconFragment, "color:" + StringUtils.getColor(entry.toString()));\r
 +                    }\r
 +                    row.add(iconFragment);\r
 +\r
 +                    // try to strip group name for less cluttered list\r
 +                    String repoName = entry.toString();\r
 +                    if (!StringUtils.isEmpty(currGroupName) && (repoName.indexOf('/') > -1)) {\r
 +                        repoName = repoName.substring(currGroupName.length() + 1);\r
 +                    }\r
 +\r
 +                    if (linksActive) {\r
 +                        Class<? extends BasePage> linkPage = SummaryPage.class;\r
 +                        PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);\r
 +                        row.add(new LinkPanel("repositoryName", "list", repoName, linkPage, pp));\r
 +                        row.add(new LinkPanel("repositoryDescription", "list", entry.description, linkPage, pp));\r
 +                    } else {\r
 +                        // no links like on a federation page\r
 +                        row.add(new Label("repositoryName", repoName));\r
 +                        row.add(new Label("repositoryDescription", entry.description));\r
 +                    }\r
 +                    if (entry.hasCommits) {\r
 +                        // Existing repository\r
 +                        row.add(new Label("repositorySize", entry.size).setVisible(showSize));\r
 +                    } else {\r
 +                        // New repository\r
 +                        row.add(new Label("repositorySize", "<span class='empty'>(" + getString("gb.empty") + ")</span>").setEscapeModelStrings(false));\r
 +                    }\r
 +\r
 +                    if (entry.isSparkleshared()) {\r
 +                        row.add(WicketUtils.newImage("sparkleshareIcon", "star_16x16.png", getString("gb.isSparkleshared")));\r
 +                    } else {\r
 +                        row.add(WicketUtils.newClearPixel("sparkleshareIcon").setVisible(false));\r
 +                    }\r
 +\r
 +                    if (!entry.isMirror && entry.isFrozen) {\r
 +                        row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png", getString("gb.isFrozen")));\r
 +                    } else {\r
 +                        row.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));\r
 +                    }\r
 +\r
 +                    if (entry.isFederated) {\r
 +                        row.add(WicketUtils.newImage("federatedIcon", "federated_16x16.png", getString("gb.isFederated")));\r
 +                    } else {\r
 +                        row.add(WicketUtils.newClearPixel("federatedIcon").setVisible(false));\r
 +                    }\r
 +\r
 +                    if (entry.isMirror) {\r
 +                        row.add(WicketUtils.newImage("accessRestrictionIcon", "mirror_16x16.png", getString("gb.isMirror")));\r
 +                    } else {\r
 +                        switch (entry.accessRestriction) {\r
 +                        case NONE:\r
 +                            row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));\r
 +                            break;\r
 +                        case PUSH:\r
 +                            row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));\r
 +                            break;\r
 +                        case CLONE:\r
 +                            row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));\r
 +                            break;\r
 +                        case VIEW:\r
 +                            row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png", accessRestrictionTranslations.get(entry.accessRestriction)));\r
 +                            break;\r
 +                        default:\r
 +                            row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));\r
 +                        }\r
 +                    }\r
 +\r
 +                    String owner = "";\r
 +                    if (!ArrayUtils.isEmpty(entry.owners)) {\r
 +                        // display first owner\r
 +                        for (String username : entry.owners) {\r
 +                            UserModel ownerModel = app().users().getUserModel(username);\r
 +                            if (ownerModel != null) {\r
 +                                owner = ownerModel.getDisplayName();\r
 +                                break;\r
 +                            }\r
 +                        }\r
 +                        if (entry.owners.size() > 1) {\r
 +                            owner += ", ...";\r
 +                        }\r
 +                    }\r
 +                    Label ownerLabel = new Label("repositoryOwner", owner);\r
 +                    WicketUtils.setHtmlTooltip(ownerLabel, ArrayUtils.toString(entry.owners));\r
 +                    row.add(ownerLabel);\r
 +\r
 +                    String lastChange;\r
 +                    if (entry.lastChange.getTime() == 0) {\r
 +                        lastChange = "--";\r
 +                    } else {\r
 +                        lastChange = getTimeUtils().timeAgo(entry.lastChange);\r
 +                    }\r
 +                    Label lastChangeLabel = new Label("repositoryLastChange", lastChange);\r
 +                    row.add(lastChangeLabel);\r
 +                    WicketUtils.setCssClass(lastChangeLabel, getTimeUtils().timeAgoCss(entry.lastChange));\r
 +                    if (!StringUtils.isEmpty(entry.lastChangeAuthor)) {\r
 +                        WicketUtils.setHtmlTooltip(lastChangeLabel, getString("gb.author") + ": " + entry.lastChangeAuthor);\r
 +                    }\r
 +\r
 +                    WicketUtils.setAlternatingBackground(item, counter);\r
 +                    counter++;\r
 +                }\r
 +            };\r
 +            add(dataView);\r
 +\r
 +            if (dp instanceof SortableDataProvider<?>) {\r
 +                // add sortable header\r
 +                SortableDataProvider<?> sdp = (SortableDataProvider<?>) dp;\r
 +                Fragment fragment = new Fragment("headerContent", "flatRepositoryHeader", this);\r
 +                fragment.add(newSort("orderByRepository", SortBy.repository, sdp, dataView));\r
 +                fragment.add(newSort("orderByDescription", SortBy.description, sdp, dataView));\r
 +                fragment.add(newSort("orderByOwner", SortBy.owner, sdp, dataView));\r
 +                fragment.add(newSort("orderByDate", SortBy.date, sdp, dataView));\r
 +                add(fragment);\r
 +            } else {\r
 +                // not sortable\r
 +                Fragment fragment = new Fragment("headerContent", "groupRepositoryHeader", this);\r
++                if(collapsibleRepoGroups == CollapsibleRepositorySetting.EXPANDED ||\r
++                        collapsibleRepoGroups == CollapsibleRepositorySetting.COLLAPSED) {\r
++                    Fragment allCollapsible = new Fragment("allCollapsible", "tableAllCollapsible", this);\r
++                    fragment.add(allCollapsible);\r
++                } else {\r
++                    Fragment allCollapsible = new Fragment("allCollapsible", "emptyFragment", this);\r
++                    fragment.add(allCollapsible);\r
++                }\r
 +                add(fragment);\r
 +            }\r
 +        }\r
 +\r
 +\r
 +    }\r
 +\r
 +    private static class GroupRepositoryModel extends RepositoryModel {\r
 +\r
 +        private static final long serialVersionUID = 1L;\r
 +\r
 +        int count;\r
 +        String title;\r
 +\r
 +        GroupRepositoryModel(String name, int count) {\r
 +            super(name, "", "", new Date(0));\r
 +            this.count = count;\r
 +        }\r
 +\r
 +        @Override\r
 +        public String toString() {\r
 +            return (StringUtils.isEmpty(title) ? name : title) + " (" + count + ")";\r
 +        }\r
 +    }\r
 +\r
 +    protected enum SortBy {\r
 +        repository, description, owner, date;\r
 +    }\r
 +\r
 +    protected OrderByBorder newSort(String wicketId, SortBy field, SortableDataProvider<?> dp, final DataView<?> dataView) {\r
 +        return new OrderByBorder(wicketId, field.name(), dp) {\r
 +            private static final long serialVersionUID = 1L;\r
 +\r
 +            @Override\r
 +            protected void onSortChanged() {\r
 +                dataView.setCurrentPage(0);\r
 +            }\r
 +        };\r
 +    }\r
 +\r
 +    private static class SortableRepositoriesProvider extends SortableDataProvider<RepositoryModel> {\r
 +\r
 +        private static final long serialVersionUID = 1L;\r
 +\r
 +        private List<RepositoryModel> list;\r
 +\r
 +        protected SortableRepositoriesProvider(List<RepositoryModel> list) {\r
 +            this.list = list;\r
 +            setSort(SortBy.date.name(), false);\r
 +        }\r
 +\r
 +        @Override\r
 +        public int size() {\r
 +            if (list == null) {\r
 +                return 0;\r
 +            }\r
 +            return list.size();\r
 +        }\r
 +\r
 +        @Override\r
 +        public IModel<RepositoryModel> model(RepositoryModel header) {\r
 +            return new Model<RepositoryModel>(header);\r
 +        }\r
 +\r
 +        @Override\r
 +        public Iterator<RepositoryModel> iterator(int first, int count) {\r
 +            SortParam sp = getSort();\r
 +            String prop = sp.getProperty();\r
 +            final boolean asc = sp.isAscending();\r
 +\r
 +            if (prop == null || prop.equals(SortBy.date.name())) {\r
 +                Collections.sort(list, new Comparator<RepositoryModel>() {\r
 +                    @Override\r
 +                    public int compare(RepositoryModel o1, RepositoryModel o2) {\r
 +                        if (asc) {\r
 +                            return o1.lastChange.compareTo(o2.lastChange);\r
 +                        }\r
 +                        return o2.lastChange.compareTo(o1.lastChange);\r
 +                    }\r
 +                });\r
 +            } else if (prop.equals(SortBy.repository.name())) {\r
 +                Collections.sort(list, new Comparator<RepositoryModel>() {\r
 +                    @Override\r
 +                    public int compare(RepositoryModel o1, RepositoryModel o2) {\r
 +                        if (asc) {\r
 +                            return o1.name.compareTo(o2.name);\r
 +                        }\r
 +                        return o2.name.compareTo(o1.name);\r
 +                    }\r
 +                });\r
 +            } else if (prop.equals(SortBy.owner.name())) {\r
 +                Collections.sort(list, new Comparator<RepositoryModel>() {\r
 +                    @Override\r
 +                    public int compare(RepositoryModel o1, RepositoryModel o2) {\r
 +                        String own1 = ArrayUtils.toString(o1.owners);\r
 +                        String own2 = ArrayUtils.toString(o2.owners);\r
 +                        if (asc) {\r
 +                            return own1.compareTo(own2);\r
 +                        }\r
 +                        return own2.compareTo(own1);\r
 +                    }\r
 +                });\r
 +            } else if (prop.equals(SortBy.description.name())) {\r
 +                Collections.sort(list, new Comparator<RepositoryModel>() {\r
 +                    @Override\r
 +                    public int compare(RepositoryModel o1, RepositoryModel o2) {\r
 +                        if (asc) {\r
 +                            return o1.description.compareTo(o2.description);\r
 +                        }\r
 +                        return o2.description.compareTo(o1.description);\r
 +                    }\r
 +                });\r
 +            }\r
 +            return list.subList(first, first + count).iterator();\r
 +        }\r
 +    }\r
  }\r
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..4887c65aa92c58624e19b63e9c08cf8f120c62a8
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,142 @@@
++package com.gitblit.wicket.panels;
++
++import java.io.Serializable;
++import java.util.ArrayList;
++import java.util.List;
++
++import com.gitblit.models.RepositoryModel;
++import com.gitblit.utils.StringUtils;
++
++public class TreeNodeModel implements Serializable {
++
++    private static final long serialVersionUID = 1L;
++    final TreeNodeModel parent;
++    final String name;
++    private final List<TreeNodeModel> subFolders = new ArrayList<>();
++    private final List<RepositoryModel> repositories = new ArrayList<>();
++
++    /**
++     * Create a new tree root
++     */
++    public TreeNodeModel() {
++        this.name = "";
++        this.parent = null;
++    }
++
++    protected TreeNodeModel(String name, TreeNodeModel parent) {
++        this.name = name;
++        this.parent = parent;
++    }
++
++    public int getDepth() {
++        if(parent == null) {
++            return 0;
++        }else {
++            return parent.getDepth() +1;
++        }
++    }
++
++    /**
++     * Add a new sub folder to the current folder
++     *
++     * @param subFolder the subFolder to create
++     * @return the newly created folder to allow chaining
++     */
++    public TreeNodeModel add(String subFolder) {
++        TreeNodeModel n = new TreeNodeModel(subFolder, this);
++        subFolders.add(n);
++        return n;
++    }
++
++    /**
++     * Add the given repo to the current folder
++     *
++     * @param repo
++     */
++    public void add(RepositoryModel repo) {
++        repositories.add(repo);
++    }
++
++    /**
++     * Adds the given repository model within the given path. Creates parent folders if they do not exist
++     *
++     * @param path
++     * @param model
++     */
++    public void add(String path, RepositoryModel model) {
++        TreeNodeModel folder = getSubTreeNode(this, path, true);
++        folder.add(model);
++    }
++
++    @Override
++    public String toString() {
++        String string = name + "\n";
++        for(TreeNodeModel n : subFolders) {
++            string += "f";
++            for(int i = 0; i < n.getDepth(); i++) {
++                string += "-";
++            }
++            string += n.toString();
++        }
++
++        for(RepositoryModel n : repositories) {
++            string += "r";
++            for(int i = 0; i < getDepth()+1; i++) {
++                string += "-";
++            }
++            string += n.toString() + "\n";
++        }
++
++        return string;
++    }
++
++    public boolean containsSubFolder(String path) {
++        return containsSubFolder(this, path);
++    }
++
++    public TreeNodeModel getSubFolder(String path) {
++        return getSubTreeNode(this, path, false);
++    }
++
++
++
++
++    private static TreeNodeModel getSubTreeNode(TreeNodeModel node, String path, boolean create) {
++        if(!StringUtils.isEmpty(path)) {
++            boolean isPathInCurrentHierarchyLevel = path.lastIndexOf('/') < 0;
++            if(isPathInCurrentHierarchyLevel) {
++                for(TreeNodeModel t : node.subFolders) {
++                    if(t.name.equals(path) ) {
++                        return t;
++                    }
++                }
++
++                if(create) {
++                    node.add(path);
++                    return getSubTreeNode(node, path, true);
++                }
++            }else {
++                //traverse into subFolder
++                String folderInCurrentHierarchyLevel = path.substring(0, path.indexOf('/'));
++
++                for(TreeNodeModel t : node.subFolders) {
++                    if(t.name.equals(folderInCurrentHierarchyLevel) ) {
++                        String folderInNextHierarchyLevel = path.substring(path.indexOf('/') + 1, path.length());
++                        return getSubTreeNode(t, folderInNextHierarchyLevel, create);
++                    }
++                }
++
++                if(create) {
++                    String folderInNextHierarchyLevel = path.substring(path.indexOf('/') + 1, path.length());
++                    return getSubTreeNode(node.add(folderInCurrentHierarchyLevel), folderInNextHierarchyLevel, true);
++                }
++            }
++        }
++
++        return null;
++    }
++
++    private static boolean containsSubFolder(TreeNodeModel node, String path) {
++        return getSubTreeNode(node, path, false) != null;
++    }
++}
index 0000000000000000000000000000000000000000,0000000000000000000000000000000000000000..57051dd43e95025edb9eaa0e2485b724fa226128
new file mode 100644 (file)
--- /dev/null
--- /dev/null
@@@ -1,0 -1,0 +1,48 @@@
++package com.gitblit.wicket.panels;
++
++import static org.junit.Assert.assertEquals;
++import static org.junit.Assert.assertFalse;
++import static org.junit.Assert.assertTrue;
++
++import org.junit.Test;
++
++import com.gitblit.models.RepositoryModel;
++
++public class TreeNodeModelTest {
++
++    @Test
++    public void testContainsSubFolder() {
++        TreeNodeModel tree = new TreeNodeModel();
++        tree.add("foo").add("bar").add("baz");
++
++        assertTrue(tree.containsSubFolder("foo/bar/baz"));
++        assertTrue(tree.containsSubFolder("foo/bar"));
++        assertFalse(tree.containsSubFolder("foo/bar/blub"));
++    }
++
++    @Test
++    public void testAddInHierarchy() {
++        TreeNodeModel tree = new TreeNodeModel();
++        tree.add("foo").add("bar");
++
++        RepositoryModel model = new RepositoryModel("test","","",null);
++
++        // add model to non-existing folder. should be created automatically
++        tree.add("foo/bar/baz", model);
++        tree.add("another/non/existing/folder", model);
++
++        assertTrue(tree.containsSubFolder("foo/bar/baz"));
++        assertTrue(tree.containsSubFolder("another/non/existing/folder"));
++    }
++
++    @Test
++    public void testGetDepth() {
++        TreeNodeModel tree = new TreeNodeModel();
++        TreeNodeModel bar = tree.add("foo").add("bar").add("baz");
++
++        assertEquals(0, tree.getDepth());
++        assertEquals(3, bar.getDepth());
++    }
++
++
++}