James Moger
2011-06-08 008322bec70a3a20bd00ed2219215a9f42fe0ca5
commit | author | age
f13c4c 1 /*
JM 2  * Copyright 2011 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
a4d249 16 package com.gitblit.wicket.panels;
JM 17
8a2e9c 18 import java.text.MessageFormat;
a4d249 19 import java.util.ArrayList;
JM 20 import java.util.Collections;
21 import java.util.Comparator;
22 import java.util.Date;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27
28 import org.apache.wicket.PageParameters;
29 import org.apache.wicket.extensions.markup.html.repeater.data.sort.OrderByBorder;
30 import org.apache.wicket.extensions.markup.html.repeater.util.SortParam;
31 import org.apache.wicket.extensions.markup.html.repeater.util.SortableDataProvider;
32 import org.apache.wicket.markup.html.basic.Label;
33 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
8a2e9c 34 import org.apache.wicket.markup.html.link.Link;
a4d249 35 import org.apache.wicket.markup.html.panel.Fragment;
JM 36 import org.apache.wicket.markup.repeater.Item;
37 import org.apache.wicket.markup.repeater.data.DataView;
38 import org.apache.wicket.markup.repeater.data.IDataProvider;
39 import org.apache.wicket.markup.repeater.data.ListDataProvider;
40 import org.apache.wicket.model.IModel;
41 import org.apache.wicket.model.Model;
42
43 import com.gitblit.Constants.AccessRestrictionType;
44 import com.gitblit.GitBlit;
45 import com.gitblit.Keys;
1f9dae 46 import com.gitblit.models.RepositoryModel;
JM 47 import com.gitblit.models.UserModel;
a4d249 48 import com.gitblit.utils.StringUtils;
JM 49 import com.gitblit.utils.TimeUtils;
50 import com.gitblit.wicket.GitBlitWebSession;
51 import com.gitblit.wicket.WicketUtils;
52 import com.gitblit.wicket.pages.EditRepositoryPage;
53 import com.gitblit.wicket.pages.SummaryPage;
54
55 public class RepositoriesPanel extends BasePanel {
56
57     private static final long serialVersionUID = 1L;
8a2e9c 58
2a7306 59     public RepositoriesPanel(String wicketId, final boolean showAdmin,
JM 60             final Map<AccessRestrictionType, String> accessRestrictionTranslations) {
a4d249 61         super(wicketId);
8a2e9c 62
a4d249 63         final UserModel user = GitBlitWebSession.get().getUser();
JM 64         List<RepositoryModel> models = GitBlit.self().getRepositoryModels(user);
8a2e9c 65         final IDataProvider<RepositoryModel> dp;
JM 66
a4d249 67         Fragment adminLinks = new Fragment("adminPanel", "adminLinks", this);
JM 68         adminLinks.add(new BookmarkablePageLink<Void>("newRepository", EditRepositoryPage.class));
69         add(adminLinks.setVisible(showAdmin));
8a2e9c 70
2a7306 71         if (GitBlit.getString(Keys.web.repositoryListType, "flat").equalsIgnoreCase("grouped")) {
a4d249 72             Map<String, List<RepositoryModel>> groups = new HashMap<String, List<RepositoryModel>>();
JM 73             for (RepositoryModel model : models) {
74                 String rootPath = StringUtils.getRootPath(model.name);
75                 if (StringUtils.isEmpty(rootPath)) {
2a7306 76                     rootPath = GitBlit.getString(Keys.web.repositoryRootGroupName, " ");
a4d249 77                 }
JM 78                 if (!groups.containsKey(rootPath)) {
79                     groups.put(rootPath, new ArrayList<RepositoryModel>());
80                 }
81                 groups.get(rootPath).add(model);
82             }
83             List<String> roots = new ArrayList<String>(groups.keySet());
84             Collections.sort(roots);
85             List<RepositoryModel> groupedModels = new ArrayList<RepositoryModel>();
86             for (String root : roots) {
87                 List<RepositoryModel> subModels = groups.get(root);
8a2e9c 88                 groupedModels.add(new GroupRepositoryModel(root, subModels.size()));
a4d249 89                 groupedModels.addAll(subModels);
JM 90             }
8a2e9c 91             dp = new RepositoriesProvider(groupedModels);
a4d249 92         } else {
8a2e9c 93             dp = new SortableRepositoriesProvider(models);
a4d249 94         }
JM 95
96         DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("row", dp) {
97             private static final long serialVersionUID = 1L;
2a7306 98             int counter;
a4d249 99
8a2e9c 100             @Override
JM 101             protected void onBeforeRender() {
102                 super.onBeforeRender();
103                 counter = 0;
104             }
105
a4d249 106             public void populateItem(final Item<RepositoryModel> item) {
JM 107                 final RepositoryModel entry = item.getModelObject();
108                 if (entry instanceof GroupRepositoryModel) {
109                     Fragment row = new Fragment("rowContent", "groupRepositoryRow", this);
110                     item.add(row);
8a2e9c 111                     row.add(new Label("groupName", entry.toString()));
a4d249 112                     WicketUtils.setCssClass(item, "group");
JM 113                     return;
114                 }
115                 Fragment row = new Fragment("rowContent", "repositoryRow", this);
116                 item.add(row);
117                 if (entry.hasCommits) {
118                     // Existing repository
119                     PageParameters pp = WicketUtils.newRepositoryParameter(entry.name);
2a7306 120                     row.add(new LinkPanel("repositoryName", "list", entry.name, SummaryPage.class,
JM 121                             pp));
122                     row.add(new LinkPanel("repositoryDescription", "list", entry.description,
123                             SummaryPage.class, pp));
a4d249 124                 } else {
JM 125                     // New repository
2a7306 126                     row.add(new Label("repositoryName", entry.name
JM 127                             + "<span class='empty'>(empty)</span>").setEscapeModelStrings(false));
a4d249 128                     row.add(new Label("repositoryDescription", entry.description));
JM 129                 }
130
131                 if (entry.useTickets) {
2a7306 132                     row.add(WicketUtils.newImage("ticketsIcon", "bug_16x16.png",
JM 133                             getString("gb.tickets")));
a4d249 134                 } else {
JM 135                     row.add(WicketUtils.newBlankImage("ticketsIcon"));
136                 }
137
138                 if (entry.useDocs) {
2a7306 139                     row.add(WicketUtils
JM 140                             .newImage("docsIcon", "book_16x16.png", getString("gb.docs")));
a4d249 141                 } else {
JM 142                     row.add(WicketUtils.newBlankImage("docsIcon"));
143                 }
144
145                 if (entry.isFrozen) {
2a7306 146                     row.add(WicketUtils.newImage("frozenIcon", "cold_16x16.png",
JM 147                             getString("gb.isFrozen")));
a4d249 148                 } else {
JM 149                     row.add(WicketUtils.newClearPixel("frozenIcon").setVisible(false));
150                 }
151                 switch (entry.accessRestriction) {
152                 case NONE:
153                     row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
154                     break;
155                 case PUSH:
2a7306 156                     row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_go_16x16.png",
JM 157                             accessRestrictionTranslations.get(entry.accessRestriction)));
a4d249 158                     break;
JM 159                 case CLONE:
2a7306 160                     row.add(WicketUtils.newImage("accessRestrictionIcon", "lock_pull_16x16.png",
JM 161                             accessRestrictionTranslations.get(entry.accessRestriction)));
a4d249 162                     break;
JM 163                 case VIEW:
2a7306 164                     row.add(WicketUtils.newImage("accessRestrictionIcon", "shield_16x16.png",
JM 165                             accessRestrictionTranslations.get(entry.accessRestriction)));
a4d249 166                     break;
JM 167                 default:
168                     row.add(WicketUtils.newBlankImage("accessRestrictionIcon"));
169                 }
170
171                 row.add(new Label("repositoryOwner", entry.owner));
172
173                 String lastChange = TimeUtils.timeAgo(entry.lastChange);
174                 Label lastChangeLabel = new Label("repositoryLastChange", lastChange);
175                 row.add(lastChangeLabel);
176                 WicketUtils.setCssClass(lastChangeLabel, TimeUtils.timeAgoCss(entry.lastChange));
177
2a7306 178                 boolean showOwner = user != null && user.username.equalsIgnoreCase(entry.owner);
a4d249 179                 if (showAdmin) {
2a7306 180                     Fragment repositoryLinks = new Fragment("repositoryLinks",
JM 181                             "repositoryAdminLinks", this);
182                     repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository",
183                             EditRepositoryPage.class, WicketUtils
184                                     .newRepositoryParameter(entry.name)));
8a2e9c 185                     Link<Void> deleteLink = new Link<Void>("deleteRepository") {
JM 186
187                         private static final long serialVersionUID = 1L;
188
189                         @Override
190                         public void onClick() {
191                             if (GitBlit.self().deleteRepositoryModel(entry)) {
192                                 info(MessageFormat.format("Repository ''{0}'' deleted.", entry));
193                                 if (dp instanceof SortableRepositoriesProvider) {
194                                     ((SortableRepositoriesProvider) dp).remove(entry);
195                                 } else {
196                                     ((RepositoriesProvider) dp).remove(entry);
197                                 }
198                             } else {
2a7306 199                                 error(MessageFormat.format("Failed to delete repository ''{0}''!",
JM 200                                         entry));
8a2e9c 201                             }
JM 202                         }
203                     };
2a7306 204                     deleteLink.add(new JavascriptEventConfirmation("onclick", MessageFormat.format(
JM 205                             "Delete repository \"{0}\"?", entry)));
8a2e9c 206                     repositoryLinks.add(deleteLink);
a4d249 207                     row.add(repositoryLinks);
JM 208                 } else if (showOwner) {
2a7306 209                     Fragment repositoryLinks = new Fragment("repositoryLinks",
JM 210                             "repositoryOwnerLinks", this);
211                     repositoryLinks.add(new BookmarkablePageLink<Void>("editRepository",
212                             EditRepositoryPage.class, WicketUtils
213                                     .newRepositoryParameter(entry.name)));
a4d249 214                     row.add(repositoryLinks);
JM 215                 } else {
216                     row.add(new Label("repositoryLinks"));
217                 }
218                 WicketUtils.setAlternatingBackground(item, counter);
219                 counter++;
220             }
221         };
222         add(dataView);
223
224         if (dp instanceof SortableDataProvider<?>) {
225             // add sortable header
226             SortableDataProvider<?> sdp = (SortableDataProvider<?>) dp;
227             Fragment fragment = new Fragment("headerContent", "flatRepositoryHeader", this);
228             fragment.add(newSort("orderByRepository", SortBy.repository, sdp, dataView));
229             fragment.add(newSort("orderByDescription", SortBy.description, sdp, dataView));
230             fragment.add(newSort("orderByOwner", SortBy.owner, sdp, dataView));
231             fragment.add(newSort("orderByDate", SortBy.date, sdp, dataView));
232             add(fragment);
233         } else {
234             // not sortable
235             Fragment fragment = new Fragment("headerContent", "groupRepositoryHeader", this);
236             add(fragment);
237         }
238     }
8a2e9c 239
2a7306 240     private static class GroupRepositoryModel extends RepositoryModel {
a4d249 241
JM 242         private static final long serialVersionUID = 1L;
243
2a7306 244         int count;
8a2e9c 245
JM 246         GroupRepositoryModel(String name, int count) {
a4d249 247             super(name, "", "", new Date(0));
8a2e9c 248             this.count = count;
JM 249         }
250
251         @Override
252         public String toString() {
253             return name + " (" + count + ")";
a4d249 254         }
JM 255     }
8a2e9c 256
a4d249 257     protected enum SortBy {
JM 258         repository, description, owner, date;
259     }
260
2a7306 261     protected OrderByBorder newSort(String wicketId, SortBy field, SortableDataProvider<?> dp,
JM 262             final DataView<?> dataView) {
a4d249 263         return new OrderByBorder(wicketId, field.name(), dp) {
JM 264             private static final long serialVersionUID = 1L;
265
266             @Override
267             protected void onSortChanged() {
268                 dataView.setCurrentPage(0);
269             }
270         };
271     }
272
2a7306 273     private static class RepositoriesProvider extends ListDataProvider<RepositoryModel> {
8a2e9c 274
JM 275         private static final long serialVersionUID = 1L;
276
277         public RepositoriesProvider(List<RepositoryModel> list) {
278             super(list);
279         }
280
281         @Override
282         public List<RepositoryModel> getData() {
283             return super.getData();
284         }
285
286         public void remove(RepositoryModel model) {
287             int index = getData().indexOf(model);
288             RepositoryModel groupModel = null;
289             if (index == (getData().size() - 1)) {
290                 // last element
291                 if (index > 0) {
292                     // previous element is group header, then this is last
293                     // repository in group. remove group too.
294                     if (getData().get(index - 1) instanceof GroupRepositoryModel) {
295                         groupModel = getData().get(index - 1);
296                     }
297                 }
298             } else if (index < (getData().size() - 1)) {
299                 // not last element. check next element for group match.
2a7306 300                 if (getData().get(index - 1) instanceof GroupRepositoryModel
JM 301                         && getData().get(index + 1) instanceof GroupRepositoryModel) {
8a2e9c 302                     // repository is sandwiched by group headers so this
JM 303                     // repository is the only element in the group. remove
304                     // group.
305                     groupModel = getData().get(index - 1);
306                 }
307             }
308
309             if (groupModel == null) {
310                 // Find the group and decrement the count
311                 for (int i = index; i >= 0; i--) {
312                     if (getData().get(i) instanceof GroupRepositoryModel) {
313                         ((GroupRepositoryModel) getData().get(i)).count--;
314                         break;
315                     }
316                 }
317             } else {
318                 // Remove the group header
319                 getData().remove(groupModel);
320             }
321
322             getData().remove(model);
323         }
324     }
325
2a7306 326     private static class SortableRepositoriesProvider extends SortableDataProvider<RepositoryModel> {
JM 327
a4d249 328         private static final long serialVersionUID = 1L;
2a7306 329
JM 330         private List<RepositoryModel> list;
a4d249 331
8a2e9c 332         protected SortableRepositoriesProvider(List<RepositoryModel> list) {
a4d249 333             this.list = list;
JM 334             setSort(SortBy.date.name(), false);
335         }
336
8a2e9c 337         public void remove(RepositoryModel model) {
JM 338             list.remove(model);
339         }
340
a4d249 341         @Override
JM 342         public int size() {
2a7306 343             if (list == null) {
a4d249 344                 return 0;
2a7306 345             }
a4d249 346             return list.size();
JM 347         }
348
349         @Override
350         public IModel<RepositoryModel> model(RepositoryModel header) {
351             return new Model<RepositoryModel>(header);
352         }
353
354         @Override
355         public Iterator<RepositoryModel> iterator(int first, int count) {
356             SortParam sp = getSort();
357             String prop = sp.getProperty();
358             final boolean asc = sp.isAscending();
359
360             if (prop == null || prop.equals(SortBy.date.name())) {
361                 Collections.sort(list, new Comparator<RepositoryModel>() {
362                     @Override
363                     public int compare(RepositoryModel o1, RepositoryModel o2) {
2a7306 364                         if (asc) {
a4d249 365                             return o1.lastChange.compareTo(o2.lastChange);
2a7306 366                         }
a4d249 367                         return o2.lastChange.compareTo(o1.lastChange);
JM 368                     }
369                 });
370             } else if (prop.equals(SortBy.repository.name())) {
371                 Collections.sort(list, new Comparator<RepositoryModel>() {
372                     @Override
373                     public int compare(RepositoryModel o1, RepositoryModel o2) {
2a7306 374                         if (asc) {
a4d249 375                             return o1.name.compareTo(o2.name);
2a7306 376                         }
a4d249 377                         return o2.name.compareTo(o1.name);
JM 378                     }
379                 });
380             } else if (prop.equals(SortBy.owner.name())) {
381                 Collections.sort(list, new Comparator<RepositoryModel>() {
382                     @Override
383                     public int compare(RepositoryModel o1, RepositoryModel o2) {
2a7306 384                         if (asc) {
a4d249 385                             return o1.owner.compareTo(o2.owner);
2a7306 386                         }
a4d249 387                         return o2.owner.compareTo(o1.owner);
JM 388                     }
389                 });
390             } else if (prop.equals(SortBy.description.name())) {
391                 Collections.sort(list, new Comparator<RepositoryModel>() {
392                     @Override
393                     public int compare(RepositoryModel o1, RepositoryModel o2) {
2a7306 394                         if (asc) {
a4d249 395                             return o1.description.compareTo(o2.description);
2a7306 396                         }
a4d249 397                         return o2.description.compareTo(o1.description);
JM 398                     }
399                 });
400             }
401             return list.subList(first, first + count).iterator();
402         }
403     }
404 }