source: trunk/www/biomaterials/events/list_events.jsp @ 5951

Last change on this file since 5951 was 5951, checked in by Nicklas Nordborg, 10 years ago

References #1655: GUI improvements

  • Fixes rest of list pages in Biomaterial LIMS menu
  • Introduced an 'iframe' page type so that thos pages doesn't have to 'cannibalise' on the 'popup' page type (doesn't works well with absolutely positioned content).
  • Fixed subclass="dialogbuttons" on all list pages when used in popup form


  • Property svn:eol-style set to native
  • Property svn:keywords set to Date Id
File size: 19.9 KB
Line 
1<%-- $Id: list_events.jsp 5951 2012-02-09 14:19:17Z nicklas $
2  ------------------------------------------------------------------
3  Copyright (C) 2006 Jari Häkkinen, Nicklas Nordborg, Martin Svensson
4  Copyright (C) 2007 Johan Enell
5
6  This file is part of BASE - BioArray Software Environment.
7  Available at http://base.thep.lu.se/
8
9  BASE is free software; you can redistribute it and/or
10  modify it under the terms of the GNU General Public License
11  as published by the Free Software Foundation; either version 3
12  of the License, or (at your option) any later version.
13
14  BASE is distributed in the hope that it will be useful,
15  but WITHOUT ANY WARRANTY; without even the implied warranty of
16  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  GNU General Public License for more details.
18
19  You should have received a copy of the GNU General Public License
20  along with BASE. If not, see <http://www.gnu.org/licenses/>.
21  ------------------------------------------------------------------
22
23  @author Nicklas
24  @version 2.0
25--%>
26<%@ page pageEncoding="UTF-8" session="false"
27  import="net.sf.basedb.core.SessionControl"
28  import="net.sf.basedb.core.DbControl"
29  import="net.sf.basedb.core.Item"
30  import="net.sf.basedb.core.ItemContext"
31  import="net.sf.basedb.core.BioMaterialEvent"
32  import="net.sf.basedb.core.BioPlateEvent"
33  import="net.sf.basedb.core.BioPlateEventParticipant"
34  import="net.sf.basedb.core.BioPlate"
35  import="net.sf.basedb.core.MeasuredBioMaterial"
36  import="net.sf.basedb.core.Quantity"
37  import="net.sf.basedb.core.Unit"
38  import="net.sf.basedb.core.ItemQuery"
39  import="net.sf.basedb.core.ItemResultIterator"
40  import="net.sf.basedb.core.Permission"
41  import="net.sf.basedb.core.PluginDefinition"
42  import="net.sf.basedb.core.PermissionDeniedException"
43  import="net.sf.basedb.core.query.Restrictions"
44  import="net.sf.basedb.core.query.Expressions"
45  import="net.sf.basedb.core.query.Orders"
46  import="net.sf.basedb.core.query.Hql"
47  import="net.sf.basedb.core.plugin.GuiContext"
48  import="net.sf.basedb.core.plugin.Plugin"
49  import="net.sf.basedb.util.Enumeration"
50  import="net.sf.basedb.util.units.UnitUtil"
51  import="net.sf.basedb.clients.web.Base"
52  import="net.sf.basedb.clients.web.ChangeHistoryUtil"
53  import="net.sf.basedb.clients.web.ModeInfo"
54  import="net.sf.basedb.clients.web.PermissionUtil"
55  import="net.sf.basedb.clients.web.util.HTML"
56  import="net.sf.basedb.util.Values"
57  import="net.sf.basedb.util.formatter.Formatter"
58  import="net.sf.basedb.clients.web.formatter.FormatterFactory"
59  import="net.sf.basedb.clients.web.extensions.ExtensionsControl"
60  import="net.sf.basedb.clients.web.extensions.JspContext"
61  import="net.sf.basedb.clients.web.extensions.renderer.PrefixSuffixRenderer"
62  import="net.sf.basedb.clients.web.extensions.toolbar.ToolbarUtil"
63  import="net.sf.basedb.util.extensions.ExtensionsInvoker"
64  import="java.util.List"
65  import="java.util.Map"
66  import="java.util.Date"
67%>
68<%@ taglib prefix="base" uri="/WEB-INF/base.tld" %>
69<%@ taglib prefix="tbl" uri="/WEB-INF/table.tld" %>
70<%@ taglib prefix="t" uri="/WEB-INF/tab.tld" %>
71<%@ taglib prefix="p" uri="/WEB-INF/path.tld" %>
72<%@ taglib prefix="ext" uri="/WEB-INF/extensions.tld" %>
73<%!
74  private static final Item itemType = Item.BIOMATERIALEVENT;
75  private static final GuiContext guiContext = new GuiContext(itemType, GuiContext.Type.LIST);
76%>
77<%
78final Item bioMaterialType = Item.valueOf(request.getParameter("biomaterial_type"));
79final int bioMaterialId = Values.getInt(request.getParameter("biomaterial_id"));
80final SessionControl sc = Base.getExistingSessionControl(pageContext, Permission.DENIED, itemType);
81final String ID = sc.getId();
82final ItemContext cc = Base.getAndSetCurrentContext(sc, itemType, null, null);
83
84final ModeInfo mode = ModeInfo.get(request.getParameter("mode"));
85final String callback = request.getParameter("callback");
86final String title = mode.generateTitle("event", "events");
87final DbControl dc = sc.newDbControl();
88ItemResultIterator<BioMaterialEvent> events = null;
89try
90{
91  final MeasuredBioMaterial bioMaterial = (MeasuredBioMaterial)bioMaterialType.getById(dc, bioMaterialId);
92  final BioMaterialEvent creationEvent = bioMaterial.getCreationEvent();
93  final boolean createPermission = bioMaterial.hasPermission(Permission.WRITE);
94  final boolean deletePermission = createPermission;
95
96  String viewPage = null;
97  String listName = null;
98  if (bioMaterialType == Item.SAMPLE)
99  {
100    listName = "Samples";
101    viewPage = "../samples/index.jsp";
102  }
103  else if (bioMaterialType == Item.EXTRACT)
104  {
105    listName = "Extracts";
106    viewPage = "../extracts/index.jsp";
107  }
108 
109  Unit microGram = UnitUtil.getUnit(dc, Quantity.MASS, "µg");
110  Map<Plugin.MainType, Integer> pluginCount = PluginDefinition.countPlugins(dc, guiContext);
111  try
112  {
113    final ItemQuery<BioMaterialEvent> query = Base.getConfiguredQuery(dc, cc, true, bioMaterial.getEvents(), mode);
114    events = query.iterate(dc);
115  }
116  catch (Throwable t)
117  {
118    cc.setMessage(t.getMessage());
119    t.printStackTrace();
120  }
121  int numListed = 0;
122  Formatter<Date> dateFormatter = FormatterFactory.getDateFormatter(sc);
123  JspContext jspContext = ExtensionsControl.createContext(dc, pageContext, guiContext, bioMaterial);
124  ExtensionsInvoker invoker = ToolbarUtil.useExtensions(jspContext);
125  %>
126  <base:page title="<%=title%>" type="<%=mode.getPageType()%>">
127  <base:head scripts="table.js,tabcontrol.js" styles="table.css,toolbar.css,headertabcontrol.css,path.css">
128    <ext:scripts context="<%=jspContext%>" />
129    <ext:stylesheets context="<%=jspContext%>" />
130    <script language="JavaScript">
131    var submitPage = 'index.jsp';
132    var formId = 'events';
133    function newItem()
134    {
135      Main.viewOrEditItem('<%=ID%>', '<%=itemType.name()%>', 0, true, '&biomaterial_type=<%=bioMaterialType.name()%>&biomaterial_id=<%=bioMaterial.getId()%>');
136    }
137    function editItem(itemId)
138    {
139      Main.viewOrEditItem('<%=ID%>', '<%=itemType.name()%>', itemId, true, '&biomaterial_type=<%=bioMaterialType.name()%>&biomaterial_id=<%=bioMaterial.getId()%>');
140    }
141    function viewItem(itemId)
142    {
143      Main.viewOrEditItem('<%=ID%>', '<%=itemType.name()%>', itemId, false, '&biomaterial_type=<%=bioMaterialType.name()%>&biomaterial_id=<%=bioMaterial.getId()%>');
144    }
145    function itemOnClick(evt, itemId)
146    {
147      Table.itemOnClick(formId, evt, itemId, '<%=mode.getName()%>', viewItem, editItem, returnSelected);
148    }
149    function deleteItems()
150    {
151      var frm = document.forms[formId];
152      var numChecked = Forms.numChecked(frm);
153      if (numChecked == 0)
154      {
155        alert('Please select at least one item in the list');
156        return;
157      }
158      else
159      {
160        var rep = numChecked == 1 ? 'event' : 'events';
161        if (!confirm('You are about to delete '+numChecked+' '+rep+'. This can\'t be undone. Continue?'))
162        {
163          return;
164        }
165      }
166      frm.action = submitPage;
167      frm.cmd.value = 'DeleteItems';
168      frm.submit();
169    }
170    function configureColumns()
171    {
172      Table.configureColumns('<%=ID%>', formId, '<%=itemType.name()%>', '<%=(String)cc.getObject("defaultColumns")%>');
173    }
174    function runPlugin(cmd)
175    {
176      Table.submitToPopup(formId, cmd, 750, 500);
177    }
178    function returnSelected()
179    {
180      Table.returnSelected(formId, <%=callback != null ? "window.opener."+callback : "null" %>);
181      window.close();
182    }
183    function presetOnChange()
184    {
185      Table.presetOnChange('<%=ID%>', formId, '<%=itemType.name()%>', '<%=(String)cc.getObject("defaultColumns")%>');
186    }
187    function switchTab(tabControlId, tabId)
188    {
189      if (tabId == 'properties' || tabId == 'annotations' || tabId == 'overview' || tabId == 'history')
190      {
191        location.href = '<%=viewPage%>?ID=<%=ID%>&cmd=ViewItem&item_id=<%=bioMaterialId%>&tab='+tabId;
192      }
193      else
194      {
195        TabControl.setActiveTab(tabControlId, tabId);
196      }
197    }
198    </script>
199  </base:head>
200 
201  <base:body>
202    <p:path><p:pathelement 
203      title="<%=listName%>" href="<%=viewPage+"?ID="+ID%>" 
204      /><p:pathelement title="<%=HTML.encodeTags(bioMaterial.getName())%>"
205      /></p:path>
206
207    <t:tabcontrol 
208      id="main" 
209      subclass="mastertabcontrol content"
210      active="events" switch="switchTab">
211    <t:tab id="properties" title="Properties" />
212    <t:tab id="annotations" title="Annotations &amp; parameters"
213      tooltip="View annotation values and protocol parameters" />
214   
215    <t:tab id="events" title="Events">
216    <tbl:table 
217      id="events" 
218      columns="<%=cc.getSetting("columns")%>"
219      sortby="<%=cc.getSortProperty()%>" 
220      direction="<%=cc.getSortDirection()%>"
221      title="<%=title%>"
222      action="index.jsp"
223      sc="<%=sc%>"
224      item="<%=itemType%>"
225      subclass="fulltable"
226      >
227      <tbl:hidden 
228        name="mode" 
229        value="<%=mode.getName()%>" 
230      />
231      <tbl:hidden 
232        name="biomaterial_id"
233        value="<%=String.valueOf(bioMaterialId)%>" 
234      />
235      <tbl:hidden 
236        name="biomaterial_type" 
237        value="<%=bioMaterialType.name()%>" 
238      />
239      <tbl:hidden 
240        name="callback" 
241        value="<%=callback%>" 
242        skip="<%=callback == null%>" 
243      />
244      <%
245      Enumeration<String, String> eventTypes = new Enumeration<String, String>();
246      for (BioMaterialEvent.Type et : BioMaterialEvent.Type.values())
247      {
248        eventTypes.add(Integer.toString(et.getValue()), HTML.encodeTags(et.toString()));
249      }
250      %>
251      <tbl:columndef 
252        id="type"
253        property="eventType"
254        datatype="int"
255        title="Type"
256        enumeration="<%=eventTypes%>"
257        sortable="true" 
258        filterable="true"
259        exportable="true"
260        show="always" 
261      />
262      <tbl:columndef 
263        id="id"
264        clazz="uniquecol"
265        property="id"
266        datatype="int"
267        title="ID"
268        sortable="true"
269        filterable="true"
270        exportable="true"
271      />
272      <tbl:columndef 
273        id="entryDate"
274        property="entryDate"
275        datatype="date"
276        title="Entry date"
277        sortable="true" 
278        filterable="true"
279        exportable="true"
280        formatter="<%=dateFormatter%>"
281      />
282      <tbl:columndef 
283        id="eventDate"
284        property="eventDate"
285        datatype="date"
286        title="Event date"
287        sortable="true" 
288        filterable="true"
289        exportable="true"
290        formatter="<%=dateFormatter%>"
291      />
292      <tbl:columndef 
293        id="quantity"
294        datatype="float"
295        title="Used quantity (µg)"
296        sortable="false" 
297        filterable="false"
298        exportable="false"
299        unit="<%=microGram%>"
300      />
301      <tbl:columndef
302        id="bioPlateEvent"
303        property="bioPlateEventParticipant.event.name"
304        datatype="string"
305        title="Plate event"
306        sortable="true" 
307        filterable="true"
308        exportable="true"
309      />
310      <tbl:columndef 
311        id="protocol"
312        property="protocol.name"
313        datatype="string"
314        title="Protocol"
315        sortable="true" 
316        filterable="true"
317        exportable="true"
318      />
319      <tbl:columndef 
320        id="user"
321        property="user.name"
322        datatype="string"
323        title="User"
324        sortable="true" 
325        filterable="true"
326        exportable="true"
327      />
328      <tbl:columndef 
329        id="comment"
330        property="comment"
331        datatype="string"
332        title="Comment" 
333        sortable="true" 
334        filterable="true" 
335        exportable="true"
336      />
337      <tbl:columndef
338        id="permission"
339        title="Permission"
340      />
341      <div class="panelgroup bottomborder">
342        <tbl:toolbar
343          visible="<%=mode.hasToolbar()%>"
344          subclass="bottomborder"
345          >
346          <tbl:button 
347            disabled="<%=!createPermission%>" 
348            image="new.png" 
349            onclick="newItem()" 
350            title="New&hellip;" 
351            tooltip="<%=createPermission ? "Create new event" : "You do not have permission to create events"%>" 
352          />
353          <tbl:button 
354            disabled="<%=!deletePermission%>" 
355            image="delete.png" 
356            title="Delete&hellip;"
357            onclick="deleteItems()" 
358            tooltip="<%=deletePermission ? "Delete the selected events" : "You do not have permission to delete events"%>" 
359          />
360          <tbl:button 
361            image="columns.png" 
362            onclick="configureColumns()" 
363            title="Columns&hellip;" 
364            tooltip="Show, hide and re-order columns" 
365          />
366          <tbl:button 
367            image="import.png" 
368            onclick="runPlugin('ImportItems')" 
369            title="Import&hellip;" 
370            tooltip="Import data" 
371            visible="<%=pluginCount.containsKey(Plugin.MainType.IMPORT)%>"
372          />
373          <tbl:button 
374            image="export.png" 
375            onclick="runPlugin('ExportItems')" 
376            title="Export&hellip;" 
377            tooltip="Export data" 
378            visible="<%=pluginCount.containsKey(Plugin.MainType.EXPORT)%>"
379          />
380          <tbl:button 
381            image="runplugin.png" 
382            onclick="runPlugin('RunListPlugin')" 
383            title="Run plugin&hellip;" 
384            tooltip="Run a plugin" 
385            visible="<%=pluginCount.containsKey(Plugin.MainType.OTHER)%>"
386          />
387          <ext:render extensions="<%=invoker%>" context="<%=jspContext%>" 
388            wrapper="<%=new PrefixSuffixRenderer(jspContext, "<td>", "</td>") %>"/>
389        </tbl:toolbar>
390        <tbl:panel>
391          <tbl:presetselector 
392            onchange="presetOnChange()"
393          />
394          <tbl:navigator
395            page="<%=cc.getPage()%>" 
396            rowsperpage="<%=cc.getRowsPerPage()%>" 
397            totalrows="<%=events == null ? 0 : events.getTotalCount()%>" 
398            visible="<%=mode.hasNavigator()%>"
399          />
400        </tbl:panel>
401      </div>
402      <tbl:data>
403        <tbl:headers>
404          <tbl:headerrow>
405            <tbl:header colspan="3" />
406            <tbl:columnheaders />
407          </tbl:headerrow>
408          <tbl:headerrow>
409            <tbl:header subclass="index" />
410            <tbl:header 
411              subclass="check" 
412              visible="<%=mode.hasCheck()%>"
413              ><base:icon 
414                image="check_uncheck.png" 
415                tooltip="Check/uncheck all" 
416                onclick="Forms.checkUncheck(document.forms[formId])" 
417              /></tbl:header>
418            <tbl:header 
419              subclass="check" 
420              visible="<%=mode.hasRadio()%>"
421              />
422            <tbl:header 
423              subclass="icons" 
424              visible="<%=mode.hasIcons()%>"
425              />
426            <tbl:propertyfilter />
427          </tbl:headerrow>
428        </tbl:headers>
429        <tbl:rows>
430          <%
431          if (cc.getMessage() != null)
432          {
433            %>
434            <tbl:panel clazz="messagepanel">
435              <div class="messagecontainer error"><%=cc.getMessage()%></div>
436            </tbl:panel>
437            <%
438            cc.setMessage(null);
439          }
440          int index = cc.getPage()*cc.getRowsPerPage();
441          int selectedItemId = cc.getId();
442          if (events != null)
443          {
444            while (events.hasNext())
445            {
446              BioMaterialEvent item = events.next();
447              BioMaterialEvent.Type eventType = item.getEventType();
448              int itemId = item.getId();
449              String name = "TODO";
450              index++;
451              numListed++;
452              %>
453              <tbl:row>
454                <tbl:header 
455                  clazz="index"
456                  ><%=index%></tbl:header>
457                <tbl:header 
458                  clazz="check" 
459                  visible="<%=mode.hasCheck()%>"
460                  ><input 
461                    type="checkbox" 
462                    name="<%=itemId%>" 
463                    value="<%=itemId%>" 
464                    title="TODO" 
465                    <%=cc.getSelected().contains(itemId) ? "checked" : ""%>
466                  ></tbl:header>
467                <tbl:header 
468                  clazz="check" 
469                  visible="<%=mode.hasRadio()%>"
470                  ><input 
471                      type="radio" 
472                      name="item_id" 
473                      value="<%=itemId%>" 
474                      title="<%=name%>" 
475                      <%=selectedItemId == itemId ? "checked" : ""%>
476                    ></tbl:header>
477                <tbl:header 
478                  clazz="icons" 
479                  visible="<%=mode.hasIcons()%>"
480                  >&nbsp;</tbl:header>
481                <tbl:cell column="type">
482                  <%
483                  if (eventType == BioMaterialEvent.Type.OTHER)
484                  {
485                    if (item.hasPermission(Permission.WRITE))
486                    {
487                      %>
488                      <div class="link" onclick="itemOnClick(event, <%=itemId%>)"><%=eventType%></div>
489                      <%
490                    }
491                    else
492                    {
493                      %>
494                      <div class="link" onclick="viewItem(<%=itemId%>)"><%=eventType%></div>
495                      <%
496                    }
497                  }
498                  else if (eventType == BioMaterialEvent.Type.CREATION)
499                  {
500                    if (item.equals(creationEvent))
501                    {
502                      %>
503                      <div class="link" onclick="viewItem(<%=itemId%>)">
504                      Created
505                      </div>
506                      <%
507                    }
508                    else
509                    {
510                      MeasuredBioMaterial child = null;
511                      try
512                      {
513                        child = item.getBioMaterial();
514                      }
515                      catch (PermissionDeniedException ex)
516                      {}
517                      %>
518                      <div class="link" onclick="viewItem(<%=itemId%>)">
519                      Created <%=child == null ? "child" : child.getType().toString().toLowerCase() %>: <base:propertyvalue 
520                            item="<%=item%>" 
521                            property="bioMaterial"
522                            enableEditLink="<%=false%>" 
523                            enablePropertyLink="<%=false%>"
524                          />
525                      </div>
526                      <%
527                    }
528                  }
529                  else if (eventType == BioMaterialEvent.Type.BIOASSAY)
530                  {
531                    %>
532                    <div class="link" onclick="viewItem(<%=itemId%>)">
533                    Bioassay: <base:propertyvalue 
534                              item="<%=item%>" 
535                              property="physicalBioAssay"
536                              enableEditLink="<%=false%>" 
537                              enablePropertyLink="<%=false%>"
538                            />
539                    </div>
540                    <%
541                  }
542                  %>
543                </tbl:cell>
544                <tbl:cell column="id"><%=item.getId()%></tbl:cell>
545                <tbl:cell column="eventDate" value="<%=item.getEventDate()%>" />
546                <tbl:cell column="entryDate" value="<%=item.getEntryDate()%>" />
547                <tbl:cell column="quantity"><%=Values.formatNumber(item.getUsedQuantity(bioMaterial), 2)%></tbl:cell>
548                <tbl:cell column="bioPlateEvent">
549                  <%
550                  try
551                  {
552                    BioPlateEventParticipant participant = item.getBioPlateEventParticipant();
553                    if (participant != null)
554                    {
555                      BioPlateEvent event = participant.getEvent();
556                      String extraUrl = "";
557                      try
558                      {
559                        extraUrl = "&bioplate_id=" + participant.getBioPlate().getId();
560                      }
561                      catch (PermissionDeniedException ex)
562                      {}
563                      boolean editLink = event.hasPermission(Permission.WRITE);
564                      if (mode.hasPropertyLink())
565                      {
566                        %>
567                        <span class="link" 
568                          onclick="Main.itemOnClick(event, '<%=ID%>', 'BIOPLATEEVENT', <%=event.getId()%>, <%=editLink%>, '<%=extraUrl%>')"
569                          title="View this bioplate event <%=editLink? "(use CTRL, ALT or SHIFT to edit)" : ""%>"
570                        >
571                        <%=HTML.encodeTags(event.getName()) %>
572                        </span>
573                        <%
574                      }
575                      else
576                      {
577                        %>
578                        <%=HTML.encodeTags(event.getName()) %>
579                        <%
580                      }
581                    }
582                    else
583                    {
584                      %>
585                      <i>- none -</i>
586                      <%
587                    }
588                  }
589                  catch (PermissionDeniedException ex)
590                  {
591                    %>
592                    <i>- denied -</i>
593                    <%
594                  }
595                  %>
596                </tbl:cell>
597                <tbl:cell column="protocol"
598                  ><base:propertyvalue 
599                    item="<%=item%>" 
600                    property="protocol"
601                    enableEditLink="<%=mode.hasEditLink()%>" 
602                    enablePropertyLink="<%=mode.hasPropertyLink()%>"
603                  /></tbl:cell>
604                <tbl:cell column="user"
605                  ><base:propertyvalue 
606                    item="<%=item%>" 
607                    property="user"
608                    enableEditLink="<%=mode.hasEditLink()%>" 
609                    enablePropertyLink="<%=mode.hasPropertyLink()%>"
610                  /></tbl:cell>
611                <tbl:cell column="comment"><%=HTML.encodeTags(item.getComment())%></tbl:cell>
612                <tbl:cell column="permission"><%=PermissionUtil.getShortPermissions(item)%></tbl:cell>
613              </tbl:row>
614              <%
615            }
616          }
617          if (numListed == 0)
618          {
619            %>
620            <tbl:panel clazz="messagepanel">
621              <div class="messagecontainer note">
622              <%=events == null || events.getTotalCount() == 0 ? "No events were found" : "No events on this page. Please select another page!" %>
623              </div>
624            </tbl:panel>
625            <%
626          }
627          %>
628          </tbl:rows>
629        </tbl:data>
630    </tbl:table>
631    <base:buttongroup subclass="dialogbuttons">
632      <base:button onclick="returnSelected();" title="Ok" visible="<%=mode.hasOkButton()%>" />
633      <base:button onclick="window.close();" title="Cancel" visible="<%=mode.hasCancelButton()%>" />
634      <base:button onclick="window.close();" title="Close" visible="<%=mode.hasCloseButton()%>" />
635    </base:buttongroup>
636    </t:tab>
637   
638    <t:tab id="overview" title="Overview" 
639      tooltip="Display a tree overview of related items" />
640    <t:tab id="history" title="Change history" 
641        tooltip="Displays a log of all modifications made to this item"
642        visible="<%=ChangeHistoryUtil.showChangeHistoryTab(sc)%>" />
643    </t:tabcontrol>
644
645  </base:body>
646  </base:page>
647  <%
648}
649finally
650{
651  if (events != null) events.close();
652  if (dc != null) dc.close();
653}
654%>
Note: See TracBrowser for help on using the repository browser.