Ver código fonte

AMBARI-15145. Revamped Filebrowser Design - UI. (dipayanb)

Dipayan Bhowmick 9 anos atrás
pai
commit
b988562aa7
100 arquivos alterados com 3721 adições e 6108 exclusões
  1. 19 53
      contrib/views/files/pom.xml
  2. 211 39
      contrib/views/files/src/main/java/org/apache/ambari/view/filebrowser/FileOperationService.java
  3. 18 5
      contrib/views/files/src/main/java/org/apache/ambari/view/filebrowser/HdfsService.java
  4. 1 1
      contrib/views/files/src/main/java/org/apache/ambari/view/filebrowser/HelpService.java
  5. 4 0
      contrib/views/files/src/main/resources/ui/.bowerrc
  6. 34 0
      contrib/views/files/src/main/resources/ui/.editorconfig
  7. 27 0
      contrib/views/files/src/main/resources/ui/.ember-cli
  8. 22 13
      contrib/views/files/src/main/resources/ui/.gitignore
  9. 33 0
      contrib/views/files/src/main/resources/ui/.jshintrc
  10. 39 0
      contrib/views/files/src/main/resources/ui/.travis.yml
  11. 3 4
      contrib/views/files/src/main/resources/ui/.watchmanconfig
  12. 68 0
      contrib/views/files/src/main/resources/ui/README.md
  13. 0 419
      contrib/views/files/src/main/resources/ui/app/adapter.js
  14. 38 0
      contrib/views/files/src/main/resources/ui/app/adapters/application.js
  15. 66 0
      contrib/views/files/src/main/resources/ui/app/adapters/file.js
  16. 18 1
      contrib/views/files/src/main/resources/ui/app/app.js
  17. BIN
      contrib/views/files/src/main/resources/ui/app/assets/fonts/fontawesome-webfont.eot
  18. 0 195
      contrib/views/files/src/main/resources/ui/app/assets/fonts/fontawesome-webfont.svg
  19. BIN
      contrib/views/files/src/main/resources/ui/app/assets/fonts/fontawesome-webfont.ttf
  20. BIN
      contrib/views/files/src/main/resources/ui/app/assets/fonts/fontawesome-webfont.woff
  21. BIN
      contrib/views/files/src/main/resources/ui/app/assets/fonts/glyphicons-halflings-regular.eot
  22. 0 229
      contrib/views/files/src/main/resources/ui/app/assets/fonts/glyphicons-halflings-regular.svg
  23. BIN
      contrib/views/files/src/main/resources/ui/app/assets/fonts/glyphicons-halflings-regular.ttf
  24. BIN
      contrib/views/files/src/main/resources/ui/app/assets/fonts/glyphicons-halflings-regular.woff
  25. 0 34
      contrib/views/files/src/main/resources/ui/app/assets/index.html
  26. 0 266
      contrib/views/files/src/main/resources/ui/app/assets/javascripts/ember-qunit.js
  27. 0 692
      contrib/views/files/src/main/resources/ui/app/assets/javascripts/jquery.mockjax.js
  28. 0 3
      contrib/views/files/src/main/resources/ui/app/assets/javascripts/modernizr-2.6.2.min.js
  29. 0 2495
      contrib/views/files/src/main/resources/ui/app/assets/javascripts/qunit.js
  30. 0 237
      contrib/views/files/src/main/resources/ui/app/assets/stylesheets/qunit.css
  31. 0 46
      contrib/views/files/src/main/resources/ui/app/assets/tests.html
  32. 0 0
      contrib/views/files/src/main/resources/ui/app/components/.gitkeep
  33. 45 0
      contrib/views/files/src/main/resources/ui/app/components/alert-message-display.js
  34. 32 0
      contrib/views/files/src/main/resources/ui/app/components/alert-message.js
  35. 0 47
      contrib/views/files/src/main/resources/ui/app/components/breadCrumbs.js
  36. 0 29
      contrib/views/files/src/main/resources/ui/app/components/bulkCheckbox.js
  37. 0 65
      contrib/views/files/src/main/resources/ui/app/components/confirmDelete.js
  38. 137 0
      contrib/views/files/src/main/resources/ui/app/components/context-row-menu.js
  39. 126 0
      contrib/views/files/src/main/resources/ui/app/components/copy-modal.js
  40. 130 0
      contrib/views/files/src/main/resources/ui/app/components/delete-modal.js
  41. 165 0
      contrib/views/files/src/main/resources/ui/app/components/directory-viewer.js
  42. 37 0
      contrib/views/files/src/main/resources/ui/app/components/file-row.js
  43. 17 17
      contrib/views/files/src/main/resources/ui/app/components/file-search.js
  44. 69 0
      contrib/views/files/src/main/resources/ui/app/components/files-breadcrumb.js
  45. 98 0
      contrib/views/files/src/main/resources/ui/app/components/files-collection.js
  46. 0 50
      contrib/views/files/src/main/resources/ui/app/components/mkdirInput.js
  47. 126 0
      contrib/views/files/src/main/resources/ui/app/components/move-modal.js
  48. 75 0
      contrib/views/files/src/main/resources/ui/app/components/new-directory.js
  49. 56 0
      contrib/views/files/src/main/resources/ui/app/components/open-preview-modal.js
  50. 116 0
      contrib/views/files/src/main/resources/ui/app/components/permission-modal.js
  51. 0 68
      contrib/views/files/src/main/resources/ui/app/components/popoverDelete.js
  52. 77 0
      contrib/views/files/src/main/resources/ui/app/components/rename-modal.js
  53. 0 92
      contrib/views/files/src/main/resources/ui/app/components/renameInput.js
  54. 0 34
      contrib/views/files/src/main/resources/ui/app/components/sortArrow.js
  55. 0 79
      contrib/views/files/src/main/resources/ui/app/components/toggleContext.js
  56. 92 0
      contrib/views/files/src/main/resources/ui/app/components/upload-file.js
  57. 0 111
      contrib/views/files/src/main/resources/ui/app/components/uploader.js
  58. 73 0
      contrib/views/files/src/main/resources/ui/app/config/files-columns.js
  59. 0 0
      contrib/views/files/src/main/resources/ui/app/controllers/.gitkeep
  60. 4 3
      contrib/views/files/src/main/resources/ui/app/controllers/application.js
  61. 0 49
      contrib/views/files/src/main/resources/ui/app/controllers/chmodModal.js
  62. 0 49
      contrib/views/files/src/main/resources/ui/app/controllers/error.js
  63. 0 117
      contrib/views/files/src/main/resources/ui/app/controllers/file.js
  64. 126 173
      contrib/views/files/src/main/resources/ui/app/controllers/files.js
  65. 11 11
      contrib/views/files/src/main/resources/ui/app/controllers/messages.js
  66. 31 0
      contrib/views/files/src/main/resources/ui/app/controllers/messages/message.js
  67. 0 91
      contrib/views/files/src/main/resources/ui/app/controllers/previewModal.js
  68. 0 0
      contrib/views/files/src/main/resources/ui/app/helpers/.gitkeep
  69. 27 0
      contrib/views/files/src/main/resources/ui/app/helpers/alert-message-context-class.js
  70. 37 0
      contrib/views/files/src/main/resources/ui/app/helpers/alert-message-icon-class.js
  71. 34 0
      contrib/views/files/src/main/resources/ui/app/helpers/get-sorting-icon.js
  72. 34 0
      contrib/views/files/src/main/resources/ui/app/helpers/get-value-from-columns.js
  73. 11 12
      contrib/views/files/src/main/resources/ui/app/helpers/shorten-text.js
  74. 27 0
      contrib/views/files/src/main/resources/ui/app/helpers/show-date.js
  75. 33 0
      contrib/views/files/src/main/resources/ui/app/helpers/size-humanize.js
  76. 25 0
      contrib/views/files/src/main/resources/ui/app/helpers/string-capitalize.js
  77. 42 0
      contrib/views/files/src/main/resources/ui/app/index.html
  78. 0 99
      contrib/views/files/src/main/resources/ui/app/initialize.js
  79. 57 0
      contrib/views/files/src/main/resources/ui/app/mixins/file-operation.js
  80. 82 0
      contrib/views/files/src/main/resources/ui/app/mixins/operation-modal.js
  81. 0 0
      contrib/views/files/src/main/resources/ui/app/models/.gitkeep
  82. 27 0
      contrib/views/files/src/main/resources/ui/app/models/alert.js
  83. 25 25
      contrib/views/files/src/main/resources/ui/app/models/file.js
  84. 13 3
      contrib/views/files/src/main/resources/ui/app/router.js
  85. 0 0
      contrib/views/files/src/main/resources/ui/app/routes/.gitkeep
  86. 78 0
      contrib/views/files/src/main/resources/ui/app/routes/application.js
  87. 0 134
      contrib/views/files/src/main/resources/ui/app/routes/file.js
  88. 61 0
      contrib/views/files/src/main/resources/ui/app/routes/files.js
  89. 5 3
      contrib/views/files/src/main/resources/ui/app/routes/index.js
  90. 11 12
      contrib/views/files/src/main/resources/ui/app/routes/messages.js
  91. 31 0
      contrib/views/files/src/main/resources/ui/app/routes/messages/message.js
  92. 3 3
      contrib/views/files/src/main/resources/ui/app/serializers/file.js
  93. 126 0
      contrib/views/files/src/main/resources/ui/app/services/alert-messages.js
  94. 48 0
      contrib/views/files/src/main/resources/ui/app/services/file-copy.js
  95. 47 0
      contrib/views/files/src/main/resources/ui/app/services/file-move.js
  96. 199 0
      contrib/views/files/src/main/resources/ui/app/services/file-operation.js
  97. 119 0
      contrib/views/files/src/main/resources/ui/app/services/file-preview.js
  98. 54 0
      contrib/views/files/src/main/resources/ui/app/services/file-rename.js
  99. 157 0
      contrib/views/files/src/main/resources/ui/app/services/files-download.js
  100. 64 0
      contrib/views/files/src/main/resources/ui/app/services/files-selection.js

+ 19 - 53
contrib/views/files/pom.xml

@@ -132,47 +132,44 @@
               <directory>${ui.directory}</directory>
               <followSymlinks>false</followSymlinks>
               <includes>
-                <include>public/**</include>
+                <include>tmp/**</include>
+                <!--
                 <include>node_modules/**</include>
                 <include>bower_components/**</include>
+                -->
                 <include>node/**</include>
               </includes>
             </fileset>
           </filesets>
         </configuration>
       </plugin>
+
+      <!-- Building frontend -->
       <plugin>
         <groupId>com.github.eirslett</groupId>
         <artifactId>frontend-maven-plugin</artifactId>
         <version>0.0.16</version>
         <configuration>
-          <workingDirectory>${ui.directory}</workingDirectory>
+          <nodeVersion>v0.12.2</nodeVersion>
+          <npmVersion>1.4.8</npmVersion>
+          <workingDirectory>src/main/resources/ui/</workingDirectory>
         </configuration>
-
         <executions>
           <execution>
             <id>install node and npm</id>
+            <phase>generate-sources</phase>
             <goals>
               <goal>install-node-and-npm</goal>
             </goals>
-            <!-- optional: default phase is "generate-resources" -->
-            <phase>initialize</phase>
-            <configuration>
-              <nodeVersion>v0.10.26</nodeVersion>
-              <npmVersion>1.4.3</npmVersion>
-            </configuration>
           </execution>
           <execution>
             <id>npm install</id>
+            <phase>generate-sources</phase>
             <goals>
               <goal>npm</goal>
             </goals>
-            <phase>generate-resources</phase>
             <configuration>
-              <!-- optional: The default argument is actually "install", so unless
-               you need to run some other npm command, you can remove this whole <configuration>
-               section. -->
-              <arguments>install --unsafe-perm</arguments>
+              <arguments>install --python="${project.basedir}/../src/main/unix/ambari-python-wrap" --unsafe-perm</arguments>
             </configuration>
           </execution>
         </executions>
@@ -180,52 +177,21 @@
       <plugin>
         <artifactId>exec-maven-plugin</artifactId>
         <groupId>org.codehaus.mojo</groupId>
-        <version>1.2.1</version>
+        <version>1.3.2</version>
         <executions>
           <execution>
-            <id>node gyp executable</id>
-            <phase>initialize</phase>
-            <goals>
-              <goal>exec</goal>
-            </goals>
-            <configuration>
-              <skip>${skip.nodegyp.chmod}</skip>
-              <workingDirectory>${ui.directory}</workingDirectory>
-              <executable>chmod</executable>
-              <arguments>
-                <argument>+x</argument>
-                <argument>${ui.directory}/node/npm/bin/node-gyp-bin/node-gyp</argument>
-              </arguments>
-            </configuration>
-          </execution>
-          <execution>
-            <id>Bower install</id>
-            <phase>generate-resources</phase>
-            <goals>
-              <goal>exec</goal>
-            </goals>
-            <configuration>
-              <workingDirectory>${ui.directory}</workingDirectory>
-              <executable>${ui.directory}/node/${node.executable}</executable>
-              <arguments>
-                <argument>${ui.directory}/node_modules/bower/bin/bower</argument>
-                <argument>install</argument>
-                <argument>--allow-root</argument>
-              </arguments>
-            </configuration>
-          </execution>
-          <execution>
-            <id>Brunch build</id>
-            <phase>generate-resources</phase>
+            <id>Files build</id>
+            <phase>generate-sources</phase>
             <goals>
               <goal>exec</goal>
             </goals>
             <configuration>
-              <workingDirectory>${ui.directory}</workingDirectory>
-              <executable>${ui.directory}/node/${node.executable}</executable>
+              <workingDirectory>${basedir}/src/main/resources/ui</workingDirectory>
+              <executable>node/node</executable>
               <arguments>
-                <argument>node_modules/brunch/bin/brunch</argument>
+                <argument>node_modules/.bin/ember</argument>
                 <argument>build</argument>
+                <argument>--environment=production</argument>
               </arguments>
             </configuration>
           </execution>
@@ -266,7 +232,7 @@
     </plugins>
     <resources>
       <resource>
-        <directory>src/main/resources/ui/public</directory>
+        <directory>src/main/resources/ui/dist</directory>
         <filtering>false</filtering>
       </resource>
 

+ 211 - 39
contrib/views/files/src/main/java/org/apache/ambari/view/filebrowser/FileOperationService.java

@@ -19,6 +19,10 @@
 package org.apache.ambari.view.filebrowser;
 
 import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
 
 import javax.ws.rs.*;
 import javax.ws.rs.core.Context;
@@ -90,7 +94,7 @@ public class FileOperationService extends HdfsService {
         result = Response.ok(getApi(context).fileStatusToJSON(api
             .getFileStatus(request.dst)));
       } else {
-        result = Response.ok(new BoolResult(false, "Can't move '" + request.src + "' to '" + request.dst + "'")).status(422);
+        result = Response.ok(new FileOperationResult(false, "Can't move '" + request.src + "' to '" + request.dst + "'")).status(422);
       }
       return result.build();
     } catch (WebApplicationException ex) {
@@ -117,7 +121,7 @@ public class FileOperationService extends HdfsService {
         result = Response.ok(getApi(context).fileStatusToJSON(api
             .getFileStatus(request.path)));
       } else {
-        result = Response.ok(new BoolResult(false, "Can't chmod '" + request.path + "'")).status(422);
+        result = Response.ok(new FileOperationResult(false, "Can't chmod '" + request.path + "'")).status(422);
       }
       return result.build();
     } catch (WebApplicationException ex) {
@@ -127,6 +131,60 @@ public class FileOperationService extends HdfsService {
     }
   }
 
+  /**
+   * Copy file
+   * @param request source and destination request
+   * @return response with success
+   */
+  @POST
+  @Path("/move")
+  @Consumes(MediaType.APPLICATION_JSON)
+  @Produces(MediaType.APPLICATION_JSON)
+  public Response move(final MultiSrcDstFileRequest request,
+                       @Context HttpHeaders headers, @Context UriInfo ui) {
+    try {
+      HdfsApi api = getApi(context);
+      ResponseBuilder result;
+      String message = "";
+
+      List<String> sources = request.sourcePaths;
+      String destination = request.destinationPath;
+      if(sources.isEmpty()) {
+        result = Response.ok(new FileOperationResult(false, "Can't move 0 file/folder to '" + destination + "'")).
+          status(422);
+        return result.build();
+      }
+
+      int index = 0;
+      for (String src : sources) {
+        String fileName = getFileName(src);
+        String finalDestination = getDestination(destination, fileName);
+        try {
+          if (api.rename(src, finalDestination)) {
+            index ++;
+          } else {
+            message = "Failed to move '" + src + "' to '" + finalDestination + "'";
+            break;
+          }
+        } catch (IOException exception) {
+          message = exception.getMessage();
+          logger.error("Failed to move '{}' to '{}'. Exception: {}", src, finalDestination,
+            exception.getMessage());
+          break;
+        }
+      }
+      if (index == sources.size()) {
+        result = Response.ok(new FileOperationResult(true)).status(200);
+      } else {
+        FileOperationResult errorResult = getFailureFileOperationResult(sources, index, message);
+        result = Response.ok(errorResult).status(422);
+      }
+      return result.build();
+    } catch (Exception ex) {
+      throw new ServiceFormattedException(ex.getMessage(), ex);
+    }
+  }
+
   /**
    * Copy file
    * @param request source and destination request
@@ -136,23 +194,42 @@ public class FileOperationService extends HdfsService {
   @Path("/copy")
   @Consumes(MediaType.APPLICATION_JSON)
   @Produces(MediaType.APPLICATION_JSON)
-  public Response copy(final SrcDstFileRequest request,
+  public Response copy(final MultiSrcDstFileRequest request,
                        @Context HttpHeaders headers, @Context UriInfo ui) {
     try {
       HdfsApi api = getApi(context);
       ResponseBuilder result;
-      try {
-        api.copy(request.src, request.dst);
+      String message = "";
 
-        result = Response.ok(getApi(context).fileStatusToJSON(api
-            .getFileStatus(request.dst)));
-      } catch (HdfsApiException e) {
-        result = Response.ok(new BoolResult(false, "Can't copy '" + request.src + "' to '" + request.dst + "'")).
-            status(422);
+      List<String> sources = request.sourcePaths;
+      String destination = request.destinationPath;
+      if(sources.isEmpty()) {
+        result = Response.ok(new FileOperationResult(false, "Can't copy 0 file/folder to '" + destination + "'")).
+          status(422);
+        return result.build();
+      }
+
+      int index = 0;
+      for (String src : sources) {
+        String fileName = getFileName(src);
+        String finalDestination = getDestination(destination, fileName);
+        try {
+          api.copy(src, finalDestination);
+          index ++;
+        } catch (IOException|HdfsApiException exception) {
+          message = exception.getMessage();
+          logger.error("Failed to copy '{}' to '{}'. Exception: {}", src, finalDestination,
+            exception.getMessage());
+          break;
+        }
+      }
+      if (index == sources.size()) {
+        result = Response.ok(new FileOperationResult(true)).status(200);
+      } else {
+        FileOperationResult errorResult = getFailureFileOperationResult(sources, index, message);
+        result = Response.ok(errorResult).status(422);
       }
       return result.build();
-    } catch (WebApplicationException ex) {
-      throw ex;
     } catch (Exception ex) {
       throw new ServiceFormattedException(ex.getMessage(), ex);
     }
@@ -173,7 +250,7 @@ public class FileOperationService extends HdfsService {
       if (api.mkdir(request.path)) {
         result = Response.ok(getApi(context).fileStatusToJSON(api.getFileStatus(request.path)));
       } else {
-        result = Response.ok(new BoolResult(false, "Can't create dir '" + request.path + "'")).status(422);
+        result = Response.ok(new FileOperationResult(false, "Can't create dir '" + request.path + "'")).status(422);
       }
       return result.build();
     } catch (WebApplicationException ex) {
@@ -194,7 +271,7 @@ public class FileOperationService extends HdfsService {
     try {
       HdfsApi api = getApi(context);
       api.emptyTrash();
-      return Response.ok(new BoolResult(true)).build();
+      return Response.ok(new FileOperationResult(true)).build();
     } catch (WebApplicationException ex) {
       throw ex;
     } catch (Exception ex) {
@@ -211,31 +288,49 @@ public class FileOperationService extends HdfsService {
   @Path("/moveToTrash")
   @Consumes(MediaType.APPLICATION_JSON)
   @Produces(MediaType.APPLICATION_JSON)
-  public Response moveToTrash(RemoveRequest request) {
+  public Response moveToTrash(MultiRemoveRequest request) {
     try {
       ResponseBuilder result;
-
       HdfsApi api = getApi(context);
       String trash = api.getTrashDirPath();
+      String message = "";
 
-      if (!api.exists(trash)) {
-        if (!api.mkdir(trash)) {
-          result = Response.ok(new BoolResult(false, "Trash dir does not exists. Can't create dir for trash '" + trash + "'")).status(422);
-          return result.build();
+      if (request.paths.size() == 0) {
+        result = Response.ok(new FileOperationResult(false, "No path entries provided.")).status(422);
+      } else {
+        if (!api.exists(trash)) {
+          if (!api.mkdir(trash)) {
+            result = Response.ok(new FileOperationResult(false, "Trash dir does not exists. Can't create dir for " +
+              "trash '" + trash + "'")).status(422);
+            return result.build();
+          }
         }
-      }
 
-      String trashFilePath = api.getTrashDirPath(request.path);
-
-      if (api.rename(request.path, trashFilePath)) {
-        result = Response.ok(getApi(context).fileStatusToJSON(api
-            .getFileStatus(trashFilePath)));
-      } else {
-        result = Response.ok(new BoolResult(false, "Can't move file to '" + trashFilePath + "'")).status(422);
+        int index = 0;
+        for (MultiRemoveRequest.PathEntry entry : request.paths) {
+          String trashFilePath = api.getTrashDirPath(entry.path);
+          try {
+            if (api.rename(entry.path, trashFilePath)) {
+              index ++;
+            } else {
+              message = "Failed to move '" + entry.path + "' to '" + trashFilePath + "'";
+              break;
+            }
+          } catch (IOException exception) {
+            message = exception.getMessage();
+            logger.error("Failed to move '{}' to '{}'. Exception: {}", entry.path, trashFilePath,
+              exception.getMessage());
+            break;
+          }
+        }
+        if (index == request.paths.size()) {
+          result = Response.ok(new FileOperationResult(true)).status(200);
+        } else {
+          FileOperationResult errorResult = getFailureFileOperationResult(getPathsFromPathsEntries(request.paths), index, message);
+          result = Response.ok(errorResult).status(422);
+        }
       }
       return result.build();
-    } catch (WebApplicationException ex) {
-      throw ex;
     } catch (Exception ex) {
       throw new ServiceFormattedException(ex.getMessage(), ex);
     }
@@ -250,24 +345,84 @@ public class FileOperationService extends HdfsService {
   @Path("/remove")
   @Consumes(MediaType.APPLICATION_JSON)
   @Produces(MediaType.APPLICATION_JSON)
-  public Response remove(RemoveRequest request, @Context HttpHeaders headers,
+  public Response remove(MultiRemoveRequest request, @Context HttpHeaders headers,
                          @Context UriInfo ui) {
     try {
       HdfsApi api = getApi(context);
       ResponseBuilder result;
-      if (api.delete(request.path, request.recursive)) {
-        result = Response.ok(new BoolResult(true)).status(204);
+      String message = "";
+      if(request.paths.size() == 0) {
+        result = Response.ok(new FileOperationResult(false, "No path entries provided."));
       } else {
-        result = Response.ok(new BoolResult(false, "Can't remove '" + request.path + "'")).status(422);
+        int index = 0;
+        for (MultiRemoveRequest.PathEntry entry : request.paths) {
+          try {
+            if (api.delete(entry.path, entry.recursive)) {
+              index++;
+            } else {
+              message = "Failed to remove '" + entry.path + "'";
+              break;
+            }
+          } catch (IOException exception) {
+            message = exception.getMessage();
+            logger.error("Failed to remove '{}'. Exception: {}", entry.path, exception.getMessage());
+            break;
+          }
+
+        }
+        if (index == request.paths.size()) {
+          result = Response.ok(new FileOperationResult(true)).status(200);
+        } else {
+          FileOperationResult errorResult = getFailureFileOperationResult(getPathsFromPathsEntries(request.paths), index, message);
+          result = Response.ok(errorResult).status(422);
+        }
       }
       return result.build();
-    } catch (WebApplicationException ex) {
-      throw ex;
     } catch (Exception ex) {
       throw new ServiceFormattedException(ex.getMessage(), ex);
     }
   }
 
+  private List<String> getPathsFromPathsEntries(List<MultiRemoveRequest.PathEntry> paths) {
+    List<String> entries = new ArrayList<>();
+    for(MultiRemoveRequest.PathEntry path: paths) {
+      entries.add(path.path);
+    }
+    return entries;
+  }
+
+  private FileOperationResult getFailureFileOperationResult(List<String> paths, int failedIndex, String message) {
+    List<String> succeeded = new ArrayList<>();
+    List<String> unprocessed = new ArrayList<>();
+    List<String> failed = new ArrayList<>();
+    ListIterator<String> iter = paths.listIterator();
+    while (iter.hasNext()) {
+      int index = iter.nextIndex();
+      String path = iter.next();
+      if (index < failedIndex) {
+        succeeded.add(path);
+      } else if (index == failedIndex) {
+        failed.add(path);
+      } else {
+        unprocessed.add(path);
+      }
+    }
+    return new FileOperationResult(false, message, succeeded, failed, unprocessed);
+  }
+
+  private String getDestination(String baseDestination, String fileName) {
+    if(baseDestination.endsWith("/")) {
+      return baseDestination + fileName;
+    } else {
+      return baseDestination + "/" + fileName;
+    }
+  }
+
+  private String getFileName(String srcPath) {
+    return srcPath.substring(srcPath.lastIndexOf('/') + 1);
+  }
+
+
   /**
    * Wrapper for json mapping of mkdir request
    */
@@ -300,13 +455,30 @@ public class FileOperationService extends HdfsService {
     public String dst;
   }
 
+  /**
+   * Wrapper for json mapping of request with multiple
+   * source and destination
+   */
+  @XmlRootElement
+  public static class MultiSrcDstFileRequest {
+    @XmlElement(nillable = false, required = true)
+    public List<String> sourcePaths = new ArrayList<>();
+    @XmlElement(nillable = false, required = true)
+    public String destinationPath;
+  }
+
   /**
    * Wrapper for json mapping of remove request
    */
   @XmlRootElement
-  public static class RemoveRequest {
+  public static class MultiRemoveRequest {
     @XmlElement(nillable = false, required = true)
-    public String path;
-    public boolean recursive;
+    public List<PathEntry> paths = new ArrayList<>();
+    public static class PathEntry {
+      @XmlElement(nillable = false, required = true)
+      public String path;
+      public boolean recursive;
+    }
+
   }
 }

+ 18 - 5
contrib/views/files/src/main/java/org/apache/ambari/view/filebrowser/HdfsService.java

@@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -50,20 +51,32 @@ public abstract class HdfsService {
   }
 
   /**
-   * Wrapper for json mapping of bool response
+   * Wrapper for json mapping of result of Multi Remove Request
    */
   @XmlRootElement
-  public static class BoolResult{
+  public static class FileOperationResult {
     public boolean success;
     public String message;
-    public BoolResult(boolean success){
+    public List<String> succeeded;
+    public List<String> failed;
+    public List<String> unprocessed;
+
+    public FileOperationResult(boolean success) {
       this.success = success;
     }
 
-    public BoolResult(boolean success, String message){
-      this.success = success;
+    public FileOperationResult(boolean success, String message) {
+      this(success);
       this.message = message;
     }
+
+    public FileOperationResult(boolean success, String message, List<String> succeeded, List<String> failed, List<String> unprocessed) {
+      this(success, message);
+      this.succeeded = succeeded;
+      this.failed = failed;
+      this.unprocessed = unprocessed;
+    }
+
   }
 
   private HdfsApi _api = null;

+ 1 - 1
contrib/views/files/src/main/java/org/apache/ambari/view/filebrowser/HelpService.java

@@ -109,7 +109,7 @@ public class HelpService extends HdfsService {
   public Response trashEnabled() {
     try {
       HdfsApi api = getApi(context);
-      return Response.ok(new BoolResult(api.trashEnabled())).build();
+      return Response.ok(new FileOperationResult(api.trashEnabled())).build();
     } catch (WebApplicationException ex) {
       throw ex;
     } catch (Exception ex) {

+ 4 - 0
contrib/views/files/src/main/resources/ui/.bowerrc

@@ -0,0 +1,4 @@
+{
+  "directory": "bower_components",
+  "analytics": false
+}

+ 34 - 0
contrib/views/files/src/main/resources/ui/.editorconfig

@@ -0,0 +1,34 @@
+# EditorConfig helps developers define and maintain consistent
+# coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+
+[*]
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+indent_style = space
+indent_size = 2
+
+[*.js]
+indent_style = space
+indent_size = 2
+
+[*.hbs]
+insert_final_newline = false
+indent_style = space
+indent_size = 2
+
+[*.css]
+indent_style = space
+indent_size = 2
+
+[*.html]
+indent_style = space
+indent_size = 2
+
+[*.{diff,md}]
+trim_trailing_whitespace = false

+ 27 - 0
contrib/views/files/src/main/resources/ui/.ember-cli

@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+{
+  /**
+    Ember CLI sends analytics information by default. The data is completely
+    anonymous, but there are times when you might want to disable this behavior.
+
+    Setting `disableAnalytics` to true will prevent any data from being sent.
+  */
+  "disableAnalytics": false
+}

+ 22 - 13
contrib/views/files/src/main/resources/ui/.gitignore

@@ -1,3 +1,24 @@
+# See http://help.github.com/ignore-files/ for more about ignoring files.
+
+# compiled output
+/dist
+/tmp
+
+# dependencies
+/node_modules
+/bower_components
+node/
+
+# misc
+/.sass-cache
+/connect.lock
+/coverage/*
+/libpeerconnection.log
+npm-debug.log
+testem.log
+
+/.idea
+
 # Numerous always-ignore extensions
 *.diff
 *.err
@@ -16,19 +37,7 @@
 .project
 .settings
 .tmproj
+dist
 nbproject
 Thumbs.db
 
-# NPM packages folder.
-node_modules/
-
-bower_components/
-
-node/
-
-# Brunch folder for temporary files.
-tmp/
-
-public/
-
-_generators/

+ 33 - 0
contrib/views/files/src/main/resources/ui/.jshintrc

@@ -0,0 +1,33 @@
+{
+  "predef": [
+    "document",
+    "window",
+    "-Promise",
+    "moment"
+  ],
+  "browser": true,
+  "boss": true,
+  "curly": true,
+  "debug": false,
+  "devel": true,
+  "eqeqeq": true,
+  "evil": true,
+  "forin": false,
+  "immed": false,
+  "laxbreak": false,
+  "newcap": true,
+  "noarg": true,
+  "noempty": false,
+  "nonew": false,
+  "nomen": false,
+  "onevar": false,
+  "plusplus": false,
+  "regexp": false,
+  "undef": true,
+  "sub": true,
+  "strict": false,
+  "white": false,
+  "eqnull": true,
+  "esnext": true,
+  "unused": true
+}

+ 39 - 0
contrib/views/files/src/main/resources/ui/.travis.yml

@@ -0,0 +1,39 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+---
+language: node_js
+node_js:
+  - "0.12"
+
+sudo: false
+
+cache:
+  directories:
+    - node_modules
+
+before_install:
+  - export PATH=/usr/local/phantomjs-2.0.0/bin:$PATH
+  - "npm config set spin false"
+  - "npm install -g npm@^2"
+
+install:
+  - npm install -g bower
+  - npm install
+  - bower install
+
+script:
+  - npm test

+ 3 - 4
contrib/views/files/src/main/resources/ui/app/routes/error.js → contrib/views/files/src/main/resources/ui/.watchmanconfig

@@ -15,7 +15,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-var App = require('app');
-
-App.ErrorRoute = Em.Route.extend({});
+{
+  "ignore_dirs": ["tmp", "dist"]
+}

+ 68 - 0
contrib/views/files/src/main/resources/ui/README.md

@@ -0,0 +1,68 @@
+<!---
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You under the Apache License, Version 2.0
+(the "License"); you may not use this file except in compliance with
+the License.  You may obtain a copy of the License at [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+-->
+
+# Files-view
+
+This README outlines the details of collaborating on this Ember application.
+A short introduction of this app could easily go here.
+
+## Prerequisites
+
+You will need the following things properly installed on your computer.
+
+* [Git](http://git-scm.com/)
+* [Node.js](http://nodejs.org/) (with NPM)
+* [Bower](http://bower.io/)
+* [Ember CLI](http://www.ember-cli.com/)
+* [PhantomJS](http://phantomjs.org/)
+
+## Installation
+
+* `git clone <repository-url>` this repository
+* change into the new directory
+* `npm install`
+* `bower install`
+
+## Running / Development
+
+* `ember server`
+* Visit your app at [http://localhost:4200](http://localhost:4200).
+
+### Code Generators
+
+Make use of the many generators for code, try `ember help generate` for more details
+
+### Running Tests
+
+* `ember test`
+* `ember test --server`
+
+### Building
+
+* `ember build` (development)
+* `ember build --environment production` (production)
+
+### Deploying
+
+Specify what it takes to deploy your app.
+
+## Further Reading / Useful Links
+
+* [ember.js](http://emberjs.com/)
+* [ember-cli](http://www.ember-cli.com/)
+* Development Browser Extensions
+  * [ember inspector for chrome](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
+  * [ember inspector for firefox](https://addons.mozilla.org/en-US/firefox/addon/ember-inspector/)
+

+ 0 - 419
contrib/views/files/src/main/resources/ui/app/adapter.js

@@ -1,419 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-App = require('app');
-
-function promiseArray(promise, label) {
-  return Ember.ArrayProxy.extend(Ember.PromiseProxyMixin).create({
-    promise: Ember.RSVP.Promise.cast(promise, label)
-  });
-}
-
-
-function serializerForAdapter(adapter, type) {
-  var serializer = adapter.serializer,
-      defaultSerializer = adapter.defaultSerializer,
-      container = adapter.container;
-
-  if (container && serializer === undefined) {
-    serializer = serializerFor(container, type.typeKey, defaultSerializer);
-  }
-
-  if (serializer === null || serializer === undefined) {
-    serializer = {
-      extract: function(store, type, payload) { return payload; }
-    };
-  }
-
-  return serializer;
-}
-
-function serializerFor(container, type, defaultSerializer) {
-  return container.lookup('serializer:'+type) ||
-                 container.lookup('serializer:application') ||
-                 container.lookup('serializer:' + defaultSerializer) ||
-                 container.lookup('serializer:-default');
-}
-
-function _listdir(adapter, store, type, query, recordArray) {
-  var promise = adapter.listdir(store, type, query, recordArray),
-      serializer = serializerForAdapter(adapter, type),
-      label = "";
-
-  return Ember.RSVP.Promise.cast(promise, label).then(function(adapterPayload) {
-    var payload = serializer.extract(store, type, adapterPayload, null, 'findAll');
-
-    Ember.assert("The response from a findQuery must be an Array, not " + Ember.inspect(payload), Ember.typeOf(payload) === 'array');
-
-    recordArray.load(payload);
-    return recordArray;
-  }, null, "DS: Extract payload of findQuery " + type);
-}
-
-function _move(adapter, store, record, query) {
-  var type = store.modelFor('file'),
-      promise = adapter.move(store, type, record, query),
-      serializer = serializerForAdapter(adapter, type),
-      label = "";
-
-  return promise.then(function(adapterPayload) {
-    var payload;
-
-    if (adapterPayload) {
-      payload = serializer.extractSingle(store, type, adapterPayload);
-    } else {
-      payload = adapterPayload;
-    }
-
-    //TODO very shady activity :/
-    if (typeof record == 'object') {
-      store.unloadRecord(record);
-    }
-
-    return store.push('file', payload);
-  }, function(reason) {
-
-    throw reason;
-  }, label);
-}
-
-function _mkdir(adapter, store, type, query) {
-  var promise = adapter.mkdir(store, type, query),
-      serializer = serializerForAdapter(adapter, type),
-      label = "";
-
-  return promise.then(function(adapterPayload) {
-    var payload;
-
-    if (adapterPayload) {
-      payload = serializer.extractSingle(store, type, adapterPayload);
-    } else {
-      payload = adapterPayload;
-    }
-
-    return store.push('file', payload);
-  }, function(reason) {
-    throw reason;
-  }, label);
-}
-
-function _remove(adapter, store, record, query, toTrash) {
-  var type = record.constructor;
-  var method = (toTrash)?'moveToTrash':'remove';
-  var promise = adapter[method](store, type, query),
-      serializer = serializerForAdapter(adapter, type),
-      label = "";
-
-  return promise.then(function(adapterPayload) {
-    store.unloadRecord(record);
-    return record;
-  }, function(reason) {
-    if (reason instanceof DS.InvalidError) {
-      store.recordWasInvalid(record, reason.errors);
-    } else {
-      record.rollback();
-      //store.recordWasError(record, reason);
-    }
-
-    throw reason;
-  }, label);
-}
-
-Ember.Inflector.inflector.uncountable('fileops');
-Ember.Inflector.inflector.uncountable('download');
-Ember.Inflector.inflector.uncountable('upload');
-
-function getNamespaceUrl() {
-  var parts = window.location.pathname.match(/\/[^\/]*/g);
-  var view = parts[1];
-  var version = '/versions' + parts[2];
-  var instance = parts[3];
-  if (parts.length == 4) { // version is not present
-    instance = parts[2];
-    version = '';
-  }
-  return 'api/v1/views' + view + version + '/instances' + instance + '/';
-}
-
-App.ApplicationStore = DS.Store.extend({
-  adapter: DS.RESTAdapter.extend({
-    namespace: getNamespaceUrl() + 'resources/files',
-    headers: {
-      'X-Requested-By': 'ambari'
-    },
-
-    /**
-      @method ajaxOptions
-      @param {String} url
-      @param {String} type The request type GET, POST, PUT, DELETE etc.
-      @param {Object} hash
-      @return {Object} hash
-    */
-    ajaxOptions: function(url, type, options) {
-      var hash = options || {};
-      hash.url = url;
-      hash.type = type;
-      hash.dataType = 'json';
-      hash.context = this;
-
-      if (hash.data && type !== 'GET') {
-        hash.contentType = 'application/json; charset=utf-8';
-        hash.data = JSON.stringify(hash.data);
-      }
-
-      var headers = this.get('headers');
-
-      if ((navigator.userAgent.indexOf("MSIE") != -1) || (!!navigator.userAgent.match(/Trident.*rv[ :]*11\./))) {
-        headers['Cache-Control'] = 'no-cache, no-store, must-revalidate';
-        headers['Pragma'] = 'no-cache';
-        headers['Expires'] = '0';
-      }
-
-      if (headers !== undefined) {
-        hash.beforeSend = function (xhr) {
-          Ember.keys(headers).forEach(function (key) {
-            xhr.setRequestHeader(key, headers[key]);
-          });
-        };
-      }
-
-      return hash;
-    },
-    listdir: function(store, type, query) {
-      return this.ajax(this.buildURL('fileops','listdir'), 'GET', { data: query });
-    },
-    move:function (store, type, record, query) {
-      return this.ajax(this.buildURL('fileops','rename'), 'POST', { data: query });
-    },
-    updateRecord:function (store, type, record) {
-      var query = {
-        "path":record.get('path'),
-        "mode":record.get('permission')
-      };
-      return this.ajax(this.buildURL('fileops','chmod'), 'POST', { data: query });
-    },
-    mkdir:function (store, type, query) {
-      return this.ajax(this.buildURL('fileops','mkdir'), 'PUT', { data: query });
-    },
-    remove:function (store, type, query) {
-      return this.ajax(this.buildURL('fileops','remove'), 'DELETE', { data: query });
-    },
-    moveToTrash:function (store, type, query) {
-      return this.ajax(this.buildURL('fileops','moveToTrash'), 'DELETE', { data: query });
-    },
-    downloadUrl:function (option, query) {
-      return [this.buildURL('download',option),Em.$.param(query)].join('?');
-    },
-    linkFor:function (option, query) {
-      return this.ajax(this.buildURL('download',[option,'generate-link'].join('/')), 'POST', { data: query });
-    }
-  }),
-  listdir:function (path) {
-    var query = {path: path};
-    var type = this.modelFor('file');
-    var array = this.recordArrayManager
-      .createAdapterPopulatedRecordArray(type, query);
-    this.recordArrayManager.registerFilteredRecordArray(array, type);
-
-    var adapter = this.adapterFor(type);
-
-    Ember.assert("You tried to load a query but you have no adapter (for " + type + ")", adapter);
-    Ember.assert("You tried to load a query but your adapter does not implement `listdir`", adapter.listdir);
-
-    return promiseArray(_listdir(adapter, this, type, query, array));
-  },
-  move:function (record, path) {
-    var oldpath = (typeof record === 'string')?record:record.get('id');
-    var query = {
-      "src":oldpath,
-      "dst":path
-    };
-    var promiseLabel = "DS: Model#move " + this;
-    var resolver = Ember.RSVP.defer(promiseLabel);
-    var adapter = this.adapterFor(record.constructor);
-
-    resolver.resolve(_move(adapter, this, record, query));
-
-    return DS.PromiseObject.create({ promise: resolver.promise });
-  },
-  chmod:function (record, path) {
-    return record.save();
-  },
-  mkdir:function (path) {
-    var query = {
-      "path":path
-    };
-    var type = this.modelFor('file');
-    var promiseLabel = "DS: Model#mkdir " + this;
-    var resolver = Ember.RSVP.defer(promiseLabel);
-    var adapter = this.adapterFor(type);
-
-    resolver.resolve(_mkdir(adapter, this, type, query));
-
-    return DS.PromiseObject.create({ promise: resolver.promise });
-  },
-  remove:function (record, toTrash) {
-    var query = {
-      "path":record.get('path'),
-      "recursive":true
-    };
-    var type = this.modelFor('file');
-    var promiseLabel = "DS: Model#remove " + this;
-    var resolver = Ember.RSVP.defer(promiseLabel);
-    var adapter = this.adapterFor(type);
-
-    record.deleteRecord();
-    resolver.resolve(_remove(adapter, this, record, query, toTrash));
-
-    return DS.PromiseObject.create({ promise: resolver.promise });
-  },
-  /**
-   * get dowload link
-   * @param  {Array} files     records for download
-   * @param  {String} option            browse, zip or concat
-   * @param  {Boolean} downloadArg
-   * @return {Promise}
-   */
-  linkFor:function (files, option, downloadArg, checkperm) {
-    var resolver = Ember.RSVP.defer('promiseLabel');
-    var query, adapter = this.adapterFor(this.modelFor('file')),
-        download = downloadArg || true,
-        checkPermission = checkperm || false;
-        option = option || "browse";
-
-    if (option == 'browse') {
-      query = { "path": (files.get('firstObject.path') || files.get('id')), "download": download, "checkperm": checkPermission };
-      resolver.resolve(adapter.downloadUrl('browse',query));
-      return resolver.promise;
-    }
-
-    query = {
-      "entries": [],
-      "download": download
-    };
-
-    files.forEach(function (item) {
-      query.entries.push(item.get('path'));
-    });
-
-    resolver.resolve(adapter.linkFor(option, query));
-
-    return resolver.promise.then(function(response) {
-      return adapter.downloadUrl(option,response);
-    }, function(reason) {
-      throw reason;
-    });
-  }
-});
-
-App.FileSerializer = DS.RESTSerializer.extend({
-  primaryKey:'path',
-  extractSingle: function(store, type, payload, id, requestType) {
-    payload = {'files': payload};
-    return this._super(store, type, payload, id, requestType);
-  },
-  extractChmod:function(store, type, payload, id, requestType) {
-    return this.extractSingle(store, type, payload, id, requestType);
-  }
-});
-
-App.Uploader = Ember.Uploader.create({
-  url: '',
-  type:'PUT',
-  upload: function(file,extraData) {
-    var data = this.setupFormData(file,extraData);
-    var url  = this.get('url');
-    var type = this.get('type');
-    var self = this;
-
-    this.set('isUploading', true);
-
-    return this.ajax(url, data, type)
-      .then(Em.run.bind(this,this.uploadSuccess),Em.run.bind(this,this.uploadFailed));
-  },
-  uploadSuccess:function(respData) {
-    this.didUpload(respData);
-    return respData;
-  },
-  uploadFailed:function (error) {
-    this.set('isUploading', false);
-    this.sendAlert(error);
-    return error;
-  },
-  sendAlert: Em.K,
-  ajax: function(url, params, method) {
-    var self = this;
-    var settings = {
-      url: url,
-      type: method || 'POST',
-      contentType: false,
-      processData: false,
-      xhr: function() {
-        var xhr = Ember.$.ajaxSettings.xhr();
-        xhr.upload.onprogress = function(e) {
-          self.didProgress(e);
-        };
-        return xhr;
-      },
-      beforeSend:function (xhr) {
-        xhr.setRequestHeader('X-Requested-By', 'ambari');
-      },
-      data: params
-    };
-
-    return this._ajax(settings);
-  }
-});
-
-App.IsodateTransform = DS.Transform.extend({
-  deserialize: function (serialized) {
-    if (serialized) {
-      return moment.utc(serialized).toDate();
-    }
-    return serialized;
-  },
-  serialize: function (deserialized) {
-    if (deserialized) {
-      return moment(deserialized).format('X');
-    }
-    return deserialized;
-  }
-});
-
-Ember.Handlebars.registerBoundHelper('showDate', function(date,format) {
-  return moment(date).format(format);
-});
-
-Ember.Handlebars.registerBoundHelper('showDateUnix', function(date,format) {
-  return moment.unix(date).format(format);
-});
-
-Ember.Handlebars.registerBoundHelper('capitalize', function(string) {
-  return string.capitalize();
-});
-
-Ember.Handlebars.registerBoundHelper('humanSize', function(fileSizeInBytes) {
-  var i = -1;
-  var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
-  do {
-      fileSizeInBytes = fileSizeInBytes / 1024;
-      i++;
-  } while (fileSizeInBytes > 1024);
-
-  return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
-});

+ 38 - 0
contrib/views/files/src/main/resources/ui/app/adapters/application.js

@@ -0,0 +1,38 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import DS from 'ember-data';
+import Ember from 'ember';
+
+export default DS.RESTAdapter.extend({
+  namespace: Ember.computed(function() {
+    var parts = window.location.pathname.match(/\/[^\/]*/g);
+    var view = parts[1];
+    var version = '/versions' + parts[2];
+    var instance = parts[3];
+    if (parts.length === 4) { // version is not present
+      instance = parts[2];
+      version = '';
+    }
+    return 'api/v1/views' + view + version + '/instances' + instance + '/resources/files/fileops';
+  }),
+
+  headers: {
+    'X-Requested-By': 'ambari'
+  }
+});

+ 66 - 0
contrib/views/files/src/main/resources/ui/app/adapters/file.js

@@ -0,0 +1,66 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import ApplicationAdapter from './application';
+
+export default ApplicationAdapter.extend({
+  pathForType: function(type) {
+    if (type === 'file') {
+      return 'listdir';
+    }
+  },
+  parseErrorResponse: function(responseText) {
+    var json = this._super(responseText);
+    if((typeof json) === 'object') {
+      var error = {};
+      if (Ember.isPresent(json.success)) {
+        // This error is for Invalid Error response (422)
+        error.success = json.success;
+        error.message = json.message;
+
+        delete json.success;
+        delete json.message;
+
+        if(Ember.isArray(json.succeeded)) {
+          error.succeeded = json.succeeded;
+          delete json.succeeded;
+        }
+        if (Ember.isArray(json.failed)) {
+          error.failed = json.failed;
+          delete json.failed;
+        }
+        if (Ember.isArray(json.unprocessed)) {
+          error.unprocessed = json.unprocessed;
+          delete json.unprocessed;
+        }
+      } else {
+        // Other errors
+        error.message = json.message;
+        error.trace = json.trace;
+        error.status = json.status;
+        delete json.trace;
+        delete json.status;
+        delete json.message;
+      }
+      json.errors = [error];
+    }
+
+    return json;
+  }
+});

+ 18 - 1
contrib/views/files/src/main/resources/ui/app/app.js

@@ -16,4 +16,21 @@
  * limitations under the License.
  */
 
-module.exports = Em.Application.create();
+import Ember from 'ember';
+import Resolver from 'ember-resolver';
+import loadInitializers from 'ember/load-initializers';
+import config from './config/environment';
+
+let App;
+
+Ember.MODEL_FACTORY_INJECTIONS = true;
+
+App = Ember.Application.extend({
+  modulePrefix: config.modulePrefix,
+  podModulePrefix: config.podModulePrefix,
+  Resolver: Resolver
+});
+
+loadInitializers(App, config.modulePrefix);
+
+export default App;

BIN
contrib/views/files/src/main/resources/ui/app/assets/fonts/fontawesome-webfont.eot


Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 195
contrib/views/files/src/main/resources/ui/app/assets/fonts/fontawesome-webfont.svg


BIN
contrib/views/files/src/main/resources/ui/app/assets/fonts/fontawesome-webfont.ttf


BIN
contrib/views/files/src/main/resources/ui/app/assets/fonts/fontawesome-webfont.woff


BIN
contrib/views/files/src/main/resources/ui/app/assets/fonts/glyphicons-halflings-regular.eot


+ 0 - 229
contrib/views/files/src/main/resources/ui/app/assets/fonts/glyphicons-halflings-regular.svg

@@ -1,229 +0,0 @@
-<?xml version="1.0" standalone="no"?>
-<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
-<svg xmlns="http://www.w3.org/2000/svg">
-<metadata></metadata>
-<defs>
-<font id="glyphicons_halflingsregular" horiz-adv-x="1200" >
-<font-face units-per-em="1200" ascent="960" descent="-240" />
-<missing-glyph horiz-adv-x="500" />
-<glyph />
-<glyph />
-<glyph unicode="&#xd;" />
-<glyph unicode=" " />
-<glyph unicode="*" d="M100 500v200h259l-183 183l141 141l183 -183v259h200v-259l183 183l141 -141l-183 -183h259v-200h-259l183 -183l-141 -141l-183 183v-259h-200v259l-183 -183l-141 141l183 183h-259z" />
-<glyph unicode="+" d="M0 400v300h400v400h300v-400h400v-300h-400v-400h-300v400h-400z" />
-<glyph unicode="&#xa0;" />
-<glyph unicode="&#x2000;" horiz-adv-x="652" />
-<glyph unicode="&#x2001;" horiz-adv-x="1304" />
-<glyph unicode="&#x2002;" horiz-adv-x="652" />
-<glyph unicode="&#x2003;" horiz-adv-x="1304" />
-<glyph unicode="&#x2004;" horiz-adv-x="434" />
-<glyph unicode="&#x2005;" horiz-adv-x="326" />
-<glyph unicode="&#x2006;" horiz-adv-x="217" />
-<glyph unicode="&#x2007;" horiz-adv-x="217" />
-<glyph unicode="&#x2008;" horiz-adv-x="163" />
-<glyph unicode="&#x2009;" horiz-adv-x="260" />
-<glyph unicode="&#x200a;" horiz-adv-x="72" />
-<glyph unicode="&#x202f;" horiz-adv-x="260" />
-<glyph unicode="&#x205f;" horiz-adv-x="326" />
-<glyph unicode="&#x20ac;" d="M100 500l100 100h113q0 47 5 100h-218l100 100h135q37 167 112 257q117 141 297 141q242 0 354 -189q60 -103 66 -209h-181q0 55 -25.5 99t-63.5 68t-75 36.5t-67 12.5q-24 0 -52.5 -10t-62.5 -32t-65.5 -67t-50.5 -107h379l-100 -100h-300q-6 -46 -6 -100h406l-100 -100 h-300q9 -74 33 -132t52.5 -91t62 -54.5t59 -29t46.5 -7.5q29 0 66 13t75 37t63.5 67.5t25.5 96.5h174q-31 -172 -128 -278q-107 -117 -274 -117q-205 0 -324 158q-36 46 -69 131.5t-45 205.5h-217z" />
-<glyph unicode="&#x2212;" d="M200 400h900v300h-900v-300z" />
-<glyph unicode="&#x2601;" d="M-14 494q0 -80 56.5 -137t135.5 -57h750q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5z" />
-<glyph unicode="&#x2709;" d="M0 100l400 400l200 -200l200 200l400 -400h-1200zM0 300v600l300 -300zM0 1100l600 -603l600 603h-1200zM900 600l300 300v-600z" />
-<glyph unicode="&#x270f;" d="M-13 -13l333 112l-223 223zM187 403l214 -214l614 614l-214 214zM887 1103l214 -214l99 92q13 13 13 32.5t-13 33.5l-153 153q-15 13 -33 13t-33 -13z" />
-<glyph unicode="&#xe000;" horiz-adv-x="500" d="M0 0z" />
-<glyph unicode="&#xe001;" d="M0 1200h1200l-500 -550v-550h300v-100h-800v100h300v550z" />
-<glyph unicode="&#xe002;" d="M14 84q18 -55 86 -75.5t147 5.5q65 21 109 69t44 90v606l600 155v-521q-64 16 -138 -7q-79 -26 -122.5 -83t-25.5 -111q17 -55 85.5 -75.5t147.5 4.5q70 23 111.5 63.5t41.5 95.5v881q0 10 -7 15.5t-17 2.5l-752 -193q-10 -3 -17 -12.5t-7 -19.5v-689q-64 17 -138 -7 q-79 -25 -122.5 -82t-25.5 -112z" />
-<glyph unicode="&#xe003;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233z" />
-<glyph unicode="&#xe005;" d="M100 784q0 64 28 123t73 100.5t104.5 64t119 20.5t120 -38.5t104.5 -104.5q48 69 109.5 105t121.5 38t118.5 -20.5t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-149.5 152.5t-126.5 127.5 t-94 124.5t-33.5 117.5z" />
-<glyph unicode="&#xe006;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1z" />
-<glyph unicode="&#xe007;" d="M-72 800h479l146 400h2l146 -400h472l-382 -278l145 -449l-384 275l-382 -275l146 447zM168 71l2 1zM237 700l196 -142l-73 -226l192 140l195 -141l-74 229l193 140h-235l-77 211l-78 -211h-239z" />
-<glyph unicode="&#xe008;" d="M0 0v143l400 257v100q-37 0 -68.5 74.5t-31.5 125.5v200q0 124 88 212t212 88t212 -88t88 -212v-200q0 -51 -31.5 -125.5t-68.5 -74.5v-100l400 -257v-143h-1200z" />
-<glyph unicode="&#xe009;" d="M0 0v1100h1200v-1100h-1200zM100 100h100v100h-100v-100zM100 300h100v100h-100v-100zM100 500h100v100h-100v-100zM100 700h100v100h-100v-100zM100 900h100v100h-100v-100zM300 100h600v400h-600v-400zM300 600h600v400h-600v-400zM1000 100h100v100h-100v-100z M1000 300h100v100h-100v-100zM1000 500h100v100h-100v-100zM1000 700h100v100h-100v-100zM1000 900h100v100h-100v-100z" />
-<glyph unicode="&#xe010;" d="M0 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM0 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5zM600 50v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5zM600 650v400q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5z" />
-<glyph unicode="&#xe011;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 450v200q0 21 14.5 35.5t35.5 14.5h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM800 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5z" />
-<glyph unicode="&#xe012;" d="M0 50v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM0 450q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v200q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5 t-14.5 -35.5v-200zM0 850v200q0 21 14.5 35.5t35.5 14.5h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5zM400 50v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5 t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 450v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5zM400 850v200q0 21 14.5 35.5t35.5 14.5h700q21 0 35.5 -14.5t14.5 -35.5 v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5z" />
-<glyph unicode="&#xe013;" d="M29 454l419 -420l818 820l-212 212l-607 -607l-206 207z" />
-<glyph unicode="&#xe014;" d="M106 318l282 282l-282 282l212 212l282 -282l282 282l212 -212l-282 -282l282 -282l-212 -212l-282 282l-282 -282z" />
-<glyph unicode="&#xe015;" d="M23 693q0 200 142 342t342 142t342 -142t142 -342q0 -142 -78 -261l300 -300q7 -8 7 -18t-7 -18l-109 -109q-8 -7 -18 -7t-18 7l-300 300q-119 -78 -261 -78q-200 0 -342 142t-142 342zM176 693q0 -136 97 -233t234 -97t233.5 96.5t96.5 233.5t-96.5 233.5t-233.5 96.5 t-234 -97t-97 -233zM300 600v200h100v100h200v-100h100v-200h-100v-100h-200v100h-100z" />
-<glyph unicode="&#xe016;" d="M23 694q0 200 142 342t342 142t342 -142t142 -342q0 -141 -78 -262l300 -299q7 -7 7 -18t-7 -18l-109 -109q-8 -8 -18 -8t-18 8l-300 299q-120 -77 -261 -77q-200 0 -342 142t-142 342zM176 694q0 -136 97 -233t234 -97t233.5 97t96.5 233t-96.5 233t-233.5 97t-234 -97 t-97 -233zM300 601h400v200h-400v-200z" />
-<glyph unicode="&#xe017;" d="M23 600q0 183 105 331t272 210v-166q-103 -55 -165 -155t-62 -220q0 -177 125 -302t302 -125t302 125t125 302q0 120 -62 220t-165 155v166q167 -62 272 -210t105 -331q0 -118 -45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5 zM500 750q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v400q0 21 -14.5 35.5t-35.5 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-400z" />
-<glyph unicode="&#xe018;" d="M100 1h200v300h-200v-300zM400 1v500h200v-500h-200zM700 1v800h200v-800h-200zM1000 1v1200h200v-1200h-200z" />
-<glyph unicode="&#xe019;" d="M26 601q0 -33 6 -74l151 -38l2 -6q14 -49 38 -93l3 -5l-80 -134q45 -59 105 -105l133 81l5 -3q45 -26 94 -39l5 -2l38 -151q40 -5 74 -5q27 0 74 5l38 151l6 2q46 13 93 39l5 3l134 -81q56 44 104 105l-80 134l3 5q24 44 39 93l1 6l152 38q5 40 5 74q0 28 -5 73l-152 38 l-1 6q-16 51 -39 93l-3 5l80 134q-44 58 -104 105l-134 -81l-5 3q-45 25 -93 39l-6 1l-38 152q-40 5 -74 5q-27 0 -74 -5l-38 -152l-5 -1q-50 -14 -94 -39l-5 -3l-133 81q-59 -47 -105 -105l80 -134l-3 -5q-25 -47 -38 -93l-2 -6l-151 -38q-6 -48 -6 -73zM385 601 q0 88 63 151t152 63t152 -63t63 -151q0 -89 -63 -152t-152 -63t-152 63t-63 152z" />
-<glyph unicode="&#xe020;" d="M100 1025v50q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-50q0 -11 -7 -18t-18 -7h-1050q-11 0 -18 7t-7 18zM200 100v800h900v-800q0 -41 -29.5 -71t-70.5 -30h-700q-41 0 -70.5 30 t-29.5 71zM300 100h100v700h-100v-700zM500 100h100v700h-100v-700zM500 1100h300v100h-300v-100zM700 100h100v700h-100v-700zM900 100h100v700h-100v-700z" />
-<glyph unicode="&#xe021;" d="M1 601l656 644l644 -644h-200v-600h-300v400h-300v-400h-300v600h-200z" />
-<glyph unicode="&#xe022;" d="M100 25v1150q0 11 7 18t18 7h475v-500h400v-675q0 -11 -7 -18t-18 -7h-850q-11 0 -18 7t-7 18zM700 800v300l300 -300h-300z" />
-<glyph unicode="&#xe023;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 500v400h100 v-300h200v-100h-300z" />
-<glyph unicode="&#xe024;" d="M-100 0l431 1200h209l-21 -300h162l-20 300h208l431 -1200h-538l-41 400h-242l-40 -400h-539zM488 500h224l-27 300h-170z" />
-<glyph unicode="&#xe025;" d="M0 0v400h490l-290 300h200v500h300v-500h200l-290 -300h490v-400h-1100zM813 200h175v100h-175v-100z" />
-<glyph unicode="&#xe026;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM188 600q0 -170 121 -291t291 -121t291 121t121 291t-121 291t-291 121 t-291 -121t-121 -291zM350 600h150v300h200v-300h150l-250 -300z" />
-<glyph unicode="&#xe027;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM350 600l250 300 l250 -300h-150v-300h-200v300h-150z" />
-<glyph unicode="&#xe028;" d="M0 25v475l200 700h800q199 -700 200 -700v-475q0 -11 -7 -18t-18 -7h-1150q-11 0 -18 7t-7 18zM200 500h200l50 -200h300l50 200h200l-97 500h-606z" />
-<glyph unicode="&#xe029;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM500 397v401 l297 -200z" />
-<glyph unicode="&#xe030;" d="M23 600q0 -118 45.5 -224.5t123 -184t184 -123t224.5 -45.5t224.5 45.5t184 123t123 184t45.5 224.5h-150q0 -177 -125 -302t-302 -125t-302 125t-125 302t125 302t302 125q136 0 246 -81l-146 -146h400v400l-145 -145q-157 122 -355 122q-118 0 -224.5 -45.5t-184 -123 t-123 -184t-45.5 -224.5z" />
-<glyph unicode="&#xe031;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5q198 0 355 -122l145 145v-400h-400l147 147q-112 80 -247 80q-177 0 -302 -125t-125 -302h-150zM100 0v400h400l-147 -147q112 -80 247 -80q177 0 302 125t125 302h150q0 -118 -45.5 -224.5t-123 -184t-184 -123 t-224.5 -45.5q-198 0 -355 122z" />
-<glyph unicode="&#xe032;" d="M100 0h1100v1200h-1100v-1200zM200 100v900h900v-900h-900zM300 200v100h100v-100h-100zM300 400v100h100v-100h-100zM300 600v100h100v-100h-100zM300 800v100h100v-100h-100zM500 200h500v100h-500v-100zM500 400v100h500v-100h-500zM500 600v100h500v-100h-500z M500 800v100h500v-100h-500z" />
-<glyph unicode="&#xe033;" d="M0 100v600q0 41 29.5 70.5t70.5 29.5h100v200q0 82 59 141t141 59h300q82 0 141 -59t59 -141v-200h100q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-900q-41 0 -70.5 29.5t-29.5 70.5zM400 800h300v150q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-150z" />
-<glyph unicode="&#xe034;" d="M100 0v1100h100v-1100h-100zM300 400q60 60 127.5 84t127.5 17.5t122 -23t119 -30t110 -11t103 42t91 120.5v500q-40 -81 -101.5 -115.5t-127.5 -29.5t-138 25t-139.5 40t-125.5 25t-103 -29.5t-65 -115.5v-500z" />
-<glyph unicode="&#xe035;" d="M0 275q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 127 70.5 231.5t184.5 161.5t245 57t245 -57t184.5 -161.5t70.5 -231.5v-300q0 -11 7 -18t18 -7h50q11 0 18 7t7 18v300q0 116 -49.5 227t-131 192.5t-192.5 131t-227 49.5t-227 -49.5t-192.5 -131t-131 -192.5 t-49.5 -227v-300zM200 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14zM800 20v460q0 8 6 14t14 6h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14z" />
-<glyph unicode="&#xe036;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM688 459l141 141l-141 141l71 71l141 -141l141 141l71 -71l-141 -141l141 -141l-71 -71l-141 141l-141 -141z" />
-<glyph unicode="&#xe037;" d="M0 400h300l300 -200v800l-300 -200h-300v-400zM700 857l69 53q111 -135 111 -310q0 -169 -106 -302l-67 54q86 110 86 248q0 146 -93 257z" />
-<glyph unicode="&#xe038;" d="M0 401v400h300l300 200v-800l-300 200h-300zM702 858l69 53q111 -135 111 -310q0 -170 -106 -303l-67 55q86 110 86 248q0 145 -93 257zM889 951l7 -8q123 -151 123 -344q0 -189 -119 -339l-7 -8l81 -66l6 8q142 178 142 405q0 230 -144 408l-6 8z" />
-<glyph unicode="&#xe039;" d="M0 0h500v500h-200v100h-100v-100h-200v-500zM0 600h100v100h400v100h100v100h-100v300h-500v-600zM100 100v300h300v-300h-300zM100 800v300h300v-300h-300zM200 200v100h100v-100h-100zM200 900h100v100h-100v-100zM500 500v100h300v-300h200v-100h-100v-100h-200v100 h-100v100h100v200h-200zM600 0v100h100v-100h-100zM600 1000h100v-300h200v-300h300v200h-200v100h200v500h-600v-200zM800 800v300h300v-300h-300zM900 0v100h300v-100h-300zM900 900v100h100v-100h-100zM1100 200v100h100v-100h-100z" />
-<glyph unicode="&#xe040;" d="M0 200h100v1000h-100v-1000zM100 0v100h300v-100h-300zM200 200v1000h100v-1000h-100zM500 0v91h100v-91h-100zM500 200v1000h200v-1000h-200zM700 0v91h100v-91h-100zM800 200v1000h100v-1000h-100zM900 0v91h200v-91h-200zM1000 200v1000h200v-1000h-200z" />
-<glyph unicode="&#xe041;" d="M1 700v475q0 10 7.5 17.5t17.5 7.5h474l700 -700l-500 -500zM148 953q0 -42 29 -71q30 -30 71.5 -30t71.5 30q29 29 29 71t-29 71q-30 30 -71.5 30t-71.5 -30q-29 -29 -29 -71z" />
-<glyph unicode="&#xe042;" d="M2 700v475q0 11 7 18t18 7h474l700 -700l-500 -500zM148 953q0 -42 30 -71q29 -30 71 -30t71 30q30 29 30 71t-30 71q-29 30 -71 30t-71 -30q-30 -29 -30 -71zM701 1200h100l700 -700l-500 -500l-50 50l450 450z" />
-<glyph unicode="&#xe043;" d="M100 0v1025l175 175h925v-1000l-100 -100v1000h-750l-100 -100h750v-1000h-900z" />
-<glyph unicode="&#xe044;" d="M200 0l450 444l450 -443v1150q0 20 -14.5 35t-35.5 15h-800q-21 0 -35.5 -15t-14.5 -35v-1151z" />
-<glyph unicode="&#xe045;" d="M0 100v700h200l100 -200h600l100 200h200v-700h-200v200h-800v-200h-200zM253 829l40 -124h592l62 124l-94 346q-2 11 -10 18t-18 7h-450q-10 0 -18 -7t-10 -18zM281 24l38 152q2 10 11.5 17t19.5 7h500q10 0 19.5 -7t11.5 -17l38 -152q2 -10 -3.5 -17t-15.5 -7h-600 q-10 0 -15.5 7t-3.5 17z" />
-<glyph unicode="&#xe046;" d="M0 200q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-150q-4 8 -11.5 21.5t-33 48t-53 61t-69 48t-83.5 21.5h-200q-41 0 -82 -20.5t-70 -50t-52 -59t-34 -50.5l-12 -20h-150q-41 0 -70.5 -29.5t-29.5 -70.5v-600z M356 500q0 100 72 172t172 72t172 -72t72 -172t-72 -172t-172 -72t-172 72t-72 172zM494 500q0 -44 31 -75t75 -31t75 31t31 75t-31 75t-75 31t-75 -31t-31 -75zM900 700v100h100v-100h-100z" />
-<glyph unicode="&#xe047;" d="M53 0h365v66q-41 0 -72 11t-49 38t1 71l92 234h391l82 -222q16 -45 -5.5 -88.5t-74.5 -43.5v-66h417v66q-34 1 -74 43q-18 19 -33 42t-21 37l-6 13l-385 998h-93l-399 -1006q-24 -48 -52 -75q-12 -12 -33 -25t-36 -20l-15 -7v-66zM416 521l178 457l46 -140l116 -317h-340 z" />
-<glyph unicode="&#xe048;" d="M100 0v89q41 7 70.5 32.5t29.5 65.5v827q0 28 -1 39.5t-5.5 26t-15.5 21t-29 14t-49 14.5v70h471q120 0 213 -88t93 -228q0 -55 -11.5 -101.5t-28 -74t-33.5 -47.5t-28 -28l-12 -7q8 -3 21.5 -9t48 -31.5t60.5 -58t47.5 -91.5t21.5 -129q0 -84 -59 -156.5t-142 -111 t-162 -38.5h-500zM400 200h161q89 0 153 48.5t64 132.5q0 90 -62.5 154.5t-156.5 64.5h-159v-400zM400 700h139q76 0 130 61.5t54 138.5q0 82 -84 130.5t-239 48.5v-379z" />
-<glyph unicode="&#xe049;" d="M200 0v57q77 7 134.5 40.5t65.5 80.5l173 849q10 56 -10 74t-91 37q-6 1 -10.5 2.5t-9.5 2.5v57h425l2 -57q-33 -8 -62 -25.5t-46 -37t-29.5 -38t-17.5 -30.5l-5 -12l-128 -825q-10 -52 14 -82t95 -36v-57h-500z" />
-<glyph unicode="&#xe050;" d="M-75 200h75v800h-75l125 167l125 -167h-75v-800h75l-125 -167zM300 900v300h150h700h150v-300h-50q0 29 -8 48.5t-18.5 30t-33.5 15t-39.5 5.5t-50.5 1h-200v-850l100 -50v-100h-400v100l100 50v850h-200q-34 0 -50.5 -1t-40 -5.5t-33.5 -15t-18.5 -30t-8.5 -48.5h-49z " />
-<glyph unicode="&#xe051;" d="M33 51l167 125v-75h800v75l167 -125l-167 -125v75h-800v-75zM100 901v300h150h700h150v-300h-50q0 29 -8 48.5t-18 30t-33.5 15t-40 5.5t-50.5 1h-200v-650l100 -50v-100h-400v100l100 50v650h-200q-34 0 -50.5 -1t-39.5 -5.5t-33.5 -15t-18.5 -30t-8 -48.5h-50z" />
-<glyph unicode="&#xe052;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 350q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM0 650q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1000q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 950q0 -20 14.5 -35t35.5 -15h600q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-600q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
-<glyph unicode="&#xe053;" d="M0 50q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM0 650q0 -20 14.5 -35t35.5 -15h1100q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5 v-100zM200 350q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM200 950q0 -20 14.5 -35t35.5 -15h700q21 0 35.5 15t14.5 35v100q0 21 -14.5 35.5t-35.5 14.5h-700q-21 0 -35.5 -14.5 t-14.5 -35.5v-100z" />
-<glyph unicode="&#xe054;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1000q-21 0 -35.5 15 t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-600 q-21 0 -35.5 15t-14.5 35z" />
-<glyph unicode="&#xe055;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-1100 q-21 0 -35.5 15t-14.5 35z" />
-<glyph unicode="&#xe056;" d="M0 50v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 350v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM0 650v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35zM0 950v100q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-100q-21 0 -35.5 15 t-14.5 35zM300 50v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800 q-21 0 -35.5 15t-14.5 35zM300 650v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15h-800q-21 0 -35.5 15t-14.5 35zM300 950v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -20 -14.5 -35t-35.5 -15 h-800q-21 0 -35.5 15t-14.5 35z" />
-<glyph unicode="&#xe057;" d="M-101 500v100h201v75l166 -125l-166 -125v75h-201zM300 0h100v1100h-100v-1100zM500 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35 v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 650q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM500 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100 q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100z" />
-<glyph unicode="&#xe058;" d="M1 50q0 -20 14.5 -35t35.5 -15h600q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-600q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 350q0 -20 14.5 -35t35.5 -15h300q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 650 q0 -20 14.5 -35t35.5 -15h500q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM1 950q0 -20 14.5 -35t35.5 -15h100q20 0 35 15t15 35v100q0 21 -15 35.5t-35 14.5h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-100zM801 0v1100h100v-1100 h-100zM934 550l167 -125v75h200v100h-200v75z" />
-<glyph unicode="&#xe059;" d="M0 275v650q0 31 22 53t53 22h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53zM900 600l300 300v-600z" />
-<glyph unicode="&#xe060;" d="M0 44v1012q0 18 13 31t31 13h1112q19 0 31.5 -13t12.5 -31v-1012q0 -18 -12.5 -31t-31.5 -13h-1112q-18 0 -31 13t-13 31zM100 263l247 182l298 -131l-74 156l293 318l236 -288v500h-1000v-737zM208 750q0 56 39 95t95 39t95 -39t39 -95t-39 -95t-95 -39t-95 39t-39 95z " />
-<glyph unicode="&#xe062;" d="M148 745q0 124 60.5 231.5t165 172t226.5 64.5q123 0 227 -63t164.5 -169.5t60.5 -229.5t-73 -272q-73 -114 -166.5 -237t-150.5 -189l-57 -66q-10 9 -27 26t-66.5 70.5t-96 109t-104 135.5t-100.5 155q-63 139 -63 262zM342 772q0 -107 75.5 -182.5t181.5 -75.5 q107 0 182.5 75.5t75.5 182.5t-75.5 182t-182.5 75t-182 -75.5t-75 -181.5z" />
-<glyph unicode="&#xe063;" d="M1 600q0 122 47.5 233t127.5 191t191 127.5t233 47.5t233 -47.5t191 -127.5t127.5 -191t47.5 -233t-47.5 -233t-127.5 -191t-191 -127.5t-233 -47.5t-233 47.5t-191 127.5t-127.5 191t-47.5 233zM173 600q0 -177 125.5 -302t301.5 -125v854q-176 0 -301.5 -125 t-125.5 -302z" />
-<glyph unicode="&#xe064;" d="M117 406q0 94 34 186t88.5 172.5t112 159t115 177t87.5 194.5q21 -71 57.5 -142.5t76 -130.5t83 -118.5t82 -117t70 -116t50 -125.5t18.5 -136q0 -89 -39 -165.5t-102 -126.5t-140 -79.5t-156 -33.5q-114 6 -211.5 53t-161.5 138.5t-64 210.5zM243 414q14 -82 59.5 -136 t136.5 -80l16 98q-7 6 -18 17t-34 48t-33 77q-15 73 -14 143.5t10 122.5l9 51q-92 -110 -119.5 -185t-12.5 -156z" />
-<glyph unicode="&#xe065;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5q366 -6 397 -14l-186 -186h-311q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v125l200 200v-225q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM436 341l161 50l412 412l-114 113l-405 -405zM995 1015l113 -113l113 113l-21 85l-92 28z" />
-<glyph unicode="&#xe066;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h261l2 -80q-133 -32 -218 -120h-145q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-53q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5 zM423 524q30 38 81.5 64t103 35.5t99 14t77.5 3.5l29 -1v-209l360 324l-359 318v-216q-7 0 -19 -1t-48 -8t-69.5 -18.5t-76.5 -37t-76.5 -59t-62 -88t-39.5 -121.5z" />
-<glyph unicode="&#xe067;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-169q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5 t-117.5 282.5zM342 632l283 -284l566 567l-136 137l-430 -431l-147 147z" />
-<glyph unicode="&#xe068;" d="M0 603l300 296v-198h200v200h-200l300 300l295 -300h-195v-200h200v198l300 -296l-300 -300v198h-200v-200h195l-295 -300l-300 300h200v200h-200v-198z" />
-<glyph unicode="&#xe069;" d="M200 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-1100l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
-<glyph unicode="&#xe070;" d="M0 50v1000q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-437l500 487v-487l500 487v-1100l-500 488v-488l-500 488v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5z" />
-<glyph unicode="&#xe071;" d="M136 550l564 550v-487l500 487v-1100l-500 488v-488z" />
-<glyph unicode="&#xe072;" d="M200 0l900 550l-900 550v-1100z" />
-<glyph unicode="&#xe073;" d="M200 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200q-21 0 -35.5 -14.5t-14.5 -35.5v-800zM600 150q0 -21 14.5 -35.5t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v800q0 21 -14.5 35.5t-35.5 14.5h-200 q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
-<glyph unicode="&#xe074;" d="M200 150q0 -20 14.5 -35t35.5 -15h800q21 0 35.5 15t14.5 35v800q0 21 -14.5 35.5t-35.5 14.5h-800q-21 0 -35.5 -14.5t-14.5 -35.5v-800z" />
-<glyph unicode="&#xe075;" d="M0 0v1100l500 -487v487l564 -550l-564 -550v488z" />
-<glyph unicode="&#xe076;" d="M0 0v1100l500 -487v487l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-500 -488v488z" />
-<glyph unicode="&#xe077;" d="M300 0v1100l500 -487v437q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438z" />
-<glyph unicode="&#xe078;" d="M100 250v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5zM100 500h1100l-550 564z" />
-<glyph unicode="&#xe079;" d="M185 599l592 -592l240 240l-353 353l353 353l-240 240z" />
-<glyph unicode="&#xe080;" d="M272 194l353 353l-353 353l241 240l572 -571l21 -22l-1 -1v-1l-592 -591z" />
-<glyph unicode="&#xe081;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM300 500h200v-200h200v200h200v200h-200v200h-200v-200h-200v-200z" />
-<glyph unicode="&#xe082;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM300 500h600v200h-600v-200z" />
-<glyph unicode="&#xe083;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM246 459l213 -213l141 142l141 -142l213 213l-142 141l142 141l-213 212l-141 -141l-141 142l-212 -213l141 -141z" />
-<glyph unicode="&#xe084;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -299.5t-217.5 -217.5t-299.5 -80t-299.5 80t-217.5 217.5t-80 299.5zM270 551l276 -277l411 411l-175 174l-236 -236l-102 102z" />
-<glyph unicode="&#xe085;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM363 700h144q4 0 11.5 -1t11 -1t6.5 3t3 9t1 11t3.5 8.5t3.5 6t5.5 4t6.5 2.5t9 1.5t9 0.5h11.5h12.5q19 0 30 -10t11 -26 q0 -22 -4 -28t-27 -22q-5 -1 -12.5 -3t-27 -13.5t-34 -27t-26.5 -46t-11 -68.5h200q5 3 14 8t31.5 25.5t39.5 45.5t31 69t14 94q0 51 -17.5 89t-42 58t-58.5 32t-58.5 15t-51.5 3q-105 0 -172 -56t-67 -183zM500 300h200v100h-200v-100z" />
-<glyph unicode="&#xe086;" d="M3 600q0 162 80 299.5t217.5 217.5t299.5 80t299.5 -80t217.5 -217.5t80 -299.5t-80 -300t-217.5 -218t-299.5 -80t-299.5 80t-217.5 218t-80 300zM400 300h400v100h-100v300h-300v-100h100v-200h-100v-100zM500 800h200v100h-200v-100z" />
-<glyph unicode="&#xe087;" d="M0 500v200h194q15 60 36 104.5t55.5 86t88 69t126.5 40.5v200h200v-200q54 -20 113 -60t112.5 -105.5t71.5 -134.5h203v-200h-203q-25 -102 -116.5 -186t-180.5 -117v-197h-200v197q-140 27 -208 102.5t-98 200.5h-194zM290 500q24 -73 79.5 -127.5t130.5 -78.5v206h200 v-206q149 48 201 206h-201v200h200q-25 74 -76 127.5t-124 76.5v-204h-200v203q-75 -24 -130 -77.5t-79 -125.5h209v-200h-210z" />
-<glyph unicode="&#xe088;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM356 465l135 135 l-135 135l109 109l135 -135l135 135l109 -109l-135 -135l135 -135l-109 -109l-135 135l-135 -135z" />
-<glyph unicode="&#xe089;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM322 537l141 141 l87 -87l204 205l142 -142l-346 -345z" />
-<glyph unicode="&#xe090;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -115 62 -215l568 567q-100 62 -216 62q-171 0 -292.5 -121.5t-121.5 -292.5zM391 245q97 -59 209 -59q171 0 292.5 121.5t121.5 292.5 q0 112 -59 209z" />
-<glyph unicode="&#xe091;" d="M0 547l600 453v-300h600v-300h-600v-301z" />
-<glyph unicode="&#xe092;" d="M0 400v300h600v300l600 -453l-600 -448v301h-600z" />
-<glyph unicode="&#xe093;" d="M204 600l450 600l444 -600h-298v-600h-300v600h-296z" />
-<glyph unicode="&#xe094;" d="M104 600h296v600h300v-600h298l-449 -600z" />
-<glyph unicode="&#xe095;" d="M0 200q6 132 41 238.5t103.5 193t184 138t271.5 59.5v271l600 -453l-600 -448v301q-95 -2 -183 -20t-170 -52t-147 -92.5t-100 -135.5z" />
-<glyph unicode="&#xe096;" d="M0 0v400l129 -129l294 294l142 -142l-294 -294l129 -129h-400zM635 777l142 -142l294 294l129 -129v400h-400l129 -129z" />
-<glyph unicode="&#xe097;" d="M34 176l295 295l-129 129h400v-400l-129 130l-295 -295zM600 600v400l129 -129l295 295l142 -141l-295 -295l129 -130h-400z" />
-<glyph unicode="&#xe101;" d="M23 600q0 118 45.5 224.5t123 184t184 123t224.5 45.5t224.5 -45.5t184 -123t123 -184t45.5 -224.5t-45.5 -224.5t-123 -184t-184 -123t-224.5 -45.5t-224.5 45.5t-184 123t-123 184t-45.5 224.5zM456 851l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5 t21.5 34.5l58 302q4 20 -8 34.5t-33 14.5h-207q-20 0 -32 -14.5t-8 -34.5zM500 300h200v100h-200v-100z" />
-<glyph unicode="&#xe102;" d="M0 800h100v-200h400v300h200v-300h400v200h100v100h-111v6t-1 15t-3 18l-34 172q-11 39 -41.5 63t-69.5 24q-32 0 -61 -17l-239 -144q-22 -13 -40 -35q-19 24 -40 36l-238 144q-33 18 -62 18q-39 0 -69.5 -23t-40.5 -61l-35 -177q-2 -8 -3 -18t-1 -15v-6h-111v-100z M100 0h400v400h-400v-400zM200 900q-3 0 14 48t35 96l18 47l214 -191h-281zM700 0v400h400v-400h-400zM731 900l202 197q5 -12 12 -32.5t23 -64t25 -72t7 -28.5h-269z" />
-<glyph unicode="&#xe103;" d="M0 -22v143l216 193q-9 53 -13 83t-5.5 94t9 113t38.5 114t74 124q47 60 99.5 102.5t103 68t127.5 48t145.5 37.5t184.5 43.5t220 58.5q0 -189 -22 -343t-59 -258t-89 -181.5t-108.5 -120t-122 -68t-125.5 -30t-121.5 -1.5t-107.5 12.5t-87.5 17t-56.5 7.5l-99 -55z M238.5 300.5q19.5 -6.5 86.5 76.5q55 66 367 234q70 38 118.5 69.5t102 79t99 111.5t86.5 148q22 50 24 60t-6 19q-7 5 -17 5t-26.5 -14.5t-33.5 -39.5q-35 -51 -113.5 -108.5t-139.5 -89.5l-61 -32q-369 -197 -458 -401q-48 -111 -28.5 -117.5z" />
-<glyph unicode="&#xe104;" d="M111 408q0 -33 5 -63q9 -56 44 -119.5t105 -108.5q31 -21 64 -16t62 23.5t57 49.5t48 61.5t35 60.5q32 66 39 184.5t-13 157.5q79 -80 122 -164t26 -184q-5 -33 -20.5 -69.5t-37.5 -80.5q-10 -19 -14.5 -29t-12 -26t-9 -23.5t-3 -19t2.5 -15.5t11 -9.5t19.5 -5t30.5 2.5 t42 8q57 20 91 34t87.5 44.5t87 64t65.5 88.5t47 122q38 172 -44.5 341.5t-246.5 278.5q22 -44 43 -129q39 -159 -32 -154q-15 2 -33 9q-79 33 -120.5 100t-44 175.5t48.5 257.5q-13 -8 -34 -23.5t-72.5 -66.5t-88.5 -105.5t-60 -138t-8 -166.5q2 -12 8 -41.5t8 -43t6 -39.5 t3.5 -39.5t-1 -33.5t-6 -31.5t-13.5 -24t-21 -20.5t-31 -12q-38 -10 -67 13t-40.5 61.5t-15 81.5t10.5 75q-52 -46 -83.5 -101t-39 -107t-7.5 -85z" />
-<glyph unicode="&#xe105;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5t145.5 -23.5t132.5 -59t116.5 -83.5t97 -90t74.5 -85.5t49 -63.5t20 -30l26 -40l-26 -40q-6 -10 -20 -30t-49 -63.5t-74.5 -85.5t-97 -90t-116.5 -83.5t-132.5 -59t-145.5 -23.5 t-145.5 23.5t-132.5 59t-116.5 83.5t-97 90t-74.5 85.5t-49 63.5t-20 30zM120 600q7 -10 40.5 -58t56 -78.5t68 -77.5t87.5 -75t103 -49.5t125 -21.5t123.5 20t100.5 45.5t85.5 71.5t66.5 75.5t58 81.5t47 66q-1 1 -28.5 37.5t-42 55t-43.5 53t-57.5 63.5t-58.5 54 q49 -74 49 -163q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l105 105q-37 24 -75 72t-57 84l-20 36z" />
-<glyph unicode="&#xe106;" d="M-61 600l26 40q6 10 20 30t49 63.5t74.5 85.5t97 90t116.5 83.5t132.5 59t145.5 23.5q61 0 121 -17l37 142h148l-314 -1200h-148l37 143q-82 21 -165 71.5t-140 102t-109.5 112t-72 88.5t-29.5 43zM120 600q210 -282 393 -336l37 141q-107 18 -178.5 101.5t-71.5 193.5 q0 85 46 158q-102 -87 -226 -258zM377 656q49 -124 154 -191l47 47l23 87q-30 28 -59 69t-44 68l-14 26zM780 161l38 145q22 15 44.5 34t46 44t40.5 44t41 50.5t33.5 43.5t33 44t24.5 34q-97 127 -140 175l39 146q67 -54 131.5 -125.5t87.5 -103.5t36 -52l26 -40l-26 -40 q-7 -12 -25.5 -38t-63.5 -79.5t-95.5 -102.5t-124 -100t-146.5 -79z" />
-<glyph unicode="&#xe107;" d="M-97.5 34q13.5 -34 50.5 -34h1294q37 0 50.5 35.5t-7.5 67.5l-642 1056q-20 33 -48 36t-48 -29l-642 -1066q-21 -32 -7.5 -66zM155 200l445 723l445 -723h-345v100h-200v-100h-345zM500 600l100 -300l100 300v100h-200v-100z" />
-<glyph unicode="&#xe108;" d="M100 262v41q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44t106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -91 100 -113v-64q0 -21 -13 -29t-32 1l-94 78h-222l-94 -78q-19 -9 -32 -1t-13 29v64 q0 22 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5z" />
-<glyph unicode="&#xe109;" d="M0 50q0 -20 14.5 -35t35.5 -15h1000q21 0 35.5 15t14.5 35v750h-1100v-750zM0 900h1100v150q0 21 -14.5 35.5t-35.5 14.5h-150v100h-100v-100h-500v100h-100v-100h-150q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 100v100h100v-100h-100zM100 300v100h100v-100h-100z M100 500v100h100v-100h-100zM300 100v100h100v-100h-100zM300 300v100h100v-100h-100zM300 500v100h100v-100h-100zM500 100v100h100v-100h-100zM500 300v100h100v-100h-100zM500 500v100h100v-100h-100zM700 100v100h100v-100h-100zM700 300v100h100v-100h-100zM700 500 v100h100v-100h-100zM900 100v100h100v-100h-100zM900 300v100h100v-100h-100zM900 500v100h100v-100h-100z" />
-<glyph unicode="&#xe110;" d="M0 200v200h259l600 600h241v198l300 -295l-300 -300v197h-159l-600 -600h-341zM0 800h259l122 -122l141 142l-181 180h-341v-200zM678 381l141 142l122 -123h159v198l300 -295l-300 -300v197h-241z" />
-<glyph unicode="&#xe111;" d="M0 400v600q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5z" />
-<glyph unicode="&#xe112;" d="M100 600v200h300v-250q0 -113 6 -145q17 -92 102 -117q39 -11 92 -11q37 0 66.5 5.5t50 15.5t36 24t24 31.5t14 37.5t7 42t2.5 45t0 47v25v250h300v-200q0 -42 -3 -83t-15 -104t-31.5 -116t-58 -109.5t-89 -96.5t-129 -65.5t-174.5 -25.5t-174.5 25.5t-129 65.5t-89 96.5 t-58 109.5t-31.5 116t-15 104t-3 83zM100 900v300h300v-300h-300zM800 900v300h300v-300h-300z" />
-<glyph unicode="&#xe113;" d="M-30 411l227 -227l352 353l353 -353l226 227l-578 579z" />
-<glyph unicode="&#xe114;" d="M70 797l580 -579l578 579l-226 227l-353 -353l-352 353z" />
-<glyph unicode="&#xe115;" d="M-198 700l299 283l300 -283h-203v-400h385l215 -200h-800v600h-196zM402 1000l215 -200h381v-400h-198l299 -283l299 283h-200v600h-796z" />
-<glyph unicode="&#xe116;" d="M18 939q-5 24 10 42q14 19 39 19h896l38 162q5 17 18.5 27.5t30.5 10.5h94q20 0 35 -14.5t15 -35.5t-15 -35.5t-35 -14.5h-54l-201 -961q-2 -4 -6 -10.5t-19 -17.5t-33 -11h-31v-50q0 -20 -14.5 -35t-35.5 -15t-35.5 15t-14.5 35v50h-300v-50q0 -20 -14.5 -35t-35.5 -15 t-35.5 15t-14.5 35v50h-50q-21 0 -35.5 15t-14.5 35q0 21 14.5 35.5t35.5 14.5h535l48 200h-633q-32 0 -54.5 21t-27.5 43z" />
-<glyph unicode="&#xe117;" d="M0 0v800h1200v-800h-1200zM0 900v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-100h-1200z" />
-<glyph unicode="&#xe118;" d="M1 0l300 700h1200l-300 -700h-1200zM1 400v600h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500v-200h-1000z" />
-<glyph unicode="&#xe119;" d="M302 300h198v600h-198l298 300l298 -300h-198v-600h198l-298 -300z" />
-<glyph unicode="&#xe120;" d="M0 600l300 298v-198h600v198l300 -298l-300 -297v197h-600v-197z" />
-<glyph unicode="&#xe121;" d="M0 100v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM31 400l172 739q5 22 23 41.5t38 19.5h672q19 0 37.5 -22.5t23.5 -45.5l172 -732h-1138zM800 100h100v100h-100v-100z M1000 100h100v100h-100v-100z" />
-<glyph unicode="&#xe122;" d="M-101 600v50q0 24 25 49t50 38l25 13v-250l-11 5.5t-24 14t-30 21.5t-24 27.5t-11 31.5zM99 500v250v5q0 13 0.5 18.5t2.5 13t8 10.5t15 3h200l675 250v-850l-675 200h-38l47 -276q2 -12 -3 -17.5t-11 -6t-21 -0.5h-8h-83q-20 0 -34.5 14t-18.5 35q-56 337 -56 351z M1100 200v850q0 21 14.5 35.5t35.5 14.5q20 0 35 -14.5t15 -35.5v-850q0 -20 -15 -35t-35 -15q-21 0 -35.5 15t-14.5 35z" />
-<glyph unicode="&#xe123;" d="M74 350q0 21 13.5 35.5t33.5 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3 32t29 13h94q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q20 0 33.5 -14.5t13.5 -35.5q0 -20 -13 -40t-31 -27q-22 -9 -63 -23t-167.5 -37 t-251.5 -23t-245.5 20.5t-178.5 41.5l-58 20q-18 7 -31 27.5t-13 40.5zM497 110q12 -49 40 -79.5t63 -30.5t63 30.5t39 79.5q-48 -6 -102 -6t-103 6z" />
-<glyph unicode="&#xe124;" d="M21 445l233 -45l-78 -224l224 78l45 -233l155 179l155 -179l45 233l224 -78l-78 224l234 45l-180 155l180 156l-234 44l78 225l-224 -78l-45 233l-155 -180l-155 180l-45 -233l-224 78l78 -225l-233 -44l179 -156z" />
-<glyph unicode="&#xe125;" d="M0 200h200v600h-200v-600zM300 275q0 -75 100 -75h61q123 -100 139 -100h250q46 0 83 57l238 344q29 31 29 74v100q0 44 -30.5 84.5t-69.5 40.5h-328q28 118 28 125v150q0 44 -30.5 84.5t-69.5 40.5h-50q-27 0 -51 -20t-38 -48l-96 -198l-145 -196q-20 -26 -20 -63v-400z M400 300v375l150 212l100 213h50v-175l-50 -225h450v-125l-250 -375h-214l-136 100h-100z" />
-<glyph unicode="&#xe126;" d="M0 400v600h200v-600h-200zM300 525v400q0 75 100 75h61q123 100 139 100h250q46 0 83 -57l238 -344q29 -31 29 -74v-100q0 -44 -30.5 -84.5t-69.5 -40.5h-328q28 -118 28 -125v-150q0 -44 -30.5 -84.5t-69.5 -40.5h-50q-27 0 -51 20t-38 48l-96 198l-145 196 q-20 26 -20 63zM400 525l150 -212l100 -213h50v175l-50 225h450v125l-250 375h-214l-136 -100h-100v-375z" />
-<glyph unicode="&#xe127;" d="M8 200v600h200v-600h-200zM308 275v525q0 17 14 35.5t28 28.5l14 9l362 230q14 6 25 6q17 0 29 -12l109 -112q14 -14 14 -34q0 -18 -11 -32l-85 -121h302q85 0 138.5 -38t53.5 -110t-54.5 -111t-138.5 -39h-107l-130 -339q-7 -22 -20.5 -41.5t-28.5 -19.5h-341 q-7 0 -90 81t-83 94zM408 289l100 -89h293l131 339q6 21 19.5 41t28.5 20h203q16 0 25 15t9 36q0 20 -9 34.5t-25 14.5h-457h-6.5h-7.5t-6.5 0.5t-6 1t-5 1.5t-5.5 2.5t-4 4t-4 5.5q-5 12 -5 20q0 14 10 27l147 183l-86 83l-339 -236v-503z" />
-<glyph unicode="&#xe128;" d="M-101 651q0 72 54 110t139 37h302l-85 121q-11 16 -11 32q0 21 14 34l109 113q13 12 29 12q11 0 25 -6l365 -230q7 -4 16.5 -10.5t26 -26t16.5 -36.5v-526q0 -13 -85.5 -93.5t-93.5 -80.5h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-84 0 -139 39t-55 111zM-1 601h222 q15 0 28.5 -20.5t19.5 -40.5l131 -339h293l106 89v502l-342 237l-87 -83l145 -184q10 -11 10 -26q0 -11 -5 -20q-1 -3 -3.5 -5.5l-4 -4t-5 -2.5t-5.5 -1.5t-6.5 -1t-6.5 -0.5h-7.5h-6.5h-476v-100zM999 201v600h200v-600h-200z" />
-<glyph unicode="&#xe129;" d="M97 719l230 -363q4 -6 10.5 -15.5t26 -25t36.5 -15.5h525q13 0 94 83t81 90v342q0 15 -20 28.5t-41 19.5l-339 131v106q0 84 -39 139t-111 55t-110 -53.5t-38 -138.5v-302l-121 84q-15 12 -33.5 11.5t-32.5 -13.5l-112 -110q-22 -22 -6 -53zM172 739l83 86l183 -146 q22 -18 47 -5q3 1 5.5 3.5l4 4t2.5 5t1.5 5.5t1 6.5t0.5 6v7.5v7v456q0 22 25 31t50 -0.5t25 -30.5v-202q0 -16 20 -29.5t41 -19.5l339 -130v-294l-89 -100h-503zM400 0v200h600v-200h-600z" />
-<glyph unicode="&#xe130;" d="M1 585q-15 -31 7 -53l112 -110q13 -13 32 -13.5t34 10.5l121 85l-1 -302q0 -84 38.5 -138t110.5 -54t111 55t39 139v106l339 131q20 6 40.5 19.5t20.5 28.5v342q0 7 -81 90t-94 83h-525q-17 0 -35.5 -14t-28.5 -28l-10 -15zM76 565l237 339h503l89 -100v-294l-340 -130 q-20 -6 -40 -20t-20 -29v-202q0 -22 -25 -31t-50 0t-25 31v456v14.5t-1.5 11.5t-5 12t-9.5 7q-24 13 -46 -5l-184 -146zM305 1104v200h600v-200h-600z" />
-<glyph unicode="&#xe131;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q162 0 299.5 -80t217.5 -218t80 -300t-80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 500h300l-2 -194l402 294l-402 298v-197h-298v-201z" />
-<glyph unicode="&#xe132;" d="M0 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t231.5 47.5q122 0 232.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-218 -217.5t-300 -80t-299.5 80t-217.5 217.5t-80 299.5zM200 600l400 -294v194h302v201h-300v197z" />
-<glyph unicode="&#xe133;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600h200v-300h200v300h200l-300 400z" />
-<glyph unicode="&#xe134;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM300 600l300 -400l300 400h-200v300h-200v-300h-200z" />
-<glyph unicode="&#xe135;" d="M5 597q0 122 47.5 232.5t127.5 190.5t190.5 127.5t232.5 47.5q121 0 231.5 -47.5t190.5 -127.5t127.5 -190.5t47.5 -232.5q0 -162 -80 -299.5t-217.5 -217.5t-299.5 -80t-300 80t-218 217.5t-80 299.5zM254 780q-8 -34 5.5 -93t7.5 -87q0 -9 17 -44t16 -60q12 0 23 -5.5 t23 -15t20 -13.5q20 -10 108 -42q22 -8 53 -31.5t59.5 -38.5t57.5 -11q8 -18 -15 -55.5t-20 -57.5q12 -21 22.5 -34.5t28 -27t36.5 -17.5q0 -6 -3 -15.5t-3.5 -14.5t4.5 -17q101 -2 221 111q31 30 47 48t34 49t21 62q-14 9 -37.5 9.5t-35.5 7.5q-14 7 -49 15t-52 19 q-9 0 -39.5 -0.5t-46.5 -1.5t-39 -6.5t-39 -16.5q-50 -35 -66 -12q-4 2 -3.5 25.5t0.5 25.5q-6 13 -26.5 17t-24.5 7q2 22 -2 41t-16.5 28t-38.5 -20q-23 -25 -42 4q-19 28 -8 58q8 16 22 22q6 -1 26 -1.5t33.5 -4.5t19.5 -13q12 -19 32 -37.5t34 -27.5l14 -8q0 3 9.5 39.5 t5.5 57.5q-4 23 14.5 44.5t22.5 31.5q5 14 10 35t8.5 31t15.5 22.5t34 21.5q-6 18 10 37q8 0 23.5 -1.5t24.5 -1.5t20.5 4.5t20.5 15.5q-10 23 -30.5 42.5t-38 30t-49 26.5t-43.5 23q11 41 1 44q31 -13 58.5 -14.5t39.5 3.5l11 4q6 36 -17 53.5t-64 28.5t-56 23 q-19 -3 -37 0q-15 -12 -36.5 -21t-34.5 -12t-44 -8t-39 -6q-15 -3 -46 0t-45 -3q-20 -6 -51.5 -25.5t-34.5 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -91t-29.5 -79zM518 915q3 12 16 30.5t16 25.5q10 -10 18.5 -10t14 6t14.5 14.5t16 12.5q0 -18 8 -42.5t16.5 -44 t9.5 -23.5q-6 1 -39 5t-53.5 10t-36.5 16z" />
-<glyph unicode="&#xe136;" d="M0 164.5q0 21.5 15 37.5l600 599q-33 101 6 201.5t135 154.5q164 92 306 -9l-259 -138l145 -232l251 126q13 -175 -151 -267q-123 -70 -253 -23l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5z" />
-<glyph unicode="&#xe137;" horiz-adv-x="1220" d="M0 196v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 596v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5zM0 996v100q0 41 29.5 70.5t70.5 29.5h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM600 596h500v100h-500v-100zM800 196h300v100h-300v-100zM900 996h200v100h-200v-100z" />
-<glyph unicode="&#xe138;" d="M100 1100v100h1000v-100h-1000zM150 1000h900l-350 -500v-300l-200 -200v500z" />
-<glyph unicode="&#xe139;" d="M0 200v200h1200v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5zM0 500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500z M500 1000h200v100h-200v-100z" />
-<glyph unicode="&#xe140;" d="M0 0v400l129 -129l200 200l142 -142l-200 -200l129 -129h-400zM0 800l129 129l200 -200l142 142l-200 200l129 129h-400v-400zM729 329l142 142l200 -200l129 129v-400h-400l129 129zM729 871l200 200l-129 129h400v-400l-129 129l-200 -200z" />
-<glyph unicode="&#xe141;" d="M0 596q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 596q0 -172 121.5 -293t292.5 -121t292.5 121t121.5 293q0 171 -121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM291 655 q0 23 15.5 38.5t38.5 15.5t39 -16t16 -38q0 -23 -16 -39t-39 -16q-22 0 -38 16t-16 39zM400 850q0 22 16 38.5t39 16.5q22 0 38 -16t16 -39t-16 -39t-38 -16q-23 0 -39 16.5t-16 38.5zM513 609q0 32 21 56.5t52 29.5l122 126l1 1q-9 14 -9 28q0 22 16 38.5t39 16.5 q22 0 38 -16t16 -39t-16 -39t-38 -16q-16 0 -29 10l-55 -145q17 -22 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5q-37 0 -62.5 25.5t-25.5 61.5zM800 655q0 22 16 38t39 16t38.5 -15.5t15.5 -38.5t-16 -39t-38 -16q-23 0 -39 16t-16 39z" />
-<glyph unicode="&#xe142;" d="M-40 375q-13 -95 35 -173q35 -57 94 -89t129 -32q63 0 119 28q33 16 65 40.5t52.5 45.5t59.5 64q40 44 57 61l394 394q35 35 47 84t-3 96q-27 87 -117 104q-20 2 -29 2q-46 0 -79.5 -17t-67.5 -51l-388 -396l-7 -7l69 -67l377 373q20 22 39 38q23 23 50 23q38 0 53 -36 q16 -39 -20 -75l-547 -547q-52 -52 -125 -52q-55 0 -100 33t-54 96q-5 35 2.5 66t31.5 63t42 50t56 54q24 21 44 41l348 348q52 52 82.5 79.5t84 54t107.5 26.5q25 0 48 -4q95 -17 154 -94.5t51 -175.5q-7 -101 -98 -192l-252 -249l-253 -256l7 -7l69 -60l517 511 q67 67 95 157t11 183q-16 87 -67 154t-130 103q-69 33 -152 33q-107 0 -197 -55q-40 -24 -111 -95l-512 -512q-68 -68 -81 -163z" />
-<glyph unicode="&#xe143;" d="M79 784q0 131 99 229.5t230 98.5q144 0 242 -129q103 129 245 129q130 0 227 -98.5t97 -229.5q0 -46 -17.5 -91t-61 -99t-77 -89.5t-104.5 -105.5q-197 -191 -293 -322l-17 -23l-16 23q-43 58 -100 122.5t-92 99.5t-101 100l-84.5 84.5t-68 74t-60 78t-33.5 70.5t-15 78z M250 784q0 -27 30.5 -70t61.5 -75.5t95 -94.5l22 -22q93 -90 190 -201q82 92 195 203l12 12q64 62 97.5 97t64.5 79t31 72q0 71 -48 119.5t-106 48.5q-73 0 -131 -83l-118 -171l-114 174q-51 80 -124 80q-59 0 -108.5 -49.5t-49.5 -118.5z" />
-<glyph unicode="&#xe144;" d="M57 353q0 -94 66 -160l141 -141q66 -66 159 -66q95 0 159 66l283 283q66 66 66 159t-66 159l-141 141q-12 12 -19 17l-105 -105l212 -212l-389 -389l-247 248l95 95l-18 18q-46 45 -75 101l-55 -55q-66 -66 -66 -159zM269 706q0 -93 66 -159l141 -141l19 -17l105 105 l-212 212l389 389l247 -247l-95 -96l18 -18q46 -46 77 -99l29 29q35 35 62.5 88t27.5 96q0 93 -66 159l-141 141q-66 66 -159 66q-95 0 -159 -66l-283 -283q-66 -64 -66 -159z" />
-<glyph unicode="&#xe145;" d="M200 100v953q0 21 30 46t81 48t129 38t163 15t162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5zM300 300h600v700h-600v-700zM496 150q0 -43 30.5 -73.5t73.5 -30.5t73.5 30.5t30.5 73.5t-30.5 73.5t-73.5 30.5 t-73.5 -30.5t-30.5 -73.5z" />
-<glyph unicode="&#xe146;" d="M0 0l303 380l207 208l-210 212h300l267 279l-35 36q-15 14 -15 35t15 35q14 15 35 15t35 -15l283 -282q15 -15 15 -36t-15 -35q-14 -15 -35 -15t-35 15l-36 35l-279 -267v-300l-212 210l-208 -207z" />
-<glyph unicode="&#xe148;" d="M295 433h139q5 -77 48.5 -126.5t117.5 -64.5v335l-27 7q-46 14 -79 26.5t-72 36t-62.5 52t-40 72.5t-16.5 99q0 92 44 159.5t109 101t144 40.5v78h100v-79q38 -4 72.5 -13.5t75.5 -31.5t71 -53.5t51.5 -84t24.5 -118.5h-159q-8 72 -35 109.5t-101 50.5v-307l64 -14 q34 -7 64 -16.5t70 -31.5t67.5 -52t47.5 -80.5t20 -112.5q0 -139 -89 -224t-244 -96v-77h-100v78q-152 17 -237 104q-40 40 -52.5 93.5t-15.5 139.5zM466 889q0 -29 8 -51t16.5 -34t29.5 -22.5t31 -13.5t38 -10q7 -2 11 -3v274q-61 -8 -97.5 -37.5t-36.5 -102.5zM700 237 q170 18 170 151q0 64 -44 99.5t-126 60.5v-311z" />
-<glyph unicode="&#xe149;" d="M100 600v100h166q-24 49 -44 104q-10 26 -14.5 55.5t-3 72.5t25 90t68.5 87q97 88 263 88q129 0 230 -89t101 -208h-153q0 52 -34 89.5t-74 51.5t-76 14q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -11 2.5 -24.5t5.5 -24t9.5 -26.5t10.5 -25t14 -27.5t14 -25.5 t15.5 -27t13.5 -24h242v-100h-197q8 -50 -2.5 -115t-31.5 -94q-41 -59 -99 -113q35 11 84 18t70 7q32 1 102 -16t104 -17q76 0 136 30l50 -147q-41 -25 -80.5 -36.5t-59 -13t-61.5 -1.5q-23 0 -128 33t-155 29q-39 -4 -82 -17t-66 -25l-24 -11l-55 145l16.5 11t15.5 10 t13.5 9.5t14.5 12t14.5 14t17.5 18.5q48 55 54 126.5t-30 142.5h-221z" />
-<glyph unicode="&#xe150;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM602 900l298 300l298 -300h-198v-900h-200v900h-198z" />
-<glyph unicode="&#xe151;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v200h100v-100h200v-100h-300zM700 400v100h300v-200h-99v-100h-100v100h99v100h-200zM700 700v500h300v-500h-100v100h-100v-100h-100zM801 900h100v200h-100v-200z" />
-<glyph unicode="&#xe152;" d="M2 300h198v900h200v-900h198l-298 -300zM700 0v500h300v-500h-100v100h-100v-100h-100zM700 700v200h100v-100h200v-100h-300zM700 1100v100h300v-200h-99v-100h-100v100h99v100h-200zM801 200h100v200h-100v-200z" />
-<glyph unicode="&#xe153;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 100v400h300v-500h-100v100h-200zM800 1100v100h200v-500h-100v400h-100zM901 200h100v200h-100v-200z" />
-<glyph unicode="&#xe154;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM800 400v100h200v-500h-100v400h-100zM800 800v400h300v-500h-100v100h-200zM901 900h100v200h-100v-200z" />
-<glyph unicode="&#xe155;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h500v-200h-500zM700 400v200h400v-200h-400zM700 700v200h300v-200h-300zM700 1000v200h200v-200h-200z" />
-<glyph unicode="&#xe156;" d="M2 300l298 -300l298 300h-198v900h-200v-900h-198zM700 100v200h200v-200h-200zM700 400v200h300v-200h-300zM700 700v200h400v-200h-400zM700 1000v200h500v-200h-500z" />
-<glyph unicode="&#xe157;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q162 0 281 -118.5t119 -281.5v-300q0 -165 -118.5 -282.5t-281.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500z" />
-<glyph unicode="&#xe158;" d="M0 400v300q0 163 119 281.5t281 118.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-163 0 -281.5 117.5t-118.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM400 300l333 250l-333 250v-500z" />
-<glyph unicode="&#xe159;" d="M0 400v300q0 163 117.5 281.5t282.5 118.5h300q163 0 281.5 -119t118.5 -281v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-300q-165 0 -282.5 117.5t-117.5 282.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 700l250 -333l250 333h-500z" />
-<glyph unicode="&#xe160;" d="M0 400v300q0 165 117.5 282.5t282.5 117.5h300q165 0 282.5 -117.5t117.5 -282.5v-300q0 -162 -118.5 -281t-281.5 -119h-300q-165 0 -282.5 118.5t-117.5 281.5zM200 300q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5 h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM300 400h500l-250 333z" />
-<glyph unicode="&#xe161;" d="M0 400v300h300v200l400 -350l-400 -350v200h-300zM500 0v200h500q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-500v200h400q165 0 282.5 -117.5t117.5 -282.5v-300q0 -165 -117.5 -282.5t-282.5 -117.5h-400z" />
-<glyph unicode="&#xe162;" d="M216 519q10 -19 32 -19h302q-155 -438 -160 -458q-5 -21 4 -32l9 -8l9 -1q13 0 26 16l538 630q15 19 6 36q-8 18 -32 16h-300q1 4 78 219.5t79 227.5q2 17 -6 27l-8 8h-9q-16 0 -25 -15q-4 -5 -98.5 -111.5t-228 -257t-209.5 -238.5q-17 -19 -7 -40z" />
-<glyph unicode="&#xe163;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q47 0 100 15v185h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h500v185q-14 4 -114 7.5t-193 5.5l-93 2q-165 0 -282.5 -117.5t-117.5 -282.5v-300zM600 400v300h300v200l400 -350l-400 -350v200h-300z " />
-<glyph unicode="&#xe164;" d="M0 400q0 -165 117.5 -282.5t282.5 -117.5h300q163 0 281.5 117.5t118.5 282.5v98l-78 73l-122 -123v-148q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5h156l118 122l-74 78h-100q-165 0 -282.5 -117.5t-117.5 -282.5 v-300zM496 709l353 342l-149 149h500v-500l-149 149l-342 -353z" />
-<glyph unicode="&#xe165;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM406 600 q0 80 57 137t137 57t137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137z" />
-<glyph unicode="&#xe166;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 800l445 -500l450 500h-295v400h-300v-400h-300zM900 150h100v50h-100v-50z" />
-<glyph unicode="&#xe167;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 700h300v-300h300v300h295l-445 500zM900 150h100v50h-100v-50z" />
-<glyph unicode="&#xe168;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 705l305 -305l596 596l-154 155l-442 -442l-150 151zM900 150h100v50h-100v-50z" />
-<glyph unicode="&#xe169;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM100 988l97 -98l212 213l-97 97zM200 401h700v699l-250 -239l-149 149l-212 -212l149 -149zM900 150h100v50h-100v-50z" />
-<glyph unicode="&#xe170;" d="M0 0v275q0 11 7 18t18 7h1048q11 0 19 -7.5t8 -17.5v-275h-1100zM200 612l212 -212l98 97l-213 212zM300 1200l239 -250l-149 -149l212 -212l149 148l248 -237v700h-699zM900 150h100v50h-100v-50z" />
-<glyph unicode="&#xe171;" d="M23 415l1177 784v-1079l-475 272l-310 -393v416h-392zM494 210l672 938l-672 -712v-226z" />
-<glyph unicode="&#xe172;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-850q0 -21 -15 -35.5t-35 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200z" />
-<glyph unicode="&#xe173;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-218l-276 -275l-120 120l-126 -127h-378v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM581 306l123 123l120 -120l353 352l123 -123l-475 -476zM600 1000h100v200h-100v-200z" />
-<glyph unicode="&#xe174;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-269l-103 -103l-170 170l-298 -298h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 1000h100v200h-100v-200zM700 133l170 170l-170 170l127 127l170 -170l170 170l127 -128l-170 -169l170 -170 l-127 -127l-170 170l-170 -170z" />
-<glyph unicode="&#xe175;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-300h-400v-200h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300l300 -300l300 300h-200v300h-200v-300h-200zM600 1000v200h100v-200h-100z" />
-<glyph unicode="&#xe176;" d="M0 150v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100l200 -200v-402l-200 200l-298 -298h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5zM600 300h200v-300h200v300h200l-300 300zM600 1000v200h100v-200h-100z" />
-<glyph unicode="&#xe177;" d="M0 250q0 -21 14.5 -35.5t35.5 -14.5h1100q21 0 35.5 14.5t14.5 35.5v550h-1200v-550zM0 900h1200v150q0 21 -14.5 35.5t-35.5 14.5h-1100q-21 0 -35.5 -14.5t-14.5 -35.5v-150zM100 300v200h400v-200h-400z" />
-<glyph unicode="&#xe178;" d="M0 400l300 298v-198h400v-200h-400v-198zM100 800v200h100v-200h-100zM300 800v200h100v-200h-100zM500 800v200h400v198l300 -298l-300 -298v198h-400zM800 300v200h100v-200h-100zM1000 300h100v200h-100v-200z" />
-<glyph unicode="&#xe179;" d="M100 700v400l50 100l50 -100v-300h100v300l50 100l50 -100v-300h100v300l50 100l50 -100v-400l-100 -203v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447zM800 597q0 -29 10.5 -55.5t25 -43t29 -28.5t25.5 -18l10 -5v-397q0 -21 14.5 -35.5 t35.5 -14.5h200q21 0 35.5 14.5t14.5 35.5v1106q0 31 -18 40.5t-44 -7.5l-276 -117q-25 -16 -43.5 -50.5t-18.5 -65.5v-359z" />
-<glyph unicode="&#xe180;" d="M100 0h400v56q-75 0 -87.5 6t-12.5 44v394h500v-394q0 -38 -12.5 -44t-87.5 -6v-56h400v56q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v888q0 22 25 34.5t50 13.5l25 2v56h-400v-56q75 0 87.5 -6t12.5 -44v-394h-500v394q0 38 12.5 44t87.5 6v56h-400v-56q4 0 11 -0.5 t24 -3t30 -7t24 -15t11 -24.5v-888q0 -22 -25 -34.5t-50 -13.5l-25 -2v-56z" />
-<glyph unicode="&#xe181;" d="M0 300q0 -41 29.5 -70.5t70.5 -29.5h300q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-300q-41 0 -70.5 -29.5t-29.5 -70.5v-500zM100 100h400l200 200h105l295 98v-298h-425l-100 -100h-375zM100 300v200h300v-200h-300zM100 600v200h300v-200h-300z M100 1000h400l200 -200v-98l295 98h105v200h-425l-100 100h-375zM700 402v163l400 133v-163z" />
-<glyph unicode="&#xe182;" d="M16.5 974.5q0.5 -21.5 16 -90t46.5 -140t104 -177.5t175 -208q103 -103 207.5 -176t180 -103.5t137 -47t92.5 -16.5l31 1l163 162q16 17 13 40.5t-22 37.5l-192 136q-19 14 -45 12t-42 -19l-119 -118q-143 103 -267 227q-126 126 -227 268l118 118q17 17 20 41.5 t-11 44.5l-139 194q-14 19 -36.5 22t-40.5 -14l-162 -162q-1 -11 -0.5 -32.5z" />
-<glyph unicode="&#xe183;" d="M0 50v212q0 20 10.5 45.5t24.5 39.5l365 303v50q0 4 1 10.5t12 22.5t30 28.5t60 23t97 10.5t97 -10t60 -23.5t30 -27.5t12 -24l1 -10v-50l365 -303q14 -14 24.5 -39.5t10.5 -45.5v-212q0 -21 -15 -35.5t-35 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5zM0 712 q0 -21 14.5 -33.5t34.5 -8.5l202 33q20 4 34.5 21t14.5 38v146q141 24 300 24t300 -24v-146q0 -21 14.5 -38t34.5 -21l202 -33q20 -4 34.5 8.5t14.5 33.5v200q-6 8 -19 20.5t-63 45t-112 57t-171 45t-235 20.5q-92 0 -175 -10.5t-141.5 -27t-108.5 -36.5t-81.5 -40 t-53.5 -36.5t-31 -27.5l-9 -10v-200z" />
-<glyph unicode="&#xe184;" d="M100 0v100h1100v-100h-1100zM175 200h950l-125 150v250l100 100v400h-100v-200h-100v200h-200v-200h-100v200h-200v-200h-100v200h-100v-400l100 -100v-250z" />
-<glyph unicode="&#xe185;" d="M100 0h300v400q0 41 -29.5 70.5t-70.5 29.5h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-400zM500 0v1000q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-1000h-300zM900 0v700q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-700h-300z" />
-<glyph unicode="&#xe186;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
-<glyph unicode="&#xe187;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h100v200h100v-200h100v500h-100v-200h-100v200h-100v-500zM600 300h200v100h100v300h-100v100h-200v-500 zM700 400v300h100v-300h-100z" />
-<glyph unicode="&#xe188;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v100h-200v300h200v100h-300v-500zM600 300h300v100h-200v300h200v100h-300v-500z" />
-<glyph unicode="&#xe189;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 550l300 -150v300zM600 400l300 150l-300 150v-300z" />
-<glyph unicode="&#xe190;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300v500h700v-500h-700zM300 400h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130v-300zM575 549 q0 -65 27 -107t68 -42h130v300h-130q-38 0 -66.5 -43t-28.5 -108z" />
-<glyph unicode="&#xe191;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v300h-200v100h200v100h-300v-300h200v-100h-200v-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
-<glyph unicode="&#xe192;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 300h300v400h-200v100h-100v-500zM301 400v200h100v-200h-100zM601 300h100v100h-100v-100zM700 700h100 v-400h100v500h-200v-100z" />
-<glyph unicode="&#xe193;" d="M-100 300v500q0 124 88 212t212 88h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212zM100 200h900v700h-900v-700zM200 700v100h300v-300h-99v-100h-100v100h99v200h-200zM201 300v100h100v-100h-100zM601 300v100h100v-100h-100z M700 700v100h200v-500h-100v400h-100z" />
-<glyph unicode="&#xe194;" d="M4 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM186 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 500v200 l100 100h300v-100h-300v-200h300v-100h-300z" />
-<glyph unicode="&#xe195;" d="M0 600q0 162 80 299t217 217t299 80t299 -80t217 -217t80 -299t-80 -299t-217 -217t-299 -80t-299 80t-217 217t-80 299zM182 600q0 -171 121.5 -292.5t292.5 -121.5t292.5 121.5t121.5 292.5t-121.5 292.5t-292.5 121.5t-292.5 -121.5t-121.5 -292.5zM400 400v400h300 l100 -100v-100h-100v100h-200v-100h200v-100h-200v-100h-100zM700 400v100h100v-100h-100z" />
-<glyph unicode="&#xe197;" d="M-14 494q0 -80 56.5 -137t135.5 -57h222v300h400v-300h128q120 0 205 86t85 208q0 120 -85 206.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200h200v300h200v-300 h200l-300 -300z" />
-<glyph unicode="&#xe198;" d="M-14 494q0 -80 56.5 -137t135.5 -57h8l414 414l403 -403q94 26 154.5 104t60.5 178q0 121 -85 207.5t-205 86.5q-46 0 -90 -14q-44 97 -134.5 156.5t-200.5 59.5q-152 0 -260 -107.5t-108 -260.5q0 -25 2 -37q-66 -14 -108.5 -67.5t-42.5 -122.5zM300 200l300 300 l300 -300h-200v-300h-200v300h-200z" />
-<glyph unicode="&#xe199;" d="M100 200h400v-155l-75 -45h350l-75 45v155h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170z" />
-<glyph unicode="&#xe200;" d="M121 700q0 -53 28.5 -97t75.5 -65q-4 -16 -4 -38q0 -74 52.5 -126.5t126.5 -52.5q56 0 100 30v-306l-75 -45h350l-75 45v306q46 -30 100 -30q74 0 126.5 52.5t52.5 126.5q0 24 -9 55q50 32 79.5 83t29.5 112q0 90 -61.5 155.5t-150.5 71.5q-26 89 -99.5 145.5 t-167.5 56.5q-116 0 -197.5 -81.5t-81.5 -197.5q0 -4 1 -12t1 -11q-14 2 -23 2q-74 0 -126.5 -52.5t-52.5 -126.5z" />
-</font>
-</defs></svg> 

BIN
contrib/views/files/src/main/resources/ui/app/assets/fonts/glyphicons-halflings-regular.ttf


BIN
contrib/views/files/src/main/resources/ui/app/assets/fonts/glyphicons-halflings-regular.woff


+ 0 - 34
contrib/views/files/src/main/resources/ui/app/assets/index.html

@@ -1,34 +0,0 @@
-<!--
-   Licensed to the Apache Software Foundation (ASF) under one or more
-   contributor license agreements.  See the NOTICE file distributed with
-   this work for additional information regarding copyright ownership.
-   The ASF licenses this file to You under the Apache License, Version 2.0
-   (the "License"); you may not use this file except in compliance with
-   the License.  You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
--->
-
-<!DOCTYPE html>
-<html lang="en">
-<head>
-    <meta charset="utf-8">
-    <title>Filebrowser</title>
-    <link rel="stylesheet" href="stylesheets/app.css">
-    <script>
-      EmberENV = {FEATURES: {'query-params-new': true}};
-    </script>
-    <script src="javascripts/vendor.js"></script>
-    <script src="javascripts/app.js"></script>
-    <script>require('initialize');</script>
-</head>
-<body>
-    <!-- ApplicationView -->
-</body>
-</html>

+ 0 - 266
contrib/views/files/src/main/resources/ui/app/assets/javascripts/ember-qunit.js

@@ -1,266 +0,0 @@
-!function(e){if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.emq=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
-"use strict";
-var testResolver = _dereq_("./test-resolver")["default"] || _dereq_("./test-resolver");
-var Ember = window.Ember["default"] || window.Ember;
-
-exports["default"] = function isolatedContainer(fullNames) {
-  var resolver = testResolver.get();
-  var container = new Ember.Container();
-  container.optionsForType('component', { singleton: false });
-  container.optionsForType('view', { singleton: false });
-  container.optionsForType('template', { instantiate: false });
-  container.optionsForType('helper', { instantiate: false });
-  container.register('component-lookup:main', Ember.ComponentLookup);
-  for (var i = fullNames.length; i > 0; i--) {
-    var fullName = fullNames[i - 1];
-    container.register(fullName, resolver.resolve(fullName));
-  }
-  return container;
-}
-},{"./test-resolver":7}],2:[function(_dereq_,module,exports){
-"use strict";
-var Ember = window.Ember["default"] || window.Ember;
-var isolatedContainer = _dereq_("./isolated-container")["default"] || _dereq_("./isolated-container");
-var moduleFor = _dereq_("./module-for")["default"] || _dereq_("./module-for");
-var moduleForComponent = _dereq_("./module-for-component")["default"] || _dereq_("./module-for-component");
-var moduleForModel = _dereq_("./module-for-model")["default"] || _dereq_("./module-for-model");
-var test = _dereq_("./test")["default"] || _dereq_("./test");
-var testResolver = _dereq_("./test-resolver")["default"] || _dereq_("./test-resolver");
-
-Ember.testing = true;
-
-function setResolver(resolver) {
-  testResolver.set(resolver);
-}
-
-function globalize() {
-  window.moduleFor = moduleFor;
-  window.moduleForComponent = moduleForComponent;
-  window.moduleForModel = moduleForModel;
-  window.test = test;
-  window.setResolver = setResolver;
-}
-
-exports.globalize = globalize;
-exports.moduleFor = moduleFor;
-exports.moduleForComponent = moduleForComponent;
-exports.moduleForModel = moduleForModel;
-exports.test = test;
-exports.setResolver = setResolver;
-},{"./isolated-container":1,"./module-for":5,"./module-for-component":3,"./module-for-model":4,"./test":8,"./test-resolver":7}],3:[function(_dereq_,module,exports){
-"use strict";
-var testResolver = _dereq_("./test-resolver")["default"] || _dereq_("./test-resolver");
-var moduleFor = _dereq_("./module-for")["default"] || _dereq_("./module-for");
-var Ember = window.Ember["default"] || window.Ember;
-
-exports["default"] = function moduleForComponent(name, description, callbacks) {
-  var resolver = testResolver.get();
-
-  moduleFor('component:' + name, description, callbacks, function(container, context, defaultSubject) {
-    var layoutName = 'template:components/' + name;
-
-    var layout = resolver.resolve(layoutName);
-
-    if (layout) {
-      container.register(layoutName, layout);
-      container.injection('component:' + name, 'layout', layoutName);
-    }
-
-    context.dispatcher = Ember.EventDispatcher.create();
-    context.dispatcher.setup({}, '#ember-testing');
-
-    context.__setup_properties__.append = function(selector) {
-      var containerView = Ember.ContainerView.create({container: container});
-      var view = Ember.run(function(){
-        var subject = context.subject();
-        containerView.pushObject(subject);
-        // TODO: destory this somewhere
-        containerView.appendTo('#ember-testing');
-        return subject;
-      });
-
-      return view.$();
-    };
-    context.__setup_properties__.$ = context.__setup_properties__.append;
-  });
-}
-},{"./module-for":5,"./test-resolver":7}],4:[function(_dereq_,module,exports){
-"use strict";
-var moduleFor = _dereq_("./module-for")["default"] || _dereq_("./module-for");
-var Ember = window.Ember["default"] || window.Ember;
-
-exports["default"] = function moduleForModel(name, description, callbacks) {
-  moduleFor('model:' + name, description, callbacks, function(container, context, defaultSubject) {
-    if (DS._setupContainer) {
-      DS._setupContainer(container);
-    } else {
-      container.register('store:main', DS.Store);
-    }
-
-    var adapterFactory = container.lookupFactory('adapter:application');
-    if (!adapterFactory) {
-      container.register('adapter:application', DS.FixtureAdapter);
-    }
-
-    context.__setup_properties__.store = function(){
-      return container.lookup('store:main');
-    };
-
-    if (context.__setup_properties__.subject === defaultSubject) {
-      context.__setup_properties__.subject = function(options) {
-        return Ember.run(function() {
-          return container.lookup('store:main').createRecord(name, options);
-        });
-      };
-    }
-  });
-}
-},{"./module-for":5}],5:[function(_dereq_,module,exports){
-"use strict";
-var Ember = window.Ember["default"] || window.Ember;
-//import QUnit from 'qunit'; // Assumed global in runner
-var testContext = _dereq_("./test-context")["default"] || _dereq_("./test-context");
-var isolatedContainer = _dereq_("./isolated-container")["default"] || _dereq_("./isolated-container");
-
-exports["default"] = function moduleFor(fullName, description, callbacks, delegate) {
-  var container;
-  var context;
-
-  var _callbacks = {
-    setup: function(){
-      callbacks = callbacks || { };
-
-      var needs = [fullName].concat(callbacks.needs || []);
-      container = isolatedContainer(needs);
-
-      callbacks.subject   = callbacks.subject || defaultSubject;
-
-      callbacks.setup     = callbacks.setup    || function() { };
-      callbacks.teardown  = callbacks.teardown || function() { };
-
-      function factory() {
-        return container.lookupFactory(fullName);
-      }
-
-      testContext.set({
-        container:            container,
-        factory:              factory,
-        dispatcher:           null,
-        __setup_properties__: callbacks
-      });
-
-      context = testContext.get();
-
-      if (delegate) {
-        delegate(container, context, defaultSubject);
-      }
-
-      if (Ember.$('#ember-testing').length === 0) {
-        Ember.$('<div id="ember-testing"/>').appendTo(document.body);
-      }
-
-      buildContextVariables(context);
-      callbacks.setup.call(context, container);
-    },
-
-    teardown: function(){
-      Ember.run(function(){
-        container.destroy();
-
-        if (context.dispatcher) {
-          context.dispatcher.destroy();
-        }
-      });
-
-      callbacks.teardown(container);
-      Ember.$('#ember-testing').empty();
-    }
-  };
-
-  QUnit.module(description || fullName, _callbacks);
-}
-
-function defaultSubject(options, factory) {
-  return factory.create(options);
-}
-
-// allow arbitrary named factories, like rspec let
-function buildContextVariables(context) {
-  var cache     = { };
-  var callbacks = context.__setup_properties__;
-  var container = context.container;
-  var factory   = context.factory;
-
-  Ember.keys(callbacks).filter(function(key){
-    // ignore the default setup/teardown keys
-    return key !== 'setup' && key !== 'teardown';
-  }).forEach(function(key){
-    context[key] = function(options) {
-      if (cache[key]) { return cache[key]; }
-
-      var result = callbacks[key](options, factory(), container);
-      cache[key] = result;
-      return result;
-    };
-  });
-}
-},{"./isolated-container":1,"./test-context":6}],6:[function(_dereq_,module,exports){
-"use strict";
-var __test_context__;
-
-function set(context) {
-  __test_context__ = context;
-}
-
-exports.set = set;function get() {
-  return __test_context__;
-}
-
-exports.get = get;
-},{}],7:[function(_dereq_,module,exports){
-"use strict";
-var __resolver__;
-
-function set(resolver) {
-  __resolver__ = resolver;
-}
-
-exports.set = set;function get() {
-  if (__resolver__ == null) throw new Error('you must set a resolver with `testResolver.set(resolver)`');
-  return __resolver__;
-}
-
-exports.get = get;
-},{}],8:[function(_dereq_,module,exports){
-"use strict";
-var Ember = window.Ember["default"] || window.Ember;
-//import QUnit from 'qunit'; // Assumed global in runner
-var testContext = _dereq_("./test-context")["default"] || _dereq_("./test-context");
-
-function resetViews() {
-  Ember.View.views = {};
-}
-
-exports["default"] = function test(testName, callback) {
-
-  function wrapper() {
-    var context = testContext.get();
-
-    resetViews();
-    var result = callback.call(context);
-
-    function failTestOnPromiseRejection(reason) {
-      ok(false, reason);
-    }
-
-    Ember.run(function(){
-      stop();
-      Ember.RSVP.Promise.cast(result)['catch'](failTestOnPromiseRejection)['finally'](start);
-    });
-  }
-
-  QUnit.test(testName, wrapper);
-}
-},{"./test-context":6}]},{},[2])
-(2)
-});

+ 0 - 692
contrib/views/files/src/main/resources/ui/app/assets/javascripts/jquery.mockjax.js

@@ -1,692 +0,0 @@
-/*!
- * MockJax - jQuery Plugin to Mock Ajax requests
- *
- * Version:  1.6.0
- * Released:
- * Home:   https://github.com/jakerella/jquery-mockjax
- * Author:   Jonathan Sharp (http://jdsharp.com)
- * License:  MIT,GPL
- *
- * Copyright (c) 2014 appendTo, Jordan Kasper
- * NOTE: This repository was taken over by Jordan Kasper (@jakerella) October, 2014
- *
- * Dual licensed under the MIT or GPL licenses.
- * http://opensource.org/licenses/MIT OR http://www.gnu.org/licenses/gpl-2.0.html
- */
-(function($) {
-  var _ajax = $.ajax,
-    mockHandlers = [],
-    mockedAjaxCalls = [],
-    unmockedAjaxCalls = [],
-    CALLBACK_REGEX = /=\?(&|$)/,
-    jsc = (new Date()).getTime();
-
-
-  // Parse the given XML string.
-  function parseXML(xml) {
-    if ( window.DOMParser == undefined && window.ActiveXObject ) {
-      DOMParser = function() { };
-      DOMParser.prototype.parseFromString = function( xmlString ) {
-        var doc = new ActiveXObject('Microsoft.XMLDOM');
-        doc.async = 'false';
-        doc.loadXML( xmlString );
-        return doc;
-      };
-    }
-
-    try {
-      var xmlDoc = ( new DOMParser() ).parseFromString( xml, 'text/xml' );
-      if ( $.isXMLDoc( xmlDoc ) ) {
-        var err = $('parsererror', xmlDoc);
-        if ( err.length == 1 ) {
-          throw new Error('Error: ' + $(xmlDoc).text() );
-        }
-      } else {
-        throw new Error('Unable to parse XML');
-      }
-      return xmlDoc;
-    } catch( e ) {
-      var msg = ( e.name == undefined ? e : e.name + ': ' + e.message );
-      $(document).trigger('xmlParseError', [ msg ]);
-      return undefined;
-    }
-  }
-
-  // Check if the data field on the mock handler and the request match. This
-  // can be used to restrict a mock handler to being used only when a certain
-  // set of data is passed to it.
-  function isMockDataEqual( mock, live ) {
-    var identical = true;
-    // Test for situations where the data is a querystring (not an object)
-    if (typeof live === 'string') {
-      // Querystring may be a regex
-      return $.isFunction( mock.test ) ? mock.test(live) : mock == live;
-    }
-    $.each(mock, function(k) {
-      if ( live[k] === undefined ) {
-        identical = false;
-        return identical;
-      } else {
-        if ( typeof live[k] === 'object' && live[k] !== null ) {
-          if ( identical && $.isArray( live[k] ) ) {
-            identical = $.isArray( mock[k] ) && live[k].length === mock[k].length;
-          }
-          identical = identical && isMockDataEqual(mock[k], live[k]);
-        } else {
-          if ( mock[k] && $.isFunction( mock[k].test ) ) {
-            identical = identical && mock[k].test(live[k]);
-          } else {
-            identical = identical && ( mock[k] == live[k] );
-          }
-        }
-      }
-    });
-
-    return identical;
-  }
-
-  // See if a mock handler property matches the default settings
-  function isDefaultSetting(handler, property) {
-    return handler[property] === $.mockjaxSettings[property];
-  }
-
-  // Check the given handler should mock the given request
-  function getMockForRequest( handler, requestSettings ) {
-    // If the mock was registered with a function, let the function decide if we
-    // want to mock this request
-    if ( $.isFunction(handler) ) {
-      return handler( requestSettings );
-    }
-
-    // Inspect the URL of the request and check if the mock handler's url
-    // matches the url for this ajax request
-    if ( $.isFunction(handler.url.test) ) {
-      // The user provided a regex for the url, test it
-      if ( !handler.url.test( requestSettings.url ) ) {
-        return null;
-      }
-    } else {
-      // Look for a simple wildcard '*' or a direct URL match
-      var star = handler.url.indexOf('*');
-      if (handler.url !== requestSettings.url && star === -1 ||
-        !new RegExp(handler.url.replace(/[-[\]{}()+?.,\\^$|#\s]/g, "\\$&").replace(/\*/g, '.+')).test(requestSettings.url)) {
-        return null;
-      }
-    }
-
-    // Inspect the data submitted in the request (either POST body or GET query string)
-    if ( handler.data ) {
-      if ( ! requestSettings.data || !isMockDataEqual(handler.data, requestSettings.data) ) {
-        // They're not identical, do not mock this request
-        return null;
-      }
-    }
-    // Inspect the request type
-    if ( handler && handler.type &&
-      handler.type.toLowerCase() != requestSettings.type.toLowerCase() ) {
-      // The request type doesn't match (GET vs. POST)
-      return null;
-    }
-
-    return handler;
-  }
-
-  function parseResponseTimeOpt(responseTime) {
-    if ($.isArray(responseTime)) {
-      var min = responseTime[0];
-      var max = responseTime[1];
-      return (typeof min === 'number' && typeof max === 'number') ? Math.floor(Math.random() * (max - min)) + min : null;
-    } else {
-      return (typeof responseTime === 'number') ? responseTime: null;
-    }
-  }
-
-  // Process the xhr objects send operation
-  function _xhrSend(mockHandler, requestSettings, origSettings) {
-
-    // This is a substitute for < 1.4 which lacks $.proxy
-    var process = (function(that) {
-      return function() {
-        return (function() {
-          // The request has returned
-          this.status     = mockHandler.status;
-          this.statusText = mockHandler.statusText;
-          this.readyState	= 1;
-
-          var finishRequest = function () {
-            this.readyState	= 4;
-
-            var onReady;
-            // Copy over our mock to our xhr object before passing control back to
-            // jQuery's onreadystatechange callback
-            if ( requestSettings.dataType == 'json' && ( typeof mockHandler.responseText == 'object' ) ) {
-              this.responseText = JSON.stringify(mockHandler.responseText);
-            } else if ( requestSettings.dataType == 'xml' ) {
-              if ( typeof mockHandler.responseXML == 'string' ) {
-                this.responseXML = parseXML(mockHandler.responseXML);
-                //in jQuery 1.9.1+, responseXML is processed differently and relies on responseText
-                this.responseText = mockHandler.responseXML;
-              } else {
-                this.responseXML = mockHandler.responseXML;
-              }
-            } else if (typeof mockHandler.responseText === 'object' && mockHandler.responseText !== null) {
-              // since jQuery 1.9 responseText type has to match contentType
-              mockHandler.contentType = 'application/json';
-              this.responseText = JSON.stringify(mockHandler.responseText);
-            } else {
-              this.responseText = mockHandler.responseText;
-            }
-            if( typeof mockHandler.status == 'number' || typeof mockHandler.status == 'string' ) {
-              this.status = mockHandler.status;
-            }
-            if( typeof mockHandler.statusText === "string") {
-              this.statusText = mockHandler.statusText;
-            }
-            // jQuery 2.0 renamed onreadystatechange to onload
-            onReady = this.onreadystatechange || this.onload;
-
-            // jQuery < 1.4 doesn't have onreadystate change for xhr
-            if ( $.isFunction( onReady ) ) {
-              if( mockHandler.isTimeout) {
-                this.status = -1;
-              }
-              onReady.call( this, mockHandler.isTimeout ? 'timeout' : undefined );
-            } else if ( mockHandler.isTimeout ) {
-              // Fix for 1.3.2 timeout to keep success from firing.
-              this.status = -1;
-            }
-          };
-
-          // We have an executable function, call it to give
-          // the mock handler a chance to update it's data
-          if ( $.isFunction(mockHandler.response) ) {
-            // Wait for it to finish
-            if ( mockHandler.response.length === 2 ) {
-              mockHandler.response(origSettings, function () {
-                finishRequest.call(that);
-              });
-              return;
-            } else {
-              mockHandler.response(origSettings);
-            }
-          }
-
-          finishRequest.call(that);
-        }).apply(that);
-      };
-    })(this);
-
-    if ( mockHandler.proxy ) {
-      // We're proxying this request and loading in an external file instead
-      _ajax({
-        global: false,
-        url: mockHandler.proxy,
-        type: mockHandler.proxyType,
-        data: mockHandler.data,
-        dataType: requestSettings.dataType === "script" ? "text/plain" : requestSettings.dataType,
-        complete: function(xhr) {
-          mockHandler.responseXML = xhr.responseXML;
-          mockHandler.responseText = xhr.responseText;
-          // Don't override the handler status/statusText if it's specified by the config
-          if (isDefaultSetting(mockHandler, 'status')) {
-            mockHandler.status = xhr.status;
-          }
-          if (isDefaultSetting(mockHandler, 'statusText')) {
-            mockHandler.statusText = xhr.statusText;
-          }
-          this.responseTimer = setTimeout(process, parseResponseTimeOpt(mockHandler.responseTime) || 0);
-        }
-      });
-    } else {
-      // type == 'POST' || 'GET' || 'DELETE'
-      if ( requestSettings.async === false ) {
-        // TODO: Blocking delay
-        process();
-      } else {
-        this.responseTimer = setTimeout(process, parseResponseTimeOpt(mockHandler.responseTime) || 50);
-      }
-    }
-  }
-
-  // Construct a mocked XHR Object
-  function xhr(mockHandler, requestSettings, origSettings, origHandler) {
-    // Extend with our default mockjax settings
-    mockHandler = $.extend(true, {}, $.mockjaxSettings, mockHandler);
-
-    if (typeof mockHandler.headers === 'undefined') {
-      mockHandler.headers = {};
-    }
-    if (typeof requestSettings.headers === 'undefined') {
-      requestSettings.headers = {};
-    }
-    if ( mockHandler.contentType ) {
-      mockHandler.headers['content-type'] = mockHandler.contentType;
-    }
-
-    return {
-      status: mockHandler.status,
-      statusText: mockHandler.statusText,
-      readyState: 1,
-      open: function() { },
-      send: function() {
-        origHandler.fired = true;
-        _xhrSend.call(this, mockHandler, requestSettings, origSettings);
-      },
-      abort: function() {
-        clearTimeout(this.responseTimer);
-      },
-      setRequestHeader: function(header, value) {
-        requestSettings.headers[header] = value;
-      },
-      getResponseHeader: function(header) {
-        // 'Last-modified', 'Etag', 'content-type' are all checked by jQuery
-        if ( mockHandler.headers && mockHandler.headers[header] ) {
-          // Return arbitrary headers
-          return mockHandler.headers[header];
-        } else if ( header.toLowerCase() == 'last-modified' ) {
-          return mockHandler.lastModified || (new Date()).toString();
-        } else if ( header.toLowerCase() == 'etag' ) {
-          return mockHandler.etag || '';
-        } else if ( header.toLowerCase() == 'content-type' ) {
-          return mockHandler.contentType || 'text/plain';
-        }
-      },
-      getAllResponseHeaders: function() {
-        var headers = '';
-        // since jQuery 1.9 responseText type has to match contentType
-        if (mockHandler.contentType) {
-          mockHandler.headers['Content-Type'] = mockHandler.contentType;
-        }
-        $.each(mockHandler.headers, function(k, v) {
-          headers += k + ': ' + v + "\n";
-        });
-        return headers;
-      }
-    };
-  }
-
-  // Process a JSONP mock request.
-  function processJsonpMock( requestSettings, mockHandler, origSettings ) {
-    // Handle JSONP Parameter Callbacks, we need to replicate some of the jQuery core here
-    // because there isn't an easy hook for the cross domain script tag of jsonp
-
-    processJsonpUrl( requestSettings );
-
-    requestSettings.dataType = "json";
-    if(requestSettings.data && CALLBACK_REGEX.test(requestSettings.data) || CALLBACK_REGEX.test(requestSettings.url)) {
-      createJsonpCallback(requestSettings, mockHandler, origSettings);
-
-      // We need to make sure
-      // that a JSONP style response is executed properly
-
-      var rurl = /^(\w+:)?\/\/([^\/?#]+)/,
-        parts = rurl.exec( requestSettings.url ),
-        remote = parts && (parts[1] && parts[1] !== location.protocol || parts[2] !== location.host);
-
-      requestSettings.dataType = "script";
-      if(requestSettings.type.toUpperCase() === "GET" && remote ) {
-        var newMockReturn = processJsonpRequest( requestSettings, mockHandler, origSettings );
-
-        // Check if we are supposed to return a Deferred back to the mock call, or just
-        // signal success
-        if(newMockReturn) {
-          return newMockReturn;
-        } else {
-          return true;
-        }
-      }
-    }
-    return null;
-  }
-
-  // Append the required callback parameter to the end of the request URL, for a JSONP request
-  function processJsonpUrl( requestSettings ) {
-    if ( requestSettings.type.toUpperCase() === "GET" ) {
-      if ( !CALLBACK_REGEX.test( requestSettings.url ) ) {
-        requestSettings.url += (/\?/.test( requestSettings.url ) ? "&" : "?") +
-          (requestSettings.jsonp || "callback") + "=?";
-      }
-    } else if ( !requestSettings.data || !CALLBACK_REGEX.test(requestSettings.data) ) {
-      requestSettings.data = (requestSettings.data ? requestSettings.data + "&" : "") + (requestSettings.jsonp || "callback") + "=?";
-    }
-  }
-
-  // Process a JSONP request by evaluating the mocked response text
-  function processJsonpRequest( requestSettings, mockHandler, origSettings ) {
-    // Synthesize the mock request for adding a script tag
-    var callbackContext = origSettings && origSettings.context || requestSettings,
-      newMock = null;
-
-
-    // If the response handler on the moock is a function, call it
-    if ( mockHandler.response && $.isFunction(mockHandler.response) ) {
-      mockHandler.response(origSettings);
-    } else {
-
-      // Evaluate the responseText javascript in a global context
-      if( typeof mockHandler.responseText === 'object' ) {
-        $.globalEval( '(' + JSON.stringify( mockHandler.responseText ) + ')');
-      } else {
-        $.globalEval( '(' + mockHandler.responseText + ')');
-      }
-    }
-
-    // Successful response
-    setTimeout(function() {
-      jsonpSuccess( requestSettings, callbackContext, mockHandler );
-      jsonpComplete( requestSettings, callbackContext, mockHandler );
-    }, parseResponseTimeOpt(mockHandler.responseTime) || 0);
-
-    // If we are running under jQuery 1.5+, return a deferred object
-    if($.Deferred){
-      newMock = new $.Deferred();
-      if(typeof mockHandler.responseText == "object"){
-        newMock.resolveWith( callbackContext, [mockHandler.responseText] );
-      }
-      else{
-        newMock.resolveWith( callbackContext, [$.parseJSON( mockHandler.responseText )] );
-      }
-    }
-    return newMock;
-  }
-
-
-  // Create the required JSONP callback function for the request
-  function createJsonpCallback( requestSettings, mockHandler, origSettings ) {
-    var callbackContext = origSettings && origSettings.context || requestSettings;
-    var jsonp = requestSettings.jsonpCallback || ("jsonp" + jsc++);
-
-    // Replace the =? sequence both in the query string and the data
-    if ( requestSettings.data ) {
-      requestSettings.data = (requestSettings.data + "").replace(CALLBACK_REGEX, "=" + jsonp + "$1");
-    }
-
-    requestSettings.url = requestSettings.url.replace(CALLBACK_REGEX, "=" + jsonp + "$1");
-
-
-    // Handle JSONP-style loading
-    window[ jsonp ] = window[ jsonp ] || function( tmp ) {
-      data = tmp;
-      jsonpSuccess( requestSettings, callbackContext, mockHandler );
-      jsonpComplete( requestSettings, callbackContext, mockHandler );
-      // Garbage collect
-      window[ jsonp ] = undefined;
-
-      try {
-        delete window[ jsonp ];
-      } catch(e) {}
-
-      if ( head ) {
-        head.removeChild( script );
-      }
-    };
-  }
-
-  // The JSONP request was successful
-  function jsonpSuccess(requestSettings, callbackContext, mockHandler) {
-    // If a local callback was specified, fire it and pass it the data
-    if ( requestSettings.success ) {
-      requestSettings.success.call( callbackContext, mockHandler.responseText || "", status, {} );
-    }
-
-    // Fire the global callback
-    if ( requestSettings.global ) {
-      (requestSettings.context ? $(requestSettings.context) : $.event).trigger("ajaxSuccess", [{}, requestSettings]);
-    }
-  }
-
-  // The JSONP request was completed
-  function jsonpComplete(requestSettings, callbackContext) {
-    // Process result
-    if ( requestSettings.complete ) {
-      requestSettings.complete.call( callbackContext, {} , status );
-    }
-
-    // The request was completed
-    if ( requestSettings.global ) {
-      (requestSettings.context ? $(requestSettings.context) : $.event).trigger("ajaxComplete", [{}, requestSettings]);
-    }
-
-    // Handle the global AJAX counter
-    if ( requestSettings.global && ! --$.active ) {
-      $.event.trigger( "ajaxStop" );
-    }
-  }
-
-
-  // The core $.ajax replacement.
-  function handleAjax( url, origSettings ) {
-    var mockRequest, requestSettings, mockHandler, overrideCallback;
-
-    // If url is an object, simulate pre-1.5 signature
-    if ( typeof url === "object" ) {
-      origSettings = url;
-      url = undefined;
-    } else {
-      // work around to support 1.5 signature
-      origSettings = origSettings || {};
-      origSettings.url = url;
-    }
-
-    // Extend the original settings for the request
-    requestSettings = $.extend(true, {}, $.ajaxSettings, origSettings);
-
-    // Generic function to override callback methods for use with
-    // callback options (onAfterSuccess, onAfterError, onAfterComplete)
-    overrideCallback = function(action, mockHandler) {
-      var origHandler = origSettings[action.toLowerCase()];
-      return function() {
-        if ( $.isFunction(origHandler) ) {
-          origHandler.apply(this, [].slice.call(arguments));
-        }
-        mockHandler['onAfter' + action]();
-      };
-    };
-
-    // Iterate over our mock handlers (in registration order) until we find
-    // one that is willing to intercept the request
-    for(var k = 0; k < mockHandlers.length; k++) {
-      if ( !mockHandlers[k] ) {
-        continue;
-      }
-
-      mockHandler = getMockForRequest( mockHandlers[k], requestSettings );
-      if(!mockHandler) {
-        // No valid mock found for this request
-        continue;
-      }
-
-      mockedAjaxCalls.push(requestSettings);
-
-      // If logging is enabled, log the mock to the console
-      $.mockjaxSettings.log( mockHandler, requestSettings );
-
-
-      if ( requestSettings.dataType && requestSettings.dataType.toUpperCase() === 'JSONP' ) {
-        if ((mockRequest = processJsonpMock( requestSettings, mockHandler, origSettings ))) {
-          // This mock will handle the JSONP request
-          return mockRequest;
-        }
-      }
-
-
-      // Removed to fix #54 - keep the mocking data object intact
-      //mockHandler.data = requestSettings.data;
-
-      mockHandler.cache = requestSettings.cache;
-      mockHandler.timeout = requestSettings.timeout;
-      mockHandler.global = requestSettings.global;
-
-      // In the case of a timeout, we just need to ensure
-      // an actual jQuery timeout (That is, our reponse won't)
-      // return faster than the timeout setting.
-      if ( mockHandler.isTimeout ) {
-        if ( mockHandler.responseTime > 1 ) {
-          origSettings.timeout = mockHandler.responseTime - 1;
-        } else {
-          mockHandler.responseTime = 2;
-          origSettings.timeout = 1;
-        }
-        mockHandler.isTimeout = false;
-      }
-
-      // Set up onAfter[X] callback functions
-      if ( $.isFunction( mockHandler.onAfterSuccess ) ) {
-        origSettings.success = overrideCallback('Success', mockHandler);
-      }
-      if ( $.isFunction( mockHandler.onAfterError ) ) {
-        origSettings.error = overrideCallback('Error', mockHandler);
-      }
-      if ( $.isFunction( mockHandler.onAfterComplete ) ) {
-        origSettings.complete = overrideCallback('Complete', mockHandler);
-      }
-
-      copyUrlParameters(mockHandler, origSettings);
-
-      (function(mockHandler, requestSettings, origSettings, origHandler) {
-
-        mockRequest = _ajax.call($, $.extend(true, {}, origSettings, {
-          // Mock the XHR object
-          xhr: function() { return xhr( mockHandler, requestSettings, origSettings, origHandler ); }
-        }));
-      })(mockHandler, requestSettings, origSettings, mockHandlers[k]);
-
-      return mockRequest;
-    }
-
-    // We don't have a mock request
-    unmockedAjaxCalls.push(origSettings);
-    if($.mockjaxSettings.throwUnmocked === true) {
-      throw new Error('AJAX not mocked: ' + origSettings.url);
-    }
-    else { // trigger a normal request
-      return _ajax.apply($, [origSettings]);
-    }
-  }
-
-  /**
-   * Copies URL parameter values if they were captured by a regular expression
-   * @param {Object} mockHandler
-   * @param {Object} origSettings
-   */
-  function copyUrlParameters(mockHandler, origSettings) {
-    //parameters aren't captured if the URL isn't a RegExp
-    if (!(mockHandler.url instanceof RegExp)) {
-      return;
-    }
-    //if no URL params were defined on the handler, don't attempt a capture
-    if (!mockHandler.hasOwnProperty('urlParams')) {
-      return;
-    }
-    var captures = mockHandler.url.exec(origSettings.url);
-    //the whole RegExp match is always the first value in the capture results
-    if (captures.length === 1) {
-      return;
-    }
-    captures.shift();
-    //use handler params as keys and capture resuts as values
-    var i = 0,
-      capturesLength = captures.length,
-      paramsLength = mockHandler.urlParams.length,
-    //in case the number of params specified is less than actual captures
-      maxIterations = Math.min(capturesLength, paramsLength),
-      paramValues = {};
-    for (i; i < maxIterations; i++) {
-      var key = mockHandler.urlParams[i];
-      paramValues[key] = captures[i];
-    }
-    origSettings.urlParams = paramValues;
-  }
-
-
-  // Public
-
-  $.extend({
-    ajax: handleAjax
-  });
-
-  $.mockjaxSettings = {
-    //url:        null,
-    //type:       'GET',
-    log:          function( mockHandler, requestSettings ) {
-      if ( mockHandler.logging === false ||
-        ( typeof mockHandler.logging === 'undefined' && $.mockjaxSettings.logging === false ) ) {
-        return;
-      }
-      if ( window.console && console.log ) {
-        var message = 'MOCK ' + requestSettings.type.toUpperCase() + ': ' + requestSettings.url;
-        var request = $.extend({}, requestSettings);
-
-        if (typeof console.log === 'function') {
-          console.log(message, request);
-        } else {
-          try {
-            console.log( message + ' ' + JSON.stringify(request) );
-          } catch (e) {
-            console.log(message);
-          }
-        }
-      }
-    },
-    logging:       true,
-    status:        200,
-    statusText:    "OK",
-    responseTime:  500,
-    isTimeout:     false,
-    throwUnmocked: false,
-    contentType:   'text/plain',
-    response:      '',
-    responseText:  '',
-    responseXML:   '',
-    proxy:         '',
-    proxyType:     'GET',
-
-    lastModified:  null,
-    etag:          '',
-    headers: {
-      etag: 'IJF@H#@923uf8023hFO@I#H#',
-      'content-type' : 'text/plain'
-    }
-  };
-
-  $.mockjax = function(settings) {
-    var i = mockHandlers.length;
-    mockHandlers[i] = settings;
-    return i;
-  };
-  $.mockjax.clear = function(i) {
-    if ( arguments.length == 1 ) {
-      mockHandlers[i] = null;
-    } else {
-      mockHandlers = [];
-    }
-    mockedAjaxCalls = [];
-    unmockedAjaxCalls = [];
-  };
-  // support older, deprecated version
-  $.mockjaxClear = function(i) {
-    window.console && window.console.warn && window.console.warn( 'DEPRECATED: The $.mockjaxClear() method has been deprecated in 1.6.0. Please use $.mockjax.clear() as the older function will be removed soon!' );
-    $.mockjax.clear();
-  };
-  $.mockjax.handler = function(i) {
-    if ( arguments.length == 1 ) {
-      return mockHandlers[i];
-    }
-  };
-  $.mockjax.mockedAjaxCalls = function() {
-    return mockedAjaxCalls;
-  };
-  $.mockjax.unfiredHandlers = function() {
-    var results = [];
-    for (var i=0, len=mockHandlers.length; i<len; i++) {
-      var handler = mockHandlers[i];
-      if (handler !== null && !handler.fired) {
-        results.push(handler);
-      }
-    }
-    return results;
-  };
-  $.mockjax.unmockedAjaxCalls = function() {
-    return unmockedAjaxCalls;
-  };
-})(jQuery);

Diferenças do arquivo suprimidas por serem muito extensas
+ 0 - 3
contrib/views/files/src/main/resources/ui/app/assets/javascripts/modernizr-2.6.2.min.js


+ 0 - 2495
contrib/views/files/src/main/resources/ui/app/assets/javascripts/qunit.js

@@ -1,2495 +0,0 @@
-/*!
- * QUnit 1.15.0
- * http://qunitjs.com/
- *
- * Copyright 2014 jQuery Foundation and other contributors
- * Released under the MIT license
- * http://jquery.org/license
- *
- * Date: 2014-08-08T16:00Z
- */
-
-(function( window ) {
-
-var QUnit,
-	config,
-	onErrorFnPrev,
-	fileName = ( sourceFromStacktrace( 0 ) || "" ).replace( /(:\d+)+\)?/, "" ).replace( /.+\//, "" ),
-	toString = Object.prototype.toString,
-	hasOwn = Object.prototype.hasOwnProperty,
-	// Keep a local reference to Date (GH-283)
-	Date = window.Date,
-	now = Date.now || function() {
-		return new Date().getTime();
-	},
-	setTimeout = window.setTimeout,
-	clearTimeout = window.clearTimeout,
-	defined = {
-		document: typeof window.document !== "undefined",
-		setTimeout: typeof window.setTimeout !== "undefined",
-		sessionStorage: (function() {
-			var x = "qunit-test-string";
-			try {
-				sessionStorage.setItem( x, x );
-				sessionStorage.removeItem( x );
-				return true;
-			} catch ( e ) {
-				return false;
-			}
-		}())
-	},
-	/**
-	 * Provides a normalized error string, correcting an issue
-	 * with IE 7 (and prior) where Error.prototype.toString is
-	 * not properly implemented
-	 *
-	 * Based on http://es5.github.com/#x15.11.4.4
-	 *
-	 * @param {String|Error} error
-	 * @return {String} error message
-	 */
-	errorString = function( error ) {
-		var name, message,
-			errorString = error.toString();
-		if ( errorString.substring( 0, 7 ) === "[object" ) {
-			name = error.name ? error.name.toString() : "Error";
-			message = error.message ? error.message.toString() : "";
-			if ( name && message ) {
-				return name + ": " + message;
-			} else if ( name ) {
-				return name;
-			} else if ( message ) {
-				return message;
-			} else {
-				return "Error";
-			}
-		} else {
-			return errorString;
-		}
-	},
-	/**
-	 * Makes a clone of an object using only Array or Object as base,
-	 * and copies over the own enumerable properties.
-	 *
-	 * @param {Object} obj
-	 * @return {Object} New object with only the own properties (recursively).
-	 */
-	objectValues = function( obj ) {
-		var key, val,
-			vals = QUnit.is( "array", obj ) ? [] : {};
-		for ( key in obj ) {
-			if ( hasOwn.call( obj, key ) ) {
-				val = obj[ key ];
-				vals[ key ] = val === Object( val ) ? objectValues( val ) : val;
-			}
-		}
-		return vals;
-	};
-
-// Root QUnit object.
-// `QUnit` initialized at top of scope
-QUnit = {
-
-	// call on start of module test to prepend name to all tests
-	module: function( name, testEnvironment ) {
-		config.currentModule = name;
-		config.currentModuleTestEnvironment = testEnvironment;
-		config.modules[ name ] = true;
-	},
-
-	asyncTest: function( testName, expected, callback ) {
-		if ( arguments.length === 2 ) {
-			callback = expected;
-			expected = null;
-		}
-
-		QUnit.test( testName, expected, callback, true );
-	},
-
-	test: function( testName, expected, callback, async ) {
-		var test;
-
-		if ( arguments.length === 2 ) {
-			callback = expected;
-			expected = null;
-		}
-
-		test = new Test({
-			testName: testName,
-			expected: expected,
-			async: async,
-			callback: callback,
-			module: config.currentModule,
-			moduleTestEnvironment: config.currentModuleTestEnvironment,
-			stack: sourceFromStacktrace( 2 )
-		});
-
-		if ( !validTest( test ) ) {
-			return;
-		}
-
-		test.queue();
-	},
-
-	start: function( count ) {
-		var message;
-
-		// QUnit hasn't been initialized yet.
-		// Note: RequireJS (et al) may delay onLoad
-		if ( config.semaphore === undefined ) {
-			QUnit.begin(function() {
-				// This is triggered at the top of QUnit.load, push start() to the event loop, to allow QUnit.load to finish first
-				setTimeout(function() {
-					QUnit.start( count );
-				});
-			});
-			return;
-		}
-
-		config.semaphore -= count || 1;
-		// don't start until equal number of stop-calls
-		if ( config.semaphore > 0 ) {
-			return;
-		}
-
-		// Set the starting time when the first test is run
-		QUnit.config.started = QUnit.config.started || now();
-		// ignore if start is called more often then stop
-		if ( config.semaphore < 0 ) {
-			config.semaphore = 0;
-
-			message = "Called start() while already started (QUnit.config.semaphore was 0 already)";
-
-			if ( config.current ) {
-				QUnit.pushFailure( message, sourceFromStacktrace( 2 ) );
-			} else {
-				throw new Error( message );
-			}
-
-			return;
-		}
-		// A slight delay, to avoid any current callbacks
-		if ( defined.setTimeout ) {
-			setTimeout(function() {
-				if ( config.semaphore > 0 ) {
-					return;
-				}
-				if ( config.timeout ) {
-					clearTimeout( config.timeout );
-				}
-
-				config.blocking = false;
-				process( true );
-			}, 13 );
-		} else {
-			config.blocking = false;
-			process( true );
-		}
-	},
-
-	stop: function( count ) {
-		config.semaphore += count || 1;
-		config.blocking = true;
-
-		if ( config.testTimeout && defined.setTimeout ) {
-			clearTimeout( config.timeout );
-			config.timeout = setTimeout(function() {
-				QUnit.ok( false, "Test timed out" );
-				config.semaphore = 1;
-				QUnit.start();
-			}, config.testTimeout );
-		}
-	}
-};
-
-// We use the prototype to distinguish between properties that should
-// be exposed as globals (and in exports) and those that shouldn't
-(function() {
-	function F() {}
-	F.prototype = QUnit;
-	QUnit = new F();
-
-	// Make F QUnit's constructor so that we can add to the prototype later
-	QUnit.constructor = F;
-}());
-
-/**
- * Config object: Maintain internal state
- * Later exposed as QUnit.config
- * `config` initialized at top of scope
- */
-config = {
-	// The queue of tests to run
-	queue: [],
-
-	// block until document ready
-	blocking: true,
-
-	// when enabled, show only failing tests
-	// gets persisted through sessionStorage and can be changed in UI via checkbox
-	hidepassed: false,
-
-	// by default, run previously failed tests first
-	// very useful in combination with "Hide passed tests" checked
-	reorder: true,
-
-	// by default, modify document.title when suite is done
-	altertitle: true,
-
-	// by default, scroll to top of the page when suite is done
-	scrolltop: true,
-
-	// when enabled, all tests must call expect()
-	requireExpects: false,
-
-	// add checkboxes that are persisted in the query-string
-	// when enabled, the id is set to `true` as a `QUnit.config` property
-	urlConfig: [
-		{
-			id: "noglobals",
-			label: "Check for Globals",
-			tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
-		},
-		{
-			id: "notrycatch",
-			label: "No try-catch",
-			tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
-		}
-	],
-
-	// Set of all modules.
-	modules: {},
-
-	callbacks: {}
-};
-
-// Initialize more QUnit.config and QUnit.urlParams
-(function() {
-	var i, current,
-		location = window.location || { search: "", protocol: "file:" },
-		params = location.search.slice( 1 ).split( "&" ),
-		length = params.length,
-		urlParams = {};
-
-	if ( params[ 0 ] ) {
-		for ( i = 0; i < length; i++ ) {
-			current = params[ i ].split( "=" );
-			current[ 0 ] = decodeURIComponent( current[ 0 ] );
-
-			// allow just a key to turn on a flag, e.g., test.html?noglobals
-			current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
-			if ( urlParams[ current[ 0 ] ] ) {
-				urlParams[ current[ 0 ] ] = [].concat( urlParams[ current[ 0 ] ], current[ 1 ] );
-			} else {
-				urlParams[ current[ 0 ] ] = current[ 1 ];
-			}
-		}
-	}
-
-	QUnit.urlParams = urlParams;
-
-	// String search anywhere in moduleName+testName
-	config.filter = urlParams.filter;
-
-	// Exact match of the module name
-	config.module = urlParams.module;
-
-	config.testNumber = [];
-	if ( urlParams.testNumber ) {
-
-		// Ensure that urlParams.testNumber is an array
-		urlParams.testNumber = [].concat( urlParams.testNumber );
-		for ( i = 0; i < urlParams.testNumber.length; i++ ) {
-			current = urlParams.testNumber[ i ];
-			config.testNumber.push( parseInt( current, 10 ) );
-		}
-	}
-
-	// Figure out if we're running the tests from a server or not
-	QUnit.isLocal = location.protocol === "file:";
-}());
-
-extend( QUnit, {
-
-	config: config,
-
-	// Safe object type checking
-	is: function( type, obj ) {
-		return QUnit.objectType( obj ) === type;
-	},
-
-	objectType: function( obj ) {
-		if ( typeof obj === "undefined" ) {
-			return "undefined";
-		}
-
-		// Consider: typeof null === object
-		if ( obj === null ) {
-			return "null";
-		}
-
-		var match = toString.call( obj ).match( /^\[object\s(.*)\]$/ ),
-			type = match && match[ 1 ] || "";
-
-		switch ( type ) {
-			case "Number":
-				if ( isNaN( obj ) ) {
-					return "nan";
-				}
-				return "number";
-			case "String":
-			case "Boolean":
-			case "Array":
-			case "Date":
-			case "RegExp":
-			case "Function":
-				return type.toLowerCase();
-		}
-		if ( typeof obj === "object" ) {
-			return "object";
-		}
-		return undefined;
-	},
-
-	url: function( params ) {
-		params = extend( extend( {}, QUnit.urlParams ), params );
-		var key,
-			querystring = "?";
-
-		for ( key in params ) {
-			if ( hasOwn.call( params, key ) ) {
-				querystring += encodeURIComponent( key ) + "=" +
-					encodeURIComponent( params[ key ] ) + "&";
-			}
-		}
-		return window.location.protocol + "//" + window.location.host +
-			window.location.pathname + querystring.slice( 0, -1 );
-	},
-
-	extend: extend
-});
-
-/**
- * @deprecated: Created for backwards compatibility with test runner that set the hook function
- * into QUnit.{hook}, instead of invoking it and passing the hook function.
- * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
- * Doing this allows us to tell if the following methods have been overwritten on the actual
- * QUnit object.
- */
-extend( QUnit.constructor.prototype, {
-
-	// Logging callbacks; all receive a single argument with the listed properties
-	// run test/logs.html for any related changes
-	begin: registerLoggingCallback( "begin" ),
-
-	// done: { failed, passed, total, runtime }
-	done: registerLoggingCallback( "done" ),
-
-	// log: { result, actual, expected, message }
-	log: registerLoggingCallback( "log" ),
-
-	// testStart: { name }
-	testStart: registerLoggingCallback( "testStart" ),
-
-	// testDone: { name, failed, passed, total, runtime }
-	testDone: registerLoggingCallback( "testDone" ),
-
-	// moduleStart: { name }
-	moduleStart: registerLoggingCallback( "moduleStart" ),
-
-	// moduleDone: { name, failed, passed, total }
-	moduleDone: registerLoggingCallback( "moduleDone" )
-});
-
-QUnit.load = function() {
-	runLoggingCallbacks( "begin", {
-		totalTests: Test.count
-	});
-
-	// Initialize the configuration options
-	extend( config, {
-		stats: { all: 0, bad: 0 },
-		moduleStats: { all: 0, bad: 0 },
-		started: 0,
-		updateRate: 1000,
-		autostart: true,
-		filter: "",
-		semaphore: 1
-	}, true );
-
-	config.blocking = false;
-
-	if ( config.autostart ) {
-		QUnit.start();
-	}
-};
-
-// `onErrorFnPrev` initialized at top of scope
-// Preserve other handlers
-onErrorFnPrev = window.onerror;
-
-// Cover uncaught exceptions
-// Returning true will suppress the default browser handler,
-// returning false will let it run.
-window.onerror = function( error, filePath, linerNr ) {
-	var ret = false;
-	if ( onErrorFnPrev ) {
-		ret = onErrorFnPrev( error, filePath, linerNr );
-	}
-
-	// Treat return value as window.onerror itself does,
-	// Only do our handling if not suppressed.
-	if ( ret !== true ) {
-		if ( QUnit.config.current ) {
-			if ( QUnit.config.current.ignoreGlobalErrors ) {
-				return true;
-			}
-			QUnit.pushFailure( error, filePath + ":" + linerNr );
-		} else {
-			QUnit.test( "global failure", extend(function() {
-				QUnit.pushFailure( error, filePath + ":" + linerNr );
-			}, { validTest: validTest } ) );
-		}
-		return false;
-	}
-
-	return ret;
-};
-
-function done() {
-	config.autorun = true;
-
-	// Log the last module results
-	if ( config.previousModule ) {
-		runLoggingCallbacks( "moduleDone", {
-			name: config.previousModule,
-			failed: config.moduleStats.bad,
-			passed: config.moduleStats.all - config.moduleStats.bad,
-			total: config.moduleStats.all
-		});
-	}
-	delete config.previousModule;
-
-	var runtime = now() - config.started,
-		passed = config.stats.all - config.stats.bad;
-
-	runLoggingCallbacks( "done", {
-		failed: config.stats.bad,
-		passed: passed,
-		total: config.stats.all,
-		runtime: runtime
-	});
-}
-
-/** @return Boolean: true if this test should be ran */
-function validTest( test ) {
-	var include,
-		filter = config.filter && config.filter.toLowerCase(),
-		module = config.module && config.module.toLowerCase(),
-		fullName = ( test.module + ": " + test.testName ).toLowerCase();
-
-	// Internally-generated tests are always valid
-	if ( test.callback && test.callback.validTest === validTest ) {
-		delete test.callback.validTest;
-		return true;
-	}
-
-	if ( config.testNumber.length > 0 ) {
-		if ( inArray( test.testNumber, config.testNumber ) < 0 ) {
-			return false;
-		}
-	}
-
-	if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
-		return false;
-	}
-
-	if ( !filter ) {
-		return true;
-	}
-
-	include = filter.charAt( 0 ) !== "!";
-	if ( !include ) {
-		filter = filter.slice( 1 );
-	}
-
-	// If the filter matches, we need to honour include
-	if ( fullName.indexOf( filter ) !== -1 ) {
-		return include;
-	}
-
-	// Otherwise, do the opposite
-	return !include;
-}
-
-// Doesn't support IE6 to IE9
-// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
-function extractStacktrace( e, offset ) {
-	offset = offset === undefined ? 4 : offset;
-
-	var stack, include, i;
-
-	if ( e.stacktrace ) {
-
-		// Opera 12.x
-		return e.stacktrace.split( "\n" )[ offset + 3 ];
-	} else if ( e.stack ) {
-
-		// Firefox, Chrome, Safari 6+, IE10+, PhantomJS and Node
-		stack = e.stack.split( "\n" );
-		if ( /^error$/i.test( stack[ 0 ] ) ) {
-			stack.shift();
-		}
-		if ( fileName ) {
-			include = [];
-			for ( i = offset; i < stack.length; i++ ) {
-				if ( stack[ i ].indexOf( fileName ) !== -1 ) {
-					break;
-				}
-				include.push( stack[ i ] );
-			}
-			if ( include.length ) {
-				return include.join( "\n" );
-			}
-		}
-		return stack[ offset ];
-	} else if ( e.sourceURL ) {
-
-		// Safari < 6
-		// exclude useless self-reference for generated Error objects
-		if ( /qunit.js$/.test( e.sourceURL ) ) {
-			return;
-		}
-
-		// for actual exceptions, this is useful
-		return e.sourceURL + ":" + e.line;
-	}
-}
-function sourceFromStacktrace( offset ) {
-	try {
-		throw new Error();
-	} catch ( e ) {
-		return extractStacktrace( e, offset );
-	}
-}
-
-function synchronize( callback, last ) {
-	config.queue.push( callback );
-
-	if ( config.autorun && !config.blocking ) {
-		process( last );
-	}
-}
-
-function process( last ) {
-	function next() {
-		process( last );
-	}
-	var start = now();
-	config.depth = config.depth ? config.depth + 1 : 1;
-
-	while ( config.queue.length && !config.blocking ) {
-		if ( !defined.setTimeout || config.updateRate <= 0 || ( ( now() - start ) < config.updateRate ) ) {
-			config.queue.shift()();
-		} else {
-			setTimeout( next, 13 );
-			break;
-		}
-	}
-	config.depth--;
-	if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
-		done();
-	}
-}
-
-function saveGlobal() {
-	config.pollution = [];
-
-	if ( config.noglobals ) {
-		for ( var key in window ) {
-			if ( hasOwn.call( window, key ) ) {
-				// in Opera sometimes DOM element ids show up here, ignore them
-				if ( /^qunit-test-output/.test( key ) ) {
-					continue;
-				}
-				config.pollution.push( key );
-			}
-		}
-	}
-}
-
-function checkPollution() {
-	var newGlobals,
-		deletedGlobals,
-		old = config.pollution;
-
-	saveGlobal();
-
-	newGlobals = diff( config.pollution, old );
-	if ( newGlobals.length > 0 ) {
-		QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join( ", " ) );
-	}
-
-	deletedGlobals = diff( old, config.pollution );
-	if ( deletedGlobals.length > 0 ) {
-		QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join( ", " ) );
-	}
-}
-
-// returns a new Array with the elements that are in a but not in b
-function diff( a, b ) {
-	var i, j,
-		result = a.slice();
-
-	for ( i = 0; i < result.length; i++ ) {
-		for ( j = 0; j < b.length; j++ ) {
-			if ( result[ i ] === b[ j ] ) {
-				result.splice( i, 1 );
-				i--;
-				break;
-			}
-		}
-	}
-	return result;
-}
-
-function extend( a, b, undefOnly ) {
-	for ( var prop in b ) {
-		if ( hasOwn.call( b, prop ) ) {
-
-			// Avoid "Member not found" error in IE8 caused by messing with window.constructor
-			if ( !( prop === "constructor" && a === window ) ) {
-				if ( b[ prop ] === undefined ) {
-					delete a[ prop ];
-				} else if ( !( undefOnly && typeof a[ prop ] !== "undefined" ) ) {
-					a[ prop ] = b[ prop ];
-				}
-			}
-		}
-	}
-
-	return a;
-}
-
-function registerLoggingCallback( key ) {
-
-	// Initialize key collection of logging callback
-	if ( QUnit.objectType( config.callbacks[ key ] ) === "undefined" ) {
-		config.callbacks[ key ] = [];
-	}
-
-	return function( callback ) {
-		config.callbacks[ key ].push( callback );
-	};
-}
-
-function runLoggingCallbacks( key, args ) {
-	var i, l, callbacks;
-
-	callbacks = config.callbacks[ key ];
-	for ( i = 0, l = callbacks.length; i < l; i++ ) {
-		callbacks[ i ]( args );
-	}
-}
-
-// from jquery.js
-function inArray( elem, array ) {
-	if ( array.indexOf ) {
-		return array.indexOf( elem );
-	}
-
-	for ( var i = 0, length = array.length; i < length; i++ ) {
-		if ( array[ i ] === elem ) {
-			return i;
-		}
-	}
-
-	return -1;
-}
-
-function Test( settings ) {
-	extend( this, settings );
-	this.assert = new Assert( this );
-	this.assertions = [];
-	this.testNumber = ++Test.count;
-}
-
-Test.count = 0;
-
-Test.prototype = {
-	setup: function() {
-		if (
-
-			// Emit moduleStart when we're switching from one module to another
-			this.module !== config.previousModule ||
-
-				// They could be equal (both undefined) but if the previousModule property doesn't
-				// yet exist it means this is the first test in a suite that isn't wrapped in a
-				// module, in which case we'll just emit a moduleStart event for 'undefined'.
-				// Without this, reporters can get testStart before moduleStart  which is a problem.
-				!hasOwn.call( config, "previousModule" )
-		) {
-			if ( hasOwn.call( config, "previousModule" ) ) {
-				runLoggingCallbacks( "moduleDone", {
-					name: config.previousModule,
-					failed: config.moduleStats.bad,
-					passed: config.moduleStats.all - config.moduleStats.bad,
-					total: config.moduleStats.all
-				});
-			}
-			config.previousModule = this.module;
-			config.moduleStats = { all: 0, bad: 0 };
-			runLoggingCallbacks( "moduleStart", {
-				name: this.module
-			});
-		}
-
-		config.current = this;
-
-		this.testEnvironment = extend({
-			setup: function() {},
-			teardown: function() {}
-		}, this.moduleTestEnvironment );
-
-		this.started = now();
-		runLoggingCallbacks( "testStart", {
-			name: this.testName,
-			module: this.module,
-			testNumber: this.testNumber
-		});
-
-		if ( !config.pollution ) {
-			saveGlobal();
-		}
-		if ( config.notrycatch ) {
-			this.testEnvironment.setup.call( this.testEnvironment, this.assert );
-			return;
-		}
-		try {
-			this.testEnvironment.setup.call( this.testEnvironment, this.assert );
-		} catch ( e ) {
-			this.pushFailure( "Setup failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
-		}
-	},
-	run: function() {
-		config.current = this;
-
-		if ( this.async ) {
-			QUnit.stop();
-		}
-
-		this.callbackStarted = now();
-
-		if ( config.notrycatch ) {
-			this.callback.call( this.testEnvironment, this.assert );
-			this.callbackRuntime = now() - this.callbackStarted;
-			return;
-		}
-
-		try {
-			this.callback.call( this.testEnvironment, this.assert );
-			this.callbackRuntime = now() - this.callbackStarted;
-		} catch ( e ) {
-			this.callbackRuntime = now() - this.callbackStarted;
-
-			this.pushFailure( "Died on test #" + ( this.assertions.length + 1 ) + " " + this.stack + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
-
-			// else next test will carry the responsibility
-			saveGlobal();
-
-			// Restart the tests if they're blocking
-			if ( config.blocking ) {
-				QUnit.start();
-			}
-		}
-	},
-	teardown: function() {
-		config.current = this;
-		if ( config.notrycatch ) {
-			if ( typeof this.callbackRuntime === "undefined" ) {
-				this.callbackRuntime = now() - this.callbackStarted;
-			}
-			this.testEnvironment.teardown.call( this.testEnvironment, this.assert );
-			return;
-		} else {
-			try {
-				this.testEnvironment.teardown.call( this.testEnvironment, this.assert );
-			} catch ( e ) {
-				this.pushFailure( "Teardown failed on " + this.testName + ": " + ( e.message || e ), extractStacktrace( e, 0 ) );
-			}
-		}
-		checkPollution();
-	},
-	finish: function() {
-		config.current = this;
-		if ( config.requireExpects && this.expected === null ) {
-			this.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
-		} else if ( this.expected !== null && this.expected !== this.assertions.length ) {
-			this.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
-		} else if ( this.expected === null && !this.assertions.length ) {
-			this.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
-		}
-
-		var i,
-			bad = 0;
-
-		this.runtime = now() - this.started;
-		config.stats.all += this.assertions.length;
-		config.moduleStats.all += this.assertions.length;
-
-		for ( i = 0; i < this.assertions.length; i++ ) {
-			if ( !this.assertions[ i ].result ) {
-				bad++;
-				config.stats.bad++;
-				config.moduleStats.bad++;
-			}
-		}
-
-		runLoggingCallbacks( "testDone", {
-			name: this.testName,
-			module: this.module,
-			failed: bad,
-			passed: this.assertions.length - bad,
-			total: this.assertions.length,
-			runtime: this.runtime,
-
-			// HTML Reporter use
-			assertions: this.assertions,
-			testNumber: this.testNumber,
-
-			// DEPRECATED: this property will be removed in 2.0.0, use runtime instead
-			duration: this.runtime
-		});
-
-		config.current = undefined;
-	},
-
-	queue: function() {
-		var bad,
-			test = this;
-
-		function run() {
-			// each of these can by async
-			synchronize(function() {
-				test.setup();
-			});
-			synchronize(function() {
-				test.run();
-			});
-			synchronize(function() {
-				test.teardown();
-			});
-			synchronize(function() {
-				test.finish();
-			});
-		}
-
-		// `bad` initialized at top of scope
-		// defer when previous test run passed, if storage is available
-		bad = QUnit.config.reorder && defined.sessionStorage &&
-				+sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
-
-		if ( bad ) {
-			run();
-		} else {
-			synchronize( run, true );
-		}
-	},
-
-	push: function( result, actual, expected, message ) {
-		var source,
-			details = {
-				module: this.module,
-				name: this.testName,
-				result: result,
-				message: message,
-				actual: actual,
-				expected: expected,
-				testNumber: this.testNumber
-			};
-
-		if ( !result ) {
-			source = sourceFromStacktrace();
-
-			if ( source ) {
-				details.source = source;
-			}
-		}
-
-		runLoggingCallbacks( "log", details );
-
-		this.assertions.push({
-			result: !!result,
-			message: message
-		});
-	},
-
-	pushFailure: function( message, source, actual ) {
-		if ( !this instanceof Test ) {
-			throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace( 2 ) );
-		}
-
-		var details = {
-				module: this.module,
-				name: this.testName,
-				result: false,
-				message: message || "error",
-				actual: actual || null,
-				testNumber: this.testNumber
-			};
-
-		if ( source ) {
-			details.source = source;
-		}
-
-		runLoggingCallbacks( "log", details );
-
-		this.assertions.push({
-			result: false,
-			message: message
-		});
-	}
-};
-
-QUnit.pushFailure = function() {
-	if ( !QUnit.config.current ) {
-		throw new Error( "pushFailure() assertion outside test context, in " + sourceFromStacktrace( 2 ) );
-	}
-
-	// Gets current test obj
-	var currentTest = QUnit.config.current.assert.test;
-
-	return currentTest.pushFailure.apply( currentTest, arguments );
-};
-
-function Assert( testContext ) {
-	this.test = testContext;
-}
-
-// Assert helpers
-QUnit.assert = Assert.prototype = {
-
-	// Specify the number of expected assertions to guarantee that failed test (no assertions are run at all) don't slip through.
-	expect: function( asserts ) {
-		if ( arguments.length === 1 ) {
-			this.test.expected = asserts;
-		} else {
-			return this.test.expected;
-		}
-	},
-
-	// Exports test.push() to the user API
-	push: function() {
-		var assert = this;
-
-		// Backwards compatibility fix.
-		// Allows the direct use of global exported assertions and QUnit.assert.*
-		// Although, it's use is not recommended as it can leak assertions
-		// to other tests from async tests, because we only get a reference to the current test,
-		// not exactly the test where assertion were intended to be called.
-		if ( !QUnit.config.current ) {
-			throw new Error( "assertion outside test context, in " + sourceFromStacktrace( 2 ) );
-		}
-		if ( !( assert instanceof Assert ) ) {
-			assert = QUnit.config.current.assert;
-		}
-		return assert.test.push.apply( assert.test, arguments );
-	},
-
-	/**
-	 * Asserts rough true-ish result.
-	 * @name ok
-	 * @function
-	 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
-	 */
-	ok: function( result, message ) {
-		message = message || ( result ? "okay" : "failed, expected argument to be truthy, was: " +
-			QUnit.dump.parse( result ) );
-		if ( !!result ) {
-			this.push( true, result, true, message );
-		} else {
-			this.test.pushFailure( message, null, result );
-		}
-	},
-
-	/**
-	 * Assert that the first two arguments are equal, with an optional message.
-	 * Prints out both actual and expected values.
-	 * @name equal
-	 * @function
-	 * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
-	 */
-	equal: function( actual, expected, message ) {
-		/*jshint eqeqeq:false */
-		this.push( expected == actual, actual, expected, message );
-	},
-
-	/**
-	 * @name notEqual
-	 * @function
-	 */
-	notEqual: function( actual, expected, message ) {
-		/*jshint eqeqeq:false */
-		this.push( expected != actual, actual, expected, message );
-	},
-
-	/**
-	 * @name propEqual
-	 * @function
-	 */
-	propEqual: function( actual, expected, message ) {
-		actual = objectValues( actual );
-		expected = objectValues( expected );
-		this.push( QUnit.equiv( actual, expected ), actual, expected, message );
-	},
-
-	/**
-	 * @name notPropEqual
-	 * @function
-	 */
-	notPropEqual: function( actual, expected, message ) {
-		actual = objectValues( actual );
-		expected = objectValues( expected );
-		this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
-	},
-
-	/**
-	 * @name deepEqual
-	 * @function
-	 */
-	deepEqual: function( actual, expected, message ) {
-		this.push( QUnit.equiv( actual, expected ), actual, expected, message );
-	},
-
-	/**
-	 * @name notDeepEqual
-	 * @function
-	 */
-	notDeepEqual: function( actual, expected, message ) {
-		this.push( !QUnit.equiv( actual, expected ), actual, expected, message );
-	},
-
-	/**
-	 * @name strictEqual
-	 * @function
-	 */
-	strictEqual: function( actual, expected, message ) {
-		this.push( expected === actual, actual, expected, message );
-	},
-
-	/**
-	 * @name notStrictEqual
-	 * @function
-	 */
-	notStrictEqual: function( actual, expected, message ) {
-		this.push( expected !== actual, actual, expected, message );
-	},
-
-	"throws": function( block, expected, message ) {
-		var actual, expectedType,
-			expectedOutput = expected,
-			ok = false;
-
-		// 'expected' is optional unless doing string comparison
-		if ( message == null && typeof expected === "string" ) {
-			message = expected;
-			expected = null;
-		}
-
-		this.test.ignoreGlobalErrors = true;
-		try {
-			block.call( this.test.testEnvironment );
-		} catch (e) {
-			actual = e;
-		}
-		this.test.ignoreGlobalErrors = false;
-
-		if ( actual ) {
-			expectedType = QUnit.objectType( expected );
-
-			// we don't want to validate thrown error
-			if ( !expected ) {
-				ok = true;
-				expectedOutput = null;
-
-			// expected is a regexp
-			} else if ( expectedType === "regexp" ) {
-				ok = expected.test( errorString( actual ) );
-
-			// expected is a string
-			} else if ( expectedType === "string" ) {
-				ok = expected === errorString( actual );
-
-			// expected is a constructor, maybe an Error constructor
-			} else if ( expectedType === "function" && actual instanceof expected ) {
-				ok = true;
-
-			// expected is an Error object
-			} else if ( expectedType === "object" ) {
-				ok = actual instanceof expected.constructor &&
-					actual.name === expected.name &&
-					actual.message === expected.message;
-
-			// expected is a validation function which returns true if validation passed
-			} else if ( expectedType === "function" && expected.call( {}, actual ) === true ) {
-				expectedOutput = null;
-				ok = true;
-			}
-
-			this.push( ok, actual, expectedOutput, message );
-		} else {
-			this.test.pushFailure( message, null, "No exception was thrown." );
-		}
-	}
-};
-
-// Test for equality any JavaScript type.
-// Author: Philippe Rathé <prathe@gmail.com>
-QUnit.equiv = (function() {
-
-	// Call the o related callback with the given arguments.
-	function bindCallbacks( o, callbacks, args ) {
-		var prop = QUnit.objectType( o );
-		if ( prop ) {
-			if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
-				return callbacks[ prop ].apply( callbacks, args );
-			} else {
-				return callbacks[ prop ]; // or undefined
-			}
-		}
-	}
-
-	// the real equiv function
-	var innerEquiv,
-
-		// stack to decide between skip/abort functions
-		callers = [],
-
-		// stack to avoiding loops from circular referencing
-		parents = [],
-		parentsB = [],
-
-		getProto = Object.getPrototypeOf || function( obj ) {
-			/* jshint camelcase: false, proto: true */
-			return obj.__proto__;
-		},
-		callbacks = (function() {
-
-			// for string, boolean, number and null
-			function useStrictEquality( b, a ) {
-
-				/*jshint eqeqeq:false */
-				if ( b instanceof a.constructor || a instanceof b.constructor ) {
-
-					// to catch short annotation VS 'new' annotation of a
-					// declaration
-					// e.g. var i = 1;
-					// var j = new Number(1);
-					return a == b;
-				} else {
-					return a === b;
-				}
-			}
-
-			return {
-				"string": useStrictEquality,
-				"boolean": useStrictEquality,
-				"number": useStrictEquality,
-				"null": useStrictEquality,
-				"undefined": useStrictEquality,
-
-				"nan": function( b ) {
-					return isNaN( b );
-				},
-
-				"date": function( b, a ) {
-					return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
-				},
-
-				"regexp": function( b, a ) {
-					return QUnit.objectType( b ) === "regexp" &&
-
-						// the regex itself
-						a.source === b.source &&
-
-						// and its modifiers
-						a.global === b.global &&
-
-						// (gmi) ...
-						a.ignoreCase === b.ignoreCase &&
-						a.multiline === b.multiline &&
-						a.sticky === b.sticky;
-				},
-
-				// - skip when the property is a method of an instance (OOP)
-				// - abort otherwise,
-				// initial === would have catch identical references anyway
-				"function": function() {
-					var caller = callers[ callers.length - 1 ];
-					return caller !== Object && typeof caller !== "undefined";
-				},
-
-				"array": function( b, a ) {
-					var i, j, len, loop, aCircular, bCircular;
-
-					// b could be an object literal here
-					if ( QUnit.objectType( b ) !== "array" ) {
-						return false;
-					}
-
-					len = a.length;
-					if ( len !== b.length ) {
-						// safe and faster
-						return false;
-					}
-
-					// track reference to avoid circular references
-					parents.push( a );
-					parentsB.push( b );
-					for ( i = 0; i < len; i++ ) {
-						loop = false;
-						for ( j = 0; j < parents.length; j++ ) {
-							aCircular = parents[ j ] === a[ i ];
-							bCircular = parentsB[ j ] === b[ i ];
-							if ( aCircular || bCircular ) {
-								if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
-									loop = true;
-								} else {
-									parents.pop();
-									parentsB.pop();
-									return false;
-								}
-							}
-						}
-						if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
-							parents.pop();
-							parentsB.pop();
-							return false;
-						}
-					}
-					parents.pop();
-					parentsB.pop();
-					return true;
-				},
-
-				"object": function( b, a ) {
-
-					/*jshint forin:false */
-					var i, j, loop, aCircular, bCircular,
-						// Default to true
-						eq = true,
-						aProperties = [],
-						bProperties = [];
-
-					// comparing constructors is more strict than using
-					// instanceof
-					if ( a.constructor !== b.constructor ) {
-
-						// Allow objects with no prototype to be equivalent to
-						// objects with Object as their constructor.
-						if ( !( ( getProto( a ) === null && getProto( b ) === Object.prototype ) ||
-							( getProto( b ) === null && getProto( a ) === Object.prototype ) ) ) {
-							return false;
-						}
-					}
-
-					// stack constructor before traversing properties
-					callers.push( a.constructor );
-
-					// track reference to avoid circular references
-					parents.push( a );
-					parentsB.push( b );
-
-					// be strict: don't ensure hasOwnProperty and go deep
-					for ( i in a ) {
-						loop = false;
-						for ( j = 0; j < parents.length; j++ ) {
-							aCircular = parents[ j ] === a[ i ];
-							bCircular = parentsB[ j ] === b[ i ];
-							if ( aCircular || bCircular ) {
-								if ( a[ i ] === b[ i ] || aCircular && bCircular ) {
-									loop = true;
-								} else {
-									eq = false;
-									break;
-								}
-							}
-						}
-						aProperties.push( i );
-						if ( !loop && !innerEquiv( a[ i ], b[ i ] ) ) {
-							eq = false;
-							break;
-						}
-					}
-
-					parents.pop();
-					parentsB.pop();
-					callers.pop(); // unstack, we are done
-
-					for ( i in b ) {
-						bProperties.push( i ); // collect b's properties
-					}
-
-					// Ensures identical properties name
-					return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
-				}
-			};
-		}());
-
-	innerEquiv = function() { // can take multiple arguments
-		var args = [].slice.apply( arguments );
-		if ( args.length < 2 ) {
-			return true; // end transition
-		}
-
-		return ( (function( a, b ) {
-			if ( a === b ) {
-				return true; // catch the most you can
-			} else if ( a === null || b === null || typeof a === "undefined" ||
-					typeof b === "undefined" ||
-					QUnit.objectType( a ) !== QUnit.objectType( b ) ) {
-
-				// don't lose time with error prone cases
-				return false;
-			} else {
-				return bindCallbacks( a, callbacks, [ b, a ] );
-			}
-
-			// apply transition with (1..n) arguments
-		}( args[ 0 ], args[ 1 ] ) ) && innerEquiv.apply( this, args.splice( 1, args.length - 1 ) ) );
-	};
-
-	return innerEquiv;
-}());
-
-// Based on jsDump by Ariel Flesler
-// http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html
-QUnit.dump = (function() {
-	function quote( str ) {
-		return "\"" + str.toString().replace( /"/g, "\\\"" ) + "\"";
-	}
-	function literal( o ) {
-		return o + "";
-	}
-	function join( pre, arr, post ) {
-		var s = dump.separator(),
-			base = dump.indent(),
-			inner = dump.indent( 1 );
-		if ( arr.join ) {
-			arr = arr.join( "," + s + inner );
-		}
-		if ( !arr ) {
-			return pre + post;
-		}
-		return [ pre, inner + arr, base + post ].join( s );
-	}
-	function array( arr, stack ) {
-		var i = arr.length,
-			ret = new Array( i );
-		this.up();
-		while ( i-- ) {
-			ret[ i ] = this.parse( arr[ i ], undefined, stack );
-		}
-		this.down();
-		return join( "[", ret, "]" );
-	}
-
-	var reName = /^function (\w+)/,
-		dump = {
-			// type is used mostly internally, you can fix a (custom)type in advance
-			parse: function( obj, type, stack ) {
-				stack = stack || [];
-				var inStack, res,
-					parser = this.parsers[ type || this.typeOf( obj ) ];
-
-				type = typeof parser;
-				inStack = inArray( obj, stack );
-
-				if ( inStack !== -1 ) {
-					return "recursion(" + ( inStack - stack.length ) + ")";
-				}
-				if ( type === "function" ) {
-					stack.push( obj );
-					res = parser.call( this, obj, stack );
-					stack.pop();
-					return res;
-				}
-				return ( type === "string" ) ? parser : this.parsers.error;
-			},
-			typeOf: function( obj ) {
-				var type;
-				if ( obj === null ) {
-					type = "null";
-				} else if ( typeof obj === "undefined" ) {
-					type = "undefined";
-				} else if ( QUnit.is( "regexp", obj ) ) {
-					type = "regexp";
-				} else if ( QUnit.is( "date", obj ) ) {
-					type = "date";
-				} else if ( QUnit.is( "function", obj ) ) {
-					type = "function";
-				} else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
-					type = "window";
-				} else if ( obj.nodeType === 9 ) {
-					type = "document";
-				} else if ( obj.nodeType ) {
-					type = "node";
-				} else if (
-
-					// native arrays
-					toString.call( obj ) === "[object Array]" ||
-
-					// NodeList objects
-					( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item( 0 ) === obj[ 0 ] : ( obj.item( 0 ) === null && typeof obj[ 0 ] === "undefined" ) ) )
-				) {
-					type = "array";
-				} else if ( obj.constructor === Error.prototype.constructor ) {
-					type = "error";
-				} else {
-					type = typeof obj;
-				}
-				return type;
-			},
-			separator: function() {
-				return this.multiline ? this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
-			},
-			// extra can be a number, shortcut for increasing-calling-decreasing
-			indent: function( extra ) {
-				if ( !this.multiline ) {
-					return "";
-				}
-				var chr = this.indentChar;
-				if ( this.HTML ) {
-					chr = chr.replace( /\t/g, "   " ).replace( / /g, "&nbsp;" );
-				}
-				return new Array( this.depth + ( extra || 0 ) ).join( chr );
-			},
-			up: function( a ) {
-				this.depth += a || 1;
-			},
-			down: function( a ) {
-				this.depth -= a || 1;
-			},
-			setParser: function( name, parser ) {
-				this.parsers[ name ] = parser;
-			},
-			// The next 3 are exposed so you can use them
-			quote: quote,
-			literal: literal,
-			join: join,
-			//
-			depth: 1,
-			// This is the list of parsers, to modify them, use dump.setParser
-			parsers: {
-				window: "[Window]",
-				document: "[Document]",
-				error: function( error ) {
-					return "Error(\"" + error.message + "\")";
-				},
-				unknown: "[Unknown]",
-				"null": "null",
-				"undefined": "undefined",
-				"function": function( fn ) {
-					var ret = "function",
-						// functions never have name in IE
-						name = "name" in fn ? fn.name : ( reName.exec( fn ) || [] )[ 1 ];
-
-					if ( name ) {
-						ret += " " + name;
-					}
-					ret += "( ";
-
-					ret = [ ret, dump.parse( fn, "functionArgs" ), "){" ].join( "" );
-					return join( ret, dump.parse( fn, "functionCode" ), "}" );
-				},
-				array: array,
-				nodelist: array,
-				"arguments": array,
-				object: function( map, stack ) {
-					/*jshint forin:false */
-					var ret = [], keys, key, val, i, nonEnumerableProperties;
-					dump.up();
-					keys = [];
-					for ( key in map ) {
-						keys.push( key );
-					}
-
-					// Some properties are not always enumerable on Error objects.
-					nonEnumerableProperties = [ "message", "name" ];
-					for ( i in nonEnumerableProperties ) {
-						key = nonEnumerableProperties[ i ];
-						if ( key in map && !( key in keys ) ) {
-							keys.push( key );
-						}
-					}
-					keys.sort();
-					for ( i = 0; i < keys.length; i++ ) {
-						key = keys[ i ];
-						val = map[ key ];
-						ret.push( dump.parse( key, "key" ) + ": " + dump.parse( val, undefined, stack ) );
-					}
-					dump.down();
-					return join( "{", ret, "}" );
-				},
-				node: function( node ) {
-					var len, i, val,
-						open = dump.HTML ? "&lt;" : "<",
-						close = dump.HTML ? "&gt;" : ">",
-						tag = node.nodeName.toLowerCase(),
-						ret = open + tag,
-						attrs = node.attributes;
-
-					if ( attrs ) {
-						for ( i = 0, len = attrs.length; i < len; i++ ) {
-							val = attrs[ i ].nodeValue;
-
-							// IE6 includes all attributes in .attributes, even ones not explicitly set.
-							// Those have values like undefined, null, 0, false, "" or "inherit".
-							if ( val && val !== "inherit" ) {
-								ret += " " + attrs[ i ].nodeName + "=" + dump.parse( val, "attribute" );
-							}
-						}
-					}
-					ret += close;
-
-					// Show content of TextNode or CDATASection
-					if ( node.nodeType === 3 || node.nodeType === 4 ) {
-						ret += node.nodeValue;
-					}
-
-					return ret + open + "/" + tag + close;
-				},
-
-				// function calls it internally, it's the arguments part of the function
-				functionArgs: function( fn ) {
-					var args,
-						l = fn.length;
-
-					if ( !l ) {
-						return "";
-					}
-
-					args = new Array( l );
-					while ( l-- ) {
-
-						// 97 is 'a'
-						args[ l ] = String.fromCharCode( 97 + l );
-					}
-					return " " + args.join( ", " ) + " ";
-				},
-				// object calls it internally, the key part of an item in a map
-				key: quote,
-				// function calls it internally, it's the content of the function
-				functionCode: "[code]",
-				// node calls it internally, it's an html attribute value
-				attribute: quote,
-				string: quote,
-				date: quote,
-				regexp: literal,
-				number: literal,
-				"boolean": literal
-			},
-			// if true, entities are escaped ( <, >, \t, space and \n )
-			HTML: false,
-			// indentation unit
-			indentChar: "  ",
-			// if true, items in a collection, are separated by a \n, else just a space.
-			multiline: true
-		};
-
-	return dump;
-}());
-
-// back compat
-QUnit.jsDump = QUnit.dump;
-
-// For browser, export only select globals
-if ( typeof window !== "undefined" ) {
-
-	// Deprecated
-	// Extend assert methods to QUnit and Global scope through Backwards compatibility
-	(function() {
-		var i,
-			assertions = Assert.prototype;
-
-		function applyCurrent( current ) {
-			return function() {
-				var assert = new Assert( QUnit.config.current );
-				current.apply( assert, arguments );
-			};
-		}
-
-		for ( i in assertions ) {
-			QUnit[ i ] = applyCurrent( assertions[ i ] );
-		}
-	})();
-
-	(function() {
-		var i, l,
-			keys = [
-				"test",
-				"module",
-				"expect",
-				"asyncTest",
-				"start",
-				"stop",
-				"ok",
-				"equal",
-				"notEqual",
-				"propEqual",
-				"notPropEqual",
-				"deepEqual",
-				"notDeepEqual",
-				"strictEqual",
-				"notStrictEqual",
-				"throws"
-			];
-
-		for ( i = 0, l = keys.length; i < l; i++ ) {
-			window[ keys[ i ] ] = QUnit[ keys[ i ] ];
-		}
-	})();
-
-	window.QUnit = QUnit;
-}
-
-// For CommonJS environments, export everything
-if ( typeof module !== "undefined" && module.exports ) {
-	module.exports = QUnit;
-}
-
-// Get a reference to the global object, like window in browsers
-}( (function() {
-	return this;
-})() ));
-
-/*istanbul ignore next */
-/*
- * Javascript Diff Algorithm
- *  By John Resig (http://ejohn.org/)
- *  Modified by Chu Alan "sprite"
- *
- * Released under the MIT license.
- *
- * More Info:
- *  http://ejohn.org/projects/javascript-diff-algorithm/
- *
- * Usage: QUnit.diff(expected, actual)
- *
- * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
- */
-QUnit.diff = (function() {
-	var hasOwn = Object.prototype.hasOwnProperty;
-
-	/*jshint eqeqeq:false, eqnull:true */
-	function diff( o, n ) {
-		var i,
-			ns = {},
-			os = {};
-
-		for ( i = 0; i < n.length; i++ ) {
-			if ( !hasOwn.call( ns, n[ i ] ) ) {
-				ns[ n[ i ] ] = {
-					rows: [],
-					o: null
-				};
-			}
-			ns[ n[ i ] ].rows.push( i );
-		}
-
-		for ( i = 0; i < o.length; i++ ) {
-			if ( !hasOwn.call( os, o[ i ] ) ) {
-				os[ o[ i ] ] = {
-					rows: [],
-					n: null
-				};
-			}
-			os[ o[ i ] ].rows.push( i );
-		}
-
-		for ( i in ns ) {
-			if ( hasOwn.call( ns, i ) ) {
-				if ( ns[ i ].rows.length === 1 && hasOwn.call( os, i ) && os[ i ].rows.length === 1 ) {
-					n[ ns[ i ].rows[ 0 ] ] = {
-						text: n[ ns[ i ].rows[ 0 ] ],
-						row: os[ i ].rows[ 0 ]
-					};
-					o[ os[ i ].rows[ 0 ] ] = {
-						text: o[ os[ i ].rows[ 0 ] ],
-						row: ns[ i ].rows[ 0 ]
-					};
-				}
-			}
-		}
-
-		for ( i = 0; i < n.length - 1; i++ ) {
-			if ( n[ i ].text != null && n[ i + 1 ].text == null && n[ i ].row + 1 < o.length && o[ n[ i ].row + 1 ].text == null &&
-				n[ i + 1 ] == o[ n[ i ].row + 1 ] ) {
-
-				n[ i + 1 ] = {
-					text: n[ i + 1 ],
-					row: n[ i ].row + 1
-				};
-				o[ n[ i ].row + 1 ] = {
-					text: o[ n[ i ].row + 1 ],
-					row: i + 1
-				};
-			}
-		}
-
-		for ( i = n.length - 1; i > 0; i-- ) {
-			if ( n[ i ].text != null && n[ i - 1 ].text == null && n[ i ].row > 0 && o[ n[ i ].row - 1 ].text == null &&
-				n[ i - 1 ] == o[ n[ i ].row - 1 ] ) {
-
-				n[ i - 1 ] = {
-					text: n[ i - 1 ],
-					row: n[ i ].row - 1
-				};
-				o[ n[ i ].row - 1 ] = {
-					text: o[ n[ i ].row - 1 ],
-					row: i - 1
-				};
-			}
-		}
-
-		return {
-			o: o,
-			n: n
-		};
-	}
-
-	return function( o, n ) {
-		o = o.replace( /\s+$/, "" );
-		n = n.replace( /\s+$/, "" );
-
-		var i, pre,
-			str = "",
-			out = diff( o === "" ? [] : o.split( /\s+/ ), n === "" ? [] : n.split( /\s+/ ) ),
-			oSpace = o.match( /\s+/g ),
-			nSpace = n.match( /\s+/g );
-
-		if ( oSpace == null ) {
-			oSpace = [ " " ];
-		} else {
-			oSpace.push( " " );
-		}
-
-		if ( nSpace == null ) {
-			nSpace = [ " " ];
-		} else {
-			nSpace.push( " " );
-		}
-
-		if ( out.n.length === 0 ) {
-			for ( i = 0; i < out.o.length; i++ ) {
-				str += "<del>" + out.o[ i ] + oSpace[ i ] + "</del>";
-			}
-		} else {
-			if ( out.n[ 0 ].text == null ) {
-				for ( n = 0; n < out.o.length && out.o[ n ].text == null; n++ ) {
-					str += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
-				}
-			}
-
-			for ( i = 0; i < out.n.length; i++ ) {
-				if ( out.n[ i ].text == null ) {
-					str += "<ins>" + out.n[ i ] + nSpace[ i ] + "</ins>";
-				} else {
-
-					// `pre` initialized at top of scope
-					pre = "";
-
-					for ( n = out.n[ i ].row + 1; n < out.o.length && out.o[ n ].text == null; n++ ) {
-						pre += "<del>" + out.o[ n ] + oSpace[ n ] + "</del>";
-					}
-					str += " " + out.n[ i ].text + nSpace[ i ] + pre;
-				}
-			}
-		}
-
-		return str;
-	};
-}());
-
-(function() {
-
-// Deprecated QUnit.init - Ref #530
-// Re-initialize the configuration options
-QUnit.init = function() {
-	var tests, banner, result, qunit,
-		config = QUnit.config;
-
-	config.stats = { all: 0, bad: 0 };
-	config.moduleStats = { all: 0, bad: 0 };
-	config.started = 0;
-	config.updateRate = 1000;
-	config.blocking = false;
-	config.autostart = true;
-	config.autorun = false;
-	config.filter = "";
-	config.queue = [];
-	config.semaphore = 1;
-
-	// Return on non-browser environments
-	// This is necessary to not break on node tests
-	if ( typeof window === "undefined" ) {
-		return;
-	}
-
-	qunit = id( "qunit" );
-	if ( qunit ) {
-		qunit.innerHTML =
-			"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
-			"<h2 id='qunit-banner'></h2>" +
-			"<div id='qunit-testrunner-toolbar'></div>" +
-			"<h2 id='qunit-userAgent'></h2>" +
-			"<ol id='qunit-tests'></ol>";
-	}
-
-	tests = id( "qunit-tests" );
-	banner = id( "qunit-banner" );
-	result = id( "qunit-testresult" );
-
-	if ( tests ) {
-		tests.innerHTML = "";
-	}
-
-	if ( banner ) {
-		banner.className = "";
-	}
-
-	if ( result ) {
-		result.parentNode.removeChild( result );
-	}
-
-	if ( tests ) {
-		result = document.createElement( "p" );
-		result.id = "qunit-testresult";
-		result.className = "result";
-		tests.parentNode.insertBefore( result, tests );
-		result.innerHTML = "Running...<br/>&nbsp;";
-	}
-};
-
-// Resets the test setup. Useful for tests that modify the DOM.
-/*
-DEPRECATED: Use multiple tests instead of resetting inside a test.
-Use testStart or testDone for custom cleanup.
-This method will throw an error in 2.0, and will be removed in 2.1
-*/
-QUnit.reset = function() {
-
-	// Return on non-browser environments
-	// This is necessary to not break on node tests
-	if ( typeof window === "undefined" ) {
-		return;
-	}
-
-	var fixture = id( "qunit-fixture" );
-	if ( fixture ) {
-		fixture.innerHTML = config.fixture;
-	}
-};
-
-// Don't load the HTML Reporter on non-Browser environments
-if ( typeof window === "undefined" ) {
-	return;
-}
-
-var config = QUnit.config,
-	hasOwn = Object.prototype.hasOwnProperty,
-	defined = {
-		document: typeof window.document !== "undefined",
-		sessionStorage: (function() {
-			var x = "qunit-test-string";
-			try {
-				sessionStorage.setItem( x, x );
-				sessionStorage.removeItem( x );
-				return true;
-			} catch ( e ) {
-				return false;
-			}
-		}())
-	};
-
-/**
-* Escape text for attribute or text content.
-*/
-function escapeText( s ) {
-	if ( !s ) {
-		return "";
-	}
-	s = s + "";
-
-	// Both single quotes and double quotes (for attributes)
-	return s.replace( /['"<>&]/g, function( s ) {
-		switch ( s ) {
-		case "'":
-			return "&#039;";
-		case "\"":
-			return "&quot;";
-		case "<":
-			return "&lt;";
-		case ">":
-			return "&gt;";
-		case "&":
-			return "&amp;";
-		}
-	});
-}
-
-/**
- * @param {HTMLElement} elem
- * @param {string} type
- * @param {Function} fn
- */
-function addEvent( elem, type, fn ) {
-	if ( elem.addEventListener ) {
-
-		// Standards-based browsers
-		elem.addEventListener( type, fn, false );
-	} else if ( elem.attachEvent ) {
-
-		// support: IE <9
-		elem.attachEvent( "on" + type, fn );
-	}
-}
-
-/**
- * @param {Array|NodeList} elems
- * @param {string} type
- * @param {Function} fn
- */
-function addEvents( elems, type, fn ) {
-	var i = elems.length;
-	while ( i-- ) {
-		addEvent( elems[ i ], type, fn );
-	}
-}
-
-function hasClass( elem, name ) {
-	return ( " " + elem.className + " " ).indexOf( " " + name + " " ) >= 0;
-}
-
-function addClass( elem, name ) {
-	if ( !hasClass( elem, name ) ) {
-		elem.className += ( elem.className ? " " : "" ) + name;
-	}
-}
-
-function toggleClass( elem, name ) {
-	if ( hasClass( elem, name ) ) {
-		removeClass( elem, name );
-	} else {
-		addClass( elem, name );
-	}
-}
-
-function removeClass( elem, name ) {
-	var set = " " + elem.className + " ";
-
-	// Class name may appear multiple times
-	while ( set.indexOf( " " + name + " " ) >= 0 ) {
-		set = set.replace( " " + name + " ", " " );
-	}
-
-	// trim for prettiness
-	elem.className = typeof set.trim === "function" ? set.trim() : set.replace( /^\s+|\s+$/g, "" );
-}
-
-function id( name ) {
-	return defined.document && document.getElementById && document.getElementById( name );
-}
-
-function getUrlConfigHtml() {
-	var i, j, val,
-		escaped, escapedTooltip,
-		selection = false,
-		len = config.urlConfig.length,
-		urlConfigHtml = "";
-
-	for ( i = 0; i < len; i++ ) {
-		val = config.urlConfig[ i ];
-		if ( typeof val === "string" ) {
-			val = {
-				id: val,
-				label: val
-			};
-		}
-
-		escaped = escapeText( val.id );
-		escapedTooltip = escapeText( val.tooltip );
-
-		config[ val.id ] = QUnit.urlParams[ val.id ];
-		if ( !val.value || typeof val.value === "string" ) {
-			urlConfigHtml += "<input id='qunit-urlconfig-" + escaped +
-				"' name='" + escaped + "' type='checkbox'" +
-				( val.value ? " value='" + escapeText( val.value ) + "'" : "" ) +
-				( config[ val.id ] ? " checked='checked'" : "" ) +
-				" title='" + escapedTooltip + "'><label for='qunit-urlconfig-" + escaped +
-				"' title='" + escapedTooltip + "'>" + val.label + "</label>";
-		} else {
-			urlConfigHtml += "<label for='qunit-urlconfig-" + escaped +
-				"' title='" + escapedTooltip + "'>" + val.label +
-				": </label><select id='qunit-urlconfig-" + escaped +
-				"' name='" + escaped + "' title='" + escapedTooltip + "'><option></option>";
-
-			if ( QUnit.is( "array", val.value ) ) {
-				for ( j = 0; j < val.value.length; j++ ) {
-					escaped = escapeText( val.value[ j ] );
-					urlConfigHtml += "<option value='" + escaped + "'" +
-						( config[ val.id ] === val.value[ j ] ?
-							( selection = true ) && " selected='selected'" : "" ) +
-						">" + escaped + "</option>";
-				}
-			} else {
-				for ( j in val.value ) {
-					if ( hasOwn.call( val.value, j ) ) {
-						urlConfigHtml += "<option value='" + escapeText( j ) + "'" +
-							( config[ val.id ] === j ?
-								( selection = true ) && " selected='selected'" : "" ) +
-							">" + escapeText( val.value[ j ] ) + "</option>";
-					}
-				}
-			}
-			if ( config[ val.id ] && !selection ) {
-				escaped = escapeText( config[ val.id ] );
-				urlConfigHtml += "<option value='" + escaped +
-					"' selected='selected' disabled='disabled'>" + escaped + "</option>";
-			}
-			urlConfigHtml += "</select>";
-		}
-	}
-
-	return urlConfigHtml;
-}
-
-function toolbarUrlConfigContainer() {
-	var urlConfigContainer = document.createElement( "span" );
-
-	urlConfigContainer.innerHTML = getUrlConfigHtml();
-
-	// For oldIE support:
-	// * Add handlers to the individual elements instead of the container
-	// * Use "click" instead of "change" for checkboxes
-	// * Fallback from event.target to event.srcElement
-	addEvents( urlConfigContainer.getElementsByTagName( "input" ), "click", function( event ) {
-		var params = {},
-			target = event.target || event.srcElement;
-		params[ target.name ] = target.checked ?
-			target.defaultValue || true :
-			undefined;
-		window.location = QUnit.url( params );
-	});
-	addEvents( urlConfigContainer.getElementsByTagName( "select" ), "change", function( event ) {
-		var params = {},
-			target = event.target || event.srcElement;
-		params[ target.name ] = target.options[ target.selectedIndex ].value || undefined;
-		window.location = QUnit.url( params );
-	});
-
-	return urlConfigContainer;
-}
-
-function getModuleNames() {
-	var i,
-		moduleNames = [];
-
-	for ( i in config.modules ) {
-		if ( config.modules.hasOwnProperty( i ) ) {
-			moduleNames.push( i );
-		}
-	}
-
-	moduleNames.sort(function( a, b ) {
-		return a.localeCompare( b );
-	});
-
-	return moduleNames;
-}
-
-function toolbarModuleFilterHtml() {
-	var i,
-		moduleFilterHtml = "",
-		moduleNames = getModuleNames();
-
-	if ( moduleNames.length <= 1 ) {
-		return false;
-	}
-
-	moduleFilterHtml += "<label for='qunit-modulefilter'>Module: </label>" +
-		"<select id='qunit-modulefilter' name='modulefilter'><option value='' " +
-		( config.module === undefined ? "selected='selected'" : "" ) +
-		">< All Modules ></option>";
-
-	for ( i = 0; i < moduleNames.length; i++ ) {
-		moduleFilterHtml += "<option value='" +
-			escapeText( encodeURIComponent( moduleNames[ i ] ) ) + "' " +
-			( config.module === moduleNames[ i ] ? "selected='selected'" : "" ) +
-			">" + escapeText( moduleNames[ i ] ) + "</option>";
-	}
-	moduleFilterHtml += "</select>";
-
-	return moduleFilterHtml;
-}
-
-function toolbarModuleFilter() {
-	var moduleFilter = document.createElement( "span" ),
-		moduleFilterHtml = toolbarModuleFilterHtml();
-
-	if ( !moduleFilterHtml ) {
-		return false;
-	}
-
-	moduleFilter.setAttribute( "id", "qunit-modulefilter-container" );
-	moduleFilter.innerHTML = moduleFilterHtml;
-
-	addEvent( moduleFilter.lastChild, "change", function() {
-		var selectBox = moduleFilter.getElementsByTagName( "select" )[ 0 ],
-			selectedModule = decodeURIComponent( selectBox.options[ selectBox.selectedIndex ].value );
-
-		window.location = QUnit.url({
-			module: ( selectedModule === "" ) ? undefined : selectedModule,
-
-			// Remove any existing filters
-			filter: undefined,
-			testNumber: undefined
-		});
-	});
-
-	return moduleFilter;
-}
-
-function toolbarFilter() {
-	var testList = id( "qunit-tests" ),
-		filter = document.createElement( "input" );
-
-	filter.type = "checkbox";
-	filter.id = "qunit-filter-pass";
-
-	addEvent( filter, "click", function() {
-		if ( filter.checked ) {
-			addClass( testList, "hidepass" );
-			if ( defined.sessionStorage ) {
-				sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
-			}
-		} else {
-			removeClass( testList, "hidepass" );
-			if ( defined.sessionStorage ) {
-				sessionStorage.removeItem( "qunit-filter-passed-tests" );
-			}
-		}
-	});
-
-	if ( config.hidepassed || defined.sessionStorage &&
-			sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
-		filter.checked = true;
-
-		addClass( testList, "hidepass" );
-	}
-
-	return filter;
-}
-
-function toolbarLabel() {
-	var label = document.createElement( "label" );
-	label.setAttribute( "for", "qunit-filter-pass" );
-	label.setAttribute( "title", "Only show tests and assertions that fail. Stored in sessionStorage." );
-	label.innerHTML = "Hide passed tests";
-
-	return label;
-}
-
-function appendToolbar() {
-	var moduleFilter,
-		toolbar = id( "qunit-testrunner-toolbar" );
-
-	if ( toolbar ) {
-		toolbar.appendChild( toolbarFilter() );
-		toolbar.appendChild( toolbarLabel() );
-		toolbar.appendChild( toolbarUrlConfigContainer() );
-
-		moduleFilter = toolbarModuleFilter();
-		if ( moduleFilter ) {
-			toolbar.appendChild( moduleFilter );
-		}
-	}
-}
-
-function appendBanner() {
-	var banner = id( "qunit-banner" );
-
-	if ( banner ) {
-		banner.className = "";
-		banner.innerHTML = "<a href='" +
-			QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) +
-			"'>" + banner.innerHTML + "</a> ";
-	}
-}
-
-function appendTestResults() {
-	var tests = id( "qunit-tests" ),
-		result = id( "qunit-testresult" );
-
-	if ( result ) {
-		result.parentNode.removeChild( result );
-	}
-
-	if ( tests ) {
-		tests.innerHTML = "";
-		result = document.createElement( "p" );
-		result.id = "qunit-testresult";
-		result.className = "result";
-		tests.parentNode.insertBefore( result, tests );
-		result.innerHTML = "Running...<br>&nbsp;";
-	}
-}
-
-function storeFixture() {
-	var fixture = id( "qunit-fixture" );
-	if ( fixture ) {
-		config.fixture = fixture.innerHTML;
-	}
-}
-
-function appendUserAgent() {
-	var userAgent = id( "qunit-userAgent" );
-	if ( userAgent ) {
-		userAgent.innerHTML = navigator.userAgent;
-	}
-}
-
-// HTML Reporter initialization and load
-QUnit.begin(function() {
-	var qunit = id( "qunit" );
-
-	if ( qunit ) {
-		qunit.innerHTML =
-		"<h1 id='qunit-header'>" + escapeText( document.title ) + "</h1>" +
-		"<h2 id='qunit-banner'></h2>" +
-		"<div id='qunit-testrunner-toolbar'></div>" +
-		"<h2 id='qunit-userAgent'></h2>" +
-		"<ol id='qunit-tests'></ol>";
-	}
-
-	appendBanner();
-	appendTestResults();
-	appendUserAgent();
-	appendToolbar();
-	storeFixture();
-});
-
-QUnit.done(function( details ) {
-	var i, key,
-		banner = id( "qunit-banner" ),
-		tests = id( "qunit-tests" ),
-		html = [
-			"Tests completed in ",
-			details.runtime,
-			" milliseconds.<br>",
-			"<span class='passed'>",
-			details.passed,
-			"</span> assertions of <span class='total'>",
-			details.total,
-			"</span> passed, <span class='failed'>",
-			details.failed,
-			"</span> failed."
-		].join( "" );
-
-	if ( banner ) {
-		banner.className = details.failed ? "qunit-fail" : "qunit-pass";
-	}
-
-	if ( tests ) {
-		id( "qunit-testresult" ).innerHTML = html;
-	}
-
-	if ( config.altertitle && defined.document && document.title ) {
-
-		// show ✖ for good, ✔ for bad suite result in title
-		// use escape sequences in case file gets loaded with non-utf-8-charset
-		document.title = [
-			( details.failed ? "\u2716" : "\u2714" ),
-			document.title.replace( /^[\u2714\u2716] /i, "" )
-		].join( " " );
-	}
-
-	// clear own sessionStorage items if all tests passed
-	if ( config.reorder && defined.sessionStorage && details.failed === 0 ) {
-		for ( i = 0; i < sessionStorage.length; i++ ) {
-			key = sessionStorage.key( i++ );
-			if ( key.indexOf( "qunit-test-" ) === 0 ) {
-				sessionStorage.removeItem( key );
-			}
-		}
-	}
-
-	// scroll back to top to show results
-	if ( config.scrolltop && window.scrollTo ) {
-		window.scrollTo( 0, 0 );
-	}
-});
-
-function getNameHtml( name, module ) {
-	var nameHtml = "";
-
-	if ( module ) {
-		nameHtml = "<span class='module-name'>" + escapeText( module ) + "</span>: ";
-	}
-
-	nameHtml += "<span class='test-name'>" + escapeText( name ) + "</span>";
-
-	return nameHtml;
-}
-
-QUnit.testStart(function( details ) {
-	var a, b, li, running, assertList,
-		name = getNameHtml( details.name, details.module ),
-		tests = id( "qunit-tests" );
-
-	if ( tests ) {
-		b = document.createElement( "strong" );
-		b.innerHTML = name;
-
-		a = document.createElement( "a" );
-		a.innerHTML = "Rerun";
-		a.href = QUnit.url({ testNumber: details.testNumber });
-
-		li = document.createElement( "li" );
-		li.appendChild( b );
-		li.appendChild( a );
-		li.className = "running";
-		li.id = "qunit-test-output" + details.testNumber;
-
-		assertList = document.createElement( "ol" );
-		assertList.className = "qunit-assert-list";
-
-		li.appendChild( assertList );
-
-		tests.appendChild( li );
-	}
-
-	running = id( "qunit-testresult" );
-	if ( running ) {
-		running.innerHTML = "Running: <br>" + name;
-	}
-
-});
-
-QUnit.log(function( details ) {
-	var assertList, assertLi,
-		message, expected, actual,
-		testItem = id( "qunit-test-output" + details.testNumber );
-
-	if ( !testItem ) {
-		return;
-	}
-
-	message = escapeText( details.message ) || ( details.result ? "okay" : "failed" );
-	message = "<span class='test-message'>" + message + "</span>";
-
-	// pushFailure doesn't provide details.expected
-	// when it calls, it's implicit to also not show expected and diff stuff
-	// Also, we need to check details.expected existence, as it can exist and be undefined
-	if ( !details.result && hasOwn.call( details, "expected" ) ) {
-		expected = escapeText( QUnit.dump.parse( details.expected ) );
-		actual = escapeText( QUnit.dump.parse( details.actual ) );
-		message += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" +
-			expected +
-			"</pre></td></tr>";
-
-		if ( actual !== expected ) {
-			message += "<tr class='test-actual'><th>Result: </th><td><pre>" +
-				actual + "</pre></td></tr>" +
-				"<tr class='test-diff'><th>Diff: </th><td><pre>" +
-				QUnit.diff( expected, actual ) + "</pre></td></tr>";
-		}
-
-		if ( details.source ) {
-			message += "<tr class='test-source'><th>Source: </th><td><pre>" +
-				escapeText( details.source ) + "</pre></td></tr>";
-		}
-
-		message += "</table>";
-
-	// this occours when pushFailure is set and we have an extracted stack trace
-	} else if ( !details.result && details.source ) {
-		message += "<table>" +
-			"<tr class='test-source'><th>Source: </th><td><pre>" +
-			escapeText( details.source ) + "</pre></td></tr>" +
-			"</table>";
-	}
-
-	assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
-
-	assertLi = document.createElement( "li" );
-	assertLi.className = details.result ? "pass" : "fail";
-	assertLi.innerHTML = message;
-	assertList.appendChild( assertLi );
-});
-
-QUnit.testDone(function( details ) {
-	var testTitle, time, testItem, assertList,
-		good, bad, testCounts,
-		tests = id( "qunit-tests" );
-
-	// QUnit.reset() is deprecated and will be replaced for a new
-	// fixture reset function on QUnit 2.0/2.1.
-	// It's still called here for backwards compatibility handling
-	QUnit.reset();
-
-	if ( !tests ) {
-		return;
-	}
-
-	testItem = id( "qunit-test-output" + details.testNumber );
-	assertList = testItem.getElementsByTagName( "ol" )[ 0 ];
-
-	good = details.passed;
-	bad = details.failed;
-
-	// store result when possible
-	if ( config.reorder && defined.sessionStorage ) {
-		if ( bad ) {
-			sessionStorage.setItem( "qunit-test-" + details.module + "-" + details.name, bad );
-		} else {
-			sessionStorage.removeItem( "qunit-test-" + details.module + "-" + details.name );
-		}
-	}
-
-	if ( bad === 0 ) {
-		addClass( assertList, "qunit-collapsed" );
-	}
-
-	// testItem.firstChild is the test name
-	testTitle = testItem.firstChild;
-
-	testCounts = bad ?
-		"<b class='failed'>" + bad + "</b>, " + "<b class='passed'>" + good + "</b>, " :
-		"";
-
-	testTitle.innerHTML += " <b class='counts'>(" + testCounts +
-		details.assertions.length + ")</b>";
-
-	addEvent( testTitle, "click", function() {
-		toggleClass( assertList, "qunit-collapsed" );
-	});
-
-	time = document.createElement( "span" );
-	time.className = "runtime";
-	time.innerHTML = details.runtime + " ms";
-
-	testItem.className = bad ? "fail" : "pass";
-
-	testItem.insertBefore( time, assertList );
-});
-
-if ( !defined.document || document.readyState === "complete" ) {
-	config.autorun = true;
-}
-
-if ( defined.document ) {
-	addEvent( window, "load", QUnit.load );
-}
-
-})();

+ 0 - 237
contrib/views/files/src/main/resources/ui/app/assets/stylesheets/qunit.css

@@ -1,237 +0,0 @@
-/*!
- * QUnit 1.15.0
- * http://qunitjs.com/
- *
- * Copyright 2014 jQuery Foundation and other contributors
- * Released under the MIT license
- * http://jquery.org/license
- *
- * Date: 2014-08-08T16:00Z
- */
-
-/** Font Family and Sizes */
-
-#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
-	font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
-}
-
-#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
-#qunit-tests { font-size: smaller; }
-
-
-/** Resets */
-
-#qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
-	margin: 0;
-	padding: 0;
-}
-
-
-/** Header */
-
-#qunit-header {
-	padding: 0.5em 0 0.5em 1em;
-
-	color: #8699A4;
-	background-color: #0D3349;
-
-	font-size: 1.5em;
-	line-height: 1em;
-	font-weight: 400;
-
-	border-radius: 5px 5px 0 0;
-}
-
-#qunit-header a {
-	text-decoration: none;
-	color: #C2CCD1;
-}
-
-#qunit-header a:hover,
-#qunit-header a:focus {
-	color: #FFF;
-}
-
-#qunit-testrunner-toolbar label {
-	display: inline-block;
-	padding: 0 0.5em 0 0.1em;
-}
-
-#qunit-banner {
-	height: 5px;
-}
-
-#qunit-testrunner-toolbar {
-	padding: 0.5em 1em 0.5em 1em;
-	color: #5E740B;
-	background-color: #EEE;
-	overflow: hidden;
-}
-
-#qunit-userAgent {
-	padding: 0.5em 1em 0.5em 1em;
-	background-color: #2B81AF;
-	color: #FFF;
-	text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
-}
-
-#qunit-modulefilter-container {
-	float: right;
-}
-
-/** Tests: Pass/Fail */
-
-#qunit-tests {
-	list-style-position: inside;
-}
-
-#qunit-tests li {
-	padding: 0.4em 1em 0.4em 1em;
-	border-bottom: 1px solid #FFF;
-	list-style-position: inside;
-}
-
-#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running  {
-	display: none;
-}
-
-#qunit-tests li strong {
-	cursor: pointer;
-}
-
-#qunit-tests li a {
-	padding: 0.5em;
-	color: #C2CCD1;
-	text-decoration: none;
-}
-#qunit-tests li a:hover,
-#qunit-tests li a:focus {
-	color: #000;
-}
-
-#qunit-tests li .runtime {
-	float: right;
-	font-size: smaller;
-}
-
-.qunit-assert-list {
-	margin-top: 0.5em;
-	padding: 0.5em;
-
-	background-color: #FFF;
-
-	border-radius: 5px;
-}
-
-.qunit-collapsed {
-	display: none;
-}
-
-#qunit-tests table {
-	border-collapse: collapse;
-	margin-top: 0.2em;
-}
-
-#qunit-tests th {
-	text-align: right;
-	vertical-align: top;
-	padding: 0 0.5em 0 0;
-}
-
-#qunit-tests td {
-	vertical-align: top;
-}
-
-#qunit-tests pre {
-	margin: 0;
-	white-space: pre-wrap;
-	word-wrap: break-word;
-}
-
-#qunit-tests del {
-	background-color: #E0F2BE;
-	color: #374E0C;
-	text-decoration: none;
-}
-
-#qunit-tests ins {
-	background-color: #FFCACA;
-	color: #500;
-	text-decoration: none;
-}
-
-/*** Test Counts */
-
-#qunit-tests b.counts                       { color: #000; }
-#qunit-tests b.passed                       { color: #5E740B; }
-#qunit-tests b.failed                       { color: #710909; }
-
-#qunit-tests li li {
-	padding: 5px;
-	background-color: #FFF;
-	border-bottom: none;
-	list-style-position: inside;
-}
-
-/*** Passing Styles */
-
-#qunit-tests li li.pass {
-	color: #3C510C;
-	background-color: #FFF;
-	border-left: 10px solid #C6E746;
-}
-
-#qunit-tests .pass                          { color: #528CE0; background-color: #D2E0E6; }
-#qunit-tests .pass .test-name               { color: #366097; }
-
-#qunit-tests .pass .test-actual,
-#qunit-tests .pass .test-expected           { color: #999; }
-
-#qunit-banner.qunit-pass                    { background-color: #C6E746; }
-
-/*** Failing Styles */
-
-#qunit-tests li li.fail {
-	color: #710909;
-	background-color: #FFF;
-	border-left: 10px solid #EE5757;
-	white-space: pre;
-}
-
-#qunit-tests > li:last-child {
-	border-radius: 0 0 5px 5px;
-}
-
-#qunit-tests .fail                          { color: #000; background-color: #EE5757; }
-#qunit-tests .fail .test-name,
-#qunit-tests .fail .module-name             { color: #000; }
-
-#qunit-tests .fail .test-actual             { color: #EE5757; }
-#qunit-tests .fail .test-expected           { color: #008000; }
-
-#qunit-banner.qunit-fail                    { background-color: #EE5757; }
-
-
-/** Result */
-
-#qunit-testresult {
-	padding: 0.5em 1em 0.5em 1em;
-
-	color: #2B81AF;
-	background-color: #D2E0E6;
-
-	border-bottom: 1px solid #FFF;
-}
-#qunit-testresult .module-name {
-	font-weight: 700;
-}
-
-/** Fixture */
-
-#qunit-fixture {
-	position: absolute;
-	top: -10000px;
-	left: -10000px;
-	width: 1000px;
-	height: 1000px;
-}

+ 0 - 46
contrib/views/files/src/main/resources/ui/app/assets/tests.html

@@ -1,46 +0,0 @@
-<!--
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
-*
-*     http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
--->
-
-<!DOCTYPE html>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Files tests</title>
-  <link rel="stylesheet" href="stylesheets/qunit.css">
-</head>
-<body>
-  <div id="qunit"></div>
-  <div id="qunit-fixture"></div>
-  <div id="ember-testing"></div>
-  <script src="javascripts/qunit.js"></script>
-  <script src="javascripts/vendor.js"></script>
-  <script src="javascripts/jquery.mockjax.js"></script>
-  <script src="javascripts/ember-qunit.js"></script>
-  <script src="javascripts/app.js"></script>
-  <script>
-    emq.globalize();
-    require('initialize');
-    App.setupForTesting();
-    App.injectTestHelpers();
-    setResolver(Em.DefaultResolver.create({ namespace: App }));
-    App.rootElement = '#ember-testing'
-  </script>
-  <script src="javascripts/test.js"></script>
-  <script src="javascripts/tests.js"></script>
-</body>
-</html>

+ 0 - 0
contrib/views/files/src/main/resources/ui/app/components/.gitkeep


+ 45 - 0
contrib/views/files/src/main/resources/ui/app/components/alert-message-display.js

@@ -0,0 +1,45 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import {shortenText} from '../helpers/shorten-text';
+
+export default Ember.Component.extend({
+  shorten: false,
+  length: 100,
+  expanded: false,
+  shortenedValue: Ember.computed('value', 'shorten', 'expanded', function() {
+    if (this.get('expanded')) {
+      return this.get('value');
+    }
+    if (this.get('shorten')) {
+      let length = this.get('length');
+      let shortenedText = shortenText([this.get('value'), length]);
+      this.set('shorten', shortenedText !== this.get('value'));
+      return shortenedText;
+    } else {
+      return this.get('value');
+    }
+  }),
+
+  actions: {
+    toggleExpanded() {
+      this.toggleProperty('expanded');
+    }
+  }
+});

+ 32 - 0
contrib/views/files/src/main/resources/ui/app/components/alert-message.js

@@ -0,0 +1,32 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  classNames: ['flash-messages'],
+
+  actions: {
+    closeAlert() {
+      const flash = this.get('flash');
+      flash.destroyMessage();
+    }
+  }
+
+
+});

+ 0 - 47
contrib/views/files/src/main/resources/ui/app/components/breadCrumbs.js

@@ -1,47 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.BreadCrumbsComponent = Ember.CollectionView.extend({
-  classNames: ['breadcrumb pull-left'],
-  tagName: 'ul',
-  path:'',
-  content: function (argument) {
-    var crumbs = [];
-    var currentPath = this.get('path').match(/((?!\/)\S)+/g)||[];
-    currentPath.forEach(function (cur,i,array) {
-      return crumbs.push({name:cur,path:'/'+array.slice(0,i+1).join('/')});
-    });
-    crumbs.unshift({name:'/',path:'/'});
-    crumbs.set('lastObject.last','true');
-    return crumbs;
-  }.property('path'),
-  itemViewClass: Ember.View.extend({
-    classNameBindings: ['isActive:active'],
-    template: Ember.Handlebars.compile("{{#link-to 'files' (query-params path=view.content.path)}}{{view.content.name}}{{/link-to}}"),
-    isActive: function () {
-      return this.get('content.last');
-    }.property('content'),
-    click:function () {
-      if (this.get('isActive')) {
-        this.get('controller').send('refreshDir');
-      }
-    }
-  })
-});

+ 0 - 29
contrib/views/files/src/main/resources/ui/app/components/bulkCheckbox.js

@@ -1,29 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.BulkCheckboxComponent = Em.Checkbox.extend({
-  selectedAll:Em.computed.alias('checked'),
-  selectAll:function () {
-    this.get('content').setEach('selected',this.get('selectedAll'));
-  }.on('change'),
-  selection:function () {
-    this.set('selectedAll', !!(this.get('content.length') && this.get('content').isEvery('selected',true)));
-  }.observes('content.@each.selected')
-});

+ 0 - 65
contrib/views/files/src/main/resources/ui/app/components/confirmDelete.js

@@ -1,65 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.DropdownWrapComponent = Em.Component.extend({
-  onResetConfirm:function () {
-    var childs = this.get('childViews').filter(function (view) {
-      return view instanceof App.ConfirmDeleteComponent;
-    });
-    childs.setEach('isRemoving',false);
-  }.on('resetConfirm'),
-  didInsertElement:function(){
-    this.$().on('hidden.bs.dropdown',Em.run.bind(this,this.onResetConfirm));
-  }
-});
-
-App.ConfirmDeleteComponent = Em.Component.extend({
-  layoutName:'components/deleteBulk',
-  tagName:'li',
-  classNameBindings:['access::disabled'],
-  deleteForever:false,
-  access:false,
-  isRemoving:false,
-  cancelRemoving:function () {
-    this.set('isRemoving',false);
-  },
-  click:function  (e) {
-    if (!$(e.target).hasClass('delete')) {
-      e.stopPropagation();
-    }
-  },
-  actions:{
-    ask:function () {
-      if (this.get('access')) {
-        this.get('parentView').trigger('resetConfirm');
-        this.set('isRemoving',true);
-      }
-      return false;
-    },
-    cancel:function () {
-      this.cancelRemoving();
-    },
-    confirm:function () {
-      if (this.get('access')) {
-        this.sendAction('confirm',this.get('deleteForever'));
-      }
-    }
-  }
-});

+ 137 - 0
contrib/views/files/src/main/resources/ui/app/components/context-row-menu.js

@@ -0,0 +1,137 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  fileSelectionService: Ember.inject.service('files-selection'),
+  modalEventBus: Ember.inject.service('modal-event-bus'),
+  alertMessages: Ember.inject.service('alert-messages'),
+  filesDownloadService: Ember.inject.service('files-download'),
+
+  classNames: ['row', 'context-menu-row'],
+  selectedFilesCount: Ember.computed.oneWay('fileSelectionService.filesCount'),
+  selectedFolderCount: Ember.computed.oneWay('fileSelectionService.folderCount'),
+  isMultiSelected: Ember.computed('selectedFilesCount', 'selectedFolderCount', function() {
+    return this.get('selectedFilesCount') + this.get('selectedFolderCount') > 1;
+  }),
+  isSingleSelected: Ember.computed('selectedFilesCount', 'selectedFolderCount', function() {
+    return this.get('selectedFilesCount') + this.get('selectedFolderCount') === 1;
+  }),
+  isSelected: Ember.computed('selectedFilesCount', 'selectedFolderCount', function() {
+    return (this.get('selectedFilesCount') + this.get('selectedFolderCount')) !== 0;
+  }),
+  isOnlyMultiFilesSelected: Ember.computed('selectedFilesCount', 'selectedFolderCount', function() {
+    return this.get('selectedFolderCount') === 0 && this.get('selectedFilesCount') > 1;
+  }),
+
+  didInitAttrs: function() {
+    // Register different modal so that they can be controlled from outside
+    this.get('modalEventBus').registerModal('ctx-open');
+    this.get('modalEventBus').registerModal('ctx-rename');
+    this.get('modalEventBus').registerModal('ctx-permission');
+    this.get('modalEventBus').registerModal('ctx-delete');
+    this.get('modalEventBus').registerModal('ctx-copy');
+    this.get('modalEventBus').registerModal('ctx-move');
+    this.get('modalEventBus').registerModal('ctx-download');
+    this.get('modalEventBus').registerModal('ctx-concatenate');
+  },
+
+  willDestroyElement() {
+    this.get('modalEventBus').resetModal('ctx-open');
+    this.get('modalEventBus').resetModal('ctx-rename');
+    this.get('modalEventBus').resetModal('ctx-permission');
+    this.get('modalEventBus').resetModal('ctx-delete');
+    this.get('modalEventBus').resetModal('ctx-copy');
+    this.get('modalEventBus').resetModal('ctx-move');
+    this.get('modalEventBus').resetModal('ctx-download');
+    this.get('modalEventBus').resetModal('ctx-concatenate');
+  },
+
+  actions: {
+    open: function(event) {
+      if (this.get('isSingleSelected')) {
+        var file = this.get('fileSelectionService.files').objectAt(0);
+        if (file.get('isDirectory')) {
+          this.sendAction('openFolderAction', file.get('path'));
+        } else {
+          this.get('modalEventBus').showModal('ctx-open');
+        }
+      }
+
+    },
+
+    delete: function(event) {
+      if (!this.get('isSelected')) {
+        return false;
+      }
+      this.get('modalEventBus').showModal('ctx-delete');
+    },
+
+    copy: function(event) {
+      if (!this.get('isSelected')) {
+        return false;
+      }
+      this.get('modalEventBus').showModal('ctx-copy');
+    },
+
+    move: function(event) {
+      if (!this.get('isSelected')) {
+        return false;
+      }
+      this.get('modalEventBus').showModal('ctx-move');
+    },
+
+    download: function(event) {
+      if (!this.get('isSelected')) {
+        return false;
+      }
+      this.get('filesDownloadService').download();
+    },
+
+    concatenate: function(event) {
+      if (!this.get('isOnlyMultiFilesSelected')) {
+        return false;
+      }
+      this.get('filesDownloadService').concatenate();
+    },
+
+    rename: function(event) {
+      if (!this.get('isSingleSelected')) {
+        return false;
+      }
+      this.get('modalEventBus').showModal('ctx-rename');
+    },
+    permission: function(event) {
+      if (!this.get('isSingleSelected')) {
+        return false;
+      }
+      this.get('modalEventBus').showModal('ctx-permission');
+    },
+
+    modalClosed: function(modalName) {
+      this.get('modalEventBus').resetModal(modalName);
+    },
+
+    refreshCurrentRoute: function() {
+      this.get('fileSelectionService').reset();
+      this.sendAction('refreshCurrentRouteAction');
+    }
+  }
+
+});

+ 126 - 0
contrib/views/files/src/main/resources/ui/app/components/copy-modal.js

@@ -0,0 +1,126 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import OperationModal from '../mixins/operation-modal';
+
+export default Ember.Component.extend(OperationModal, {
+  closeOnEscape: true,
+  fileSelectionService: Ember.inject.service('files-selection'),
+  fileOperationService: Ember.inject.service('file-operation'),
+  selectedFiles: Ember.computed.alias('fileSelectionService.files'),
+  selected: Ember.computed('selectedFiles', function () {
+    return this.get('selectedFiles').objectAt(0);
+  }),
+  selectionName: '/',
+  isUpdating: false,
+  browseError: false,
+  browseErrorMessege: '',
+  hasError: false,
+  shouldRetry: false,
+  currentFailedPath: '',
+  currentUnprocessedPaths: [],
+  currentFailureMessage: '',
+
+  copyPaths: function (paths, destination) {
+    this.set('isUpdating', true);
+
+    this.get('fileOperationService').copyPaths(paths, destination).then(
+      (response) => {
+        this.set('isUpdating', false);
+        this.send('close');
+        this.sendAction('refreshAction');
+      }, (error) => {
+        this.set('isUpdating', false);
+        if (error.unprocessable === true) {
+          this.set('hasError', true);
+          this.set('currentFailedPath', error.failed);
+          this.set('currentFailureMessage', error.message);
+          this.set('shouldRetry', error.retry);
+          this.set('currentUnprocessedPaths', error.unprocessed);
+        } else {
+          this.set('isUpdating', false);
+          this.get('logger').danger("Failed to delete files and folders.", error);
+          this.send('close');
+        }
+      });
+  },
+  reset: function () {
+    this.set('browseError', false);
+    this.set('browseErrorMessege', '');
+    this.set('selectionName', '/');
+    this.set('hasError', false);
+    this.set('shouldRetry', false);
+    this.set('isUpdating', false);
+    this.set('currentFailedPath', '');
+    this.set('currentFailureMessage', '');
+    this.set('currentUnprocessedPaths', '');
+  },
+  actions: {
+
+    didOpenModal: function () {
+      this.reset();
+      console.log("Move modal opened");
+    },
+
+    didCloseModal: function () {
+      console.log("Move Modal did close.");
+    },
+
+    copy: function () {
+      var currentPathsToMove = this.get('selectedFiles').map((entry) => {
+        return entry.get('path')
+      });
+      var destinationPath = (this.get('selectionName') !== '') ? this.get('selectionName') : '/';
+      this.copyPaths(currentPathsToMove, destinationPath);
+    },
+
+    retryError: function () {
+      var newPaths = [this.get('currentFailedPath')];
+      if (Ember.isArray(this.get('currentUnprocessedPaths'))) {
+        newPaths.pushObjects(this.get('currentUnprocessedPaths'));
+      }
+      var destinationPath = (this.get('selectionName') !== '') ? this.get('selectionName') : '/';
+      this.copyPaths(newPaths, destinationPath);
+    },
+
+    skipAndRetry: function () {
+      var destinationPath = (this.get('selectionName') !== '') ? this.get('selectionName') : '/';
+      this.copyPaths(this.get('currentUnprocessedPaths'), destinationPath);
+    },
+
+    skipAll: function () {
+      this.send('close');
+      this.sendAction('refreshAction');
+    },
+
+    pathSelected: function (path) {
+      console.log(path);
+      this.set('selectionName', path);
+      this.set('browseError', false);
+
+    },
+
+    browseError: function (error) {
+      this.set('browseError', true);
+      this.set('browseErrorMessage', error.message);
+    }
+  }
+
+
+});

+ 130 - 0
contrib/views/files/src/main/resources/ui/app/components/delete-modal.js

@@ -0,0 +1,130 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import OperationModal from '../mixins/operation-modal';
+
+export default Ember.Component.extend(OperationModal, {
+  fileSelectionService: Ember.inject.service('files-selection'),
+  fileOperationService: Ember.inject.service('file-operation'),
+  logger: Ember.inject.service('alert-messages'),
+  closeOnEscape: true,
+  deletePermanently: false,
+  deletePermanentlyAlways: false,
+  showDeletePermanentCheckbox: true,
+  selectedFiles: Ember.computed.alias('fileSelectionService.files'),
+  filesCount: Ember.computed.oneWay('fileSelectionService.filesCount'),
+  folderCount: Ember.computed.oneWay('fileSelectionService.folderCount'),
+  hasFiles: Ember.computed('filesCount', function() {
+    return this.get('filesCount') > 0;
+  }),
+  hasFolders: Ember.computed('folderCount', function() {
+    return this.get('folderCount') > 0;
+  }),
+  hasError: false,
+  shouldRetry: false,
+  currentFailedPath: '',
+  currentUnprocessedPaths: [],
+  currentFailureMessage: '',
+  currentServerFailureMessage: '',
+  isDeleting: false,
+
+  setTrashSettings: Ember.on('init', Ember.observer('currentPathIsTrash', function() {
+    if(this.get('currentPathIsTrash')) {
+      this.set('deletePermanentlyAlways', true);
+      this.set('showDeletePermanentCheckbox', false);
+    } else {
+      this.set('deletePermanentlyAlways', false);
+      this.set('showDeletePermanentCheckbox', true);
+    }
+
+  })),
+
+  disableCloseOnEscape: Ember.observer('isDeleting', function() {
+    if (this.get('isDeleting') === true) {
+      this.set('closeOnEscape', false);
+    } else {
+      this.set('closeOnEscape', true);
+    }
+  }),
+
+  deletePaths: function(paths) {
+    this.set('isDeleting', true);
+    let deletePermanently = this.get('deletePermanently');
+    if(this.get('deletePermanentlyAlways')) {
+      deletePermanently = true;
+    }
+    this.get('fileOperationService').deletePaths(paths, deletePermanently).then(
+      (response) => {
+        this.set('isDeleting', false);
+        this.send('close');
+        this.sendAction('refreshAction');
+      }, (error) => {
+        this.set('isDeleting', false);
+        if (error.unprocessable === true) {
+          this.set('hasError', true);
+          this.set('currentFailedPath', error.failed);
+          this.set('currentServerFailureMessage', error.message);
+          this.set('currentFailureMessage', `Failed to delete <strong>${error.failed}</strong>.`);
+          this.set('shouldRetry', error.retry);
+          this.set('currentUnprocessedPaths', error.unprocessed);
+        } else {
+          this.set('isDeleting', false);
+          this.get('logger').danger("Failed to delete files and folders.", error);
+          this.send('close');
+        }
+      });
+  },
+  reset: function() {
+    this.set('deletePermanently', false);
+    this.set('hasError', false);
+    this.set('shouldRetry', false);
+    this.set('isDeleting', false);
+    this.set('currentFailedPath', '');
+    this.set('currentFailureMessage', '');
+    this.set('currentUnprocessedPaths', '');
+  },
+  actions: {
+    didOpenModal: function() {
+      this.reset();
+      console.log("Delete modal opened");
+    },
+
+    didCloseModal: function() {
+      console.log("Delete Modal closed");
+    },
+    delete: function() {
+      var currentPathsToDelete = this.get('selectedFiles').map((entry) => { return entry.get('path');});
+      this.deletePaths(currentPathsToDelete);
+    },
+    retryError: function() {
+      var newPaths = [this.get('currentFailedPath')];
+      if (Ember.isArray(this.get('currentUnprocessedPaths'))) {
+        newPaths.pushObjects(this.get('currentUnprocessedPaths'));
+      }
+      this.deletePaths(newPaths);
+    },
+    skipAndRetry: function() {
+      this.deletePaths(this.get('currentUnprocessedPaths'));
+    },
+    skipAll: function() {
+      this.send('close');
+      this.sendAction('refreshAction');
+    }
+  }
+});

+ 165 - 0
contrib/views/files/src/main/resources/ui/app/components/directory-viewer.js

@@ -0,0 +1,165 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import FileOperationMixin from '../mixins/file-operation';
+
+export default Ember.Component.extend(FileOperationMixin, {
+  fileOperationService: Ember.inject.service('file-operation'),
+  classNames: ['directory-viewer'],
+  startPath: '/',
+  treeData: Ember.A(),
+  currentPath: Ember.computed.oneWay('startPath'),
+  currentQueryParam: Ember.computed('currentPath', function() {
+    return Ember.$.param({path: this.get('currentPath')});
+  }),
+
+  startFetch: Ember.on('didInitAttrs', function() {
+    this.fetchData();
+  }),
+
+  fetchData: function() {
+    this.get('fileOperationService').listPath(this.get('currentQueryParam')).then(
+      (response) => {
+        this.modifyTreeViewData(response);
+      }, (error) => {
+        this.sendAction('errorAction', error);
+      }
+    )
+  },
+
+  modifyTreeViewData: function(response) {
+
+    let paths = response.map((entry) => {
+      return {
+        path: entry.path,
+        pathSegment: this.getNameForPath(entry.path),
+        text: this.getNameForPath(entry.path),
+        nodes: Ember.A()
+      };
+    });
+
+    var currentPath = this.get('currentPath');
+    var newTreeData = Ember.copy(this.get('treeData'), true);
+    if(currentPath === '/') {
+      newTreeData = paths;
+    } else {
+      this.insertPathToTreeData(newTreeData, paths, currentPath.substring(1));
+    }
+
+    this.set('treeData', newTreeData);
+    this.send('refreshTreeView');
+  },
+
+  insertPathToTreeData(treeData, paths, pathSegment) {
+    let firstPathSegment;
+    if (pathSegment.indexOf('/') !== -1) {
+      firstPathSegment = pathSegment.substring(0, pathSegment.indexOf('/'));
+    } else {
+      firstPathSegment = pathSegment;
+    }
+
+    if(treeData.length === 0) {
+      treeData.pushObjects(paths);
+    } else {
+      treeData.forEach((entry) => {
+        entry.state = {};
+        if (entry.pathSegment === firstPathSegment) {
+          entry.state.expanded = true;
+          if(entry.nodes.length === 0) {
+            entry.nodes.pushObjects(paths);
+          } else {
+            this.insertPathToTreeData(entry.nodes, paths, pathSegment.substring(pathSegment.indexOf('/') + 1));
+          }
+        } else {
+          this.collapseAll(entry);
+        }
+      });
+    }
+  },
+
+  collapseAll: function(node) {
+    if (Ember.isNone(node.state)) {
+      node.state = {};
+    }
+    node.state.expanded = false;
+    node.nodes.forEach((entry) => {
+      this.collapseAll(entry);
+    });
+
+  },
+
+  getNameForPath: function(path) {
+    return path.substring(path.lastIndexOf("/") + 1);
+  },
+
+  collapseAllExceptPath: function(pathSegment) {
+    let collapseAll = function(nodes, pathSegment) {
+      var firstPathSegment;
+      if (pathSegment.indexOf('/') !== -1) {
+        firstPathSegment = pathSegment.substring(0, pathSegment.indexOf('/'));
+      } else {
+        firstPathSegment = pathSegment;
+      }
+
+      nodes.forEach((entry) => {
+        if (Ember.isNone(entry.state)) {
+          entry.state = {};
+        }
+        if(firstPathSegment !== entry.pathSegment) {
+          entry.state.expanded = false;
+        } else {
+          entry.state.expanded = true;
+          collapseAll(entry.nodes, pathSegment.substring(pathSegment.indexOf('/') + 1));
+        }
+      });
+    };
+    var newTreeData = this.get('treeData');
+    collapseAll(newTreeData, pathSegment);
+    this.set('treeData', newTreeData);
+    this.send('refreshTreeView');
+  },
+
+  actions: {
+    refreshTreeView() {
+      Ember.run.later(() => {
+        this.$().treeview({
+          data: this.get('treeData'),
+          expandIcon: "fa fa-folder",
+          collapseIcon: "fa fa-folder-open",
+          emptyIcon: "fa fa-folder-open-o",
+          showBorder: false,
+          onNodeSelected: (event, data) => {
+            this.set('currentPath', data.path);
+            this.sendAction('pathSelectAction', data.path);
+          },
+          onNodeExpanded: (event, data) => {
+            this.set('currentPath', data.path);
+            if (!Ember.isNone(data.nodes) && data.nodes.length === 0) {
+              var node = this.$().treeview('getNode', data.nodeId);
+              node.icon = "fa fa-refresh fa-spin";
+              this.fetchData();
+            } else {
+              this.collapseAllExceptPath(data.path.substring(1));
+            }
+          }
+        });
+      });
+    }
+  }
+});

+ 37 - 0
contrib/views/files/src/main/resources/ui/app/components/file-row.js

@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  classNames: ['col-md-12', 'file-row'],
+  classNameBindings: ['isSelected:row-selected'],
+  isSelected: Ember.computed.alias('file.isSelected'),
+
+  click: function(event) {
+    if(event.shiftKey) {
+      this.sendAction("multiSelectAction", this.get('file'), true);
+    } else if (event.ctrlKey) {
+      this.sendAction("multiSelectAction", this.get('file'), false);
+    } else if (event.metaKey) {
+      this.sendAction("multiSelectAction", this.get('file'), false);
+    } else {
+      this.sendAction("singleSelectAction", this.get('file'));
+    }
+  }
+});

+ 17 - 17
contrib/views/files/src/main/resources/ui/app/components/contextMenu.js → contrib/views/files/src/main/resources/ui/app/components/file-search.js

@@ -16,27 +16,27 @@
  * limitations under the License.
  */
 
-var App = require('app');
+import Ember from 'ember';
 
-App.ContextMenuComponent = Em.Component.extend({
-  layoutName:'components/contextMenu',
+export default Ember.Component.extend({
+  classNames: ['input-group'],
+  classNameBindings: ['expanded::col-md-9', 'expanded::col-md-offset-3'],
+  expanded: false,
 
-  onTargetChange:function () {
-    this.$().off('hidden.bs.context');
-    this.$().on('hidden.bs.context', Em.run.bind(this, this.resetConfirmations));
-  }.observes('target'),
+  searchText: '',
 
-  resetConfirmations:function () {
-    this.triggerRecursively('resetConfirm');
+  throttleTyping: Ember.observer('searchText', function() {
+    Ember.run.debounce(this, this.searchFiles, 500);
+  }),
+
+  searchFiles: function() {
+    this.sendAction('searchAction', this.get('searchText'));
   },
 
-  actions:{
-    removeFile:function () {
-      this.get('target').send('deleteFile',true);
-    },
-    moveToTrash:function () {
-      this.get('target').send('deleteFile');
-    }
+  focusIn: function() {
+    this.set('expanded', true);
+  },
+  focusOut: function() {
+    this.set('expanded', false);
   }
-
 });

+ 69 - 0
contrib/views/files/src/main/resources/ui/app/components/files-breadcrumb.js

@@ -0,0 +1,69 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Component.extend({
+  path: '',
+  collapseAt: 4,
+  tagName: 'ul',
+  classNames: ['breadcrumb'],
+  collapsingRequired: false,
+  collapsedCrumbs: [],
+  expandedCrumbs: [],
+
+  crumbs: Ember.on('init', Ember.observer('path', function() {
+    var path = this.get('path');
+    var currentPath = path.split('/').filter((entry) => { return !Ember.isBlank(entry) });
+    currentPath.unshift("/");
+    var that = this;
+    var shouldCollapse = function(scope, array, index) {
+      return (((array.length - 1) >= scope.get('collapseAt')) && (index < array.length - 2));
+    };
+    var getCrumb = function(index, allCrumbs) {
+      return {name: allCrumbs[index], path: "/" + allCrumbs.slice(1, index + 1).join('/'), last: false};
+    };
+
+    var collapsedCrumbs = currentPath.map(function(curr, i, array) {
+      if(shouldCollapse(that, array, i)) {
+        return getCrumb(i, array);
+      } else {
+        return {};
+      }
+    }).filterBy('name');
+
+    var crumbs = currentPath.map(function(curr, i, array) {
+      if(!shouldCollapse(that, array, i)) {
+        return getCrumb(i, array);
+      } else {
+        return {};
+      }
+    }).filterBy('name');
+
+    crumbs.set('lastObject.last', true);
+
+    if (collapsedCrumbs.length > 0) {
+      this.set('collapsingRequired', true);
+    } else {
+      this.set('collapsingRequired', false);
+    }
+    this.set('collapsedCrumbs', collapsedCrumbs.reverse());
+    this.set('expandedCrumbs', crumbs);
+  }))
+
+});

+ 98 - 0
contrib/views/files/src/main/resources/ui/app/components/files-collection.js

@@ -0,0 +1,98 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import { EKMixin, keyUp } from 'ember-keyboard';
+
+export default Ember.Component.extend(EKMixin, {
+  minHeight: 600,
+  currentWidth: 1000,
+  currentHeight: 600,
+  columnsConfig: [],
+  sortOptions: [-1, 0, 1],
+  parentPath: '',
+  isEmptyParentPath: Ember.computed('parentPath', function() {
+    return Ember.isBlank(this.get('parentPath'));
+  }),
+
+  resizeView: Ember.on('init', function() {
+    $(window).resize(this.windowResized(this));
+  }),
+
+  destroyResizeView: Ember.on('willDestroyElement', function() {
+    $(window).off("resize");
+  }),
+
+  activateKeyboard: Ember.on('init', function() {
+    this.set('keyboardActivated', true);
+  }),
+
+  resetAllSelection: Ember.on(keyUp('Escape'), function() {
+    this.sendAction('resetSelection');
+  }),
+
+  selectAll: Ember.on(keyUp('shift+s'), function() {
+    this.sendAction('selectAllAction', false);
+  }),
+
+  containerStyle: Ember.computed('currentHeight', function() {
+    var height = this.get('currentHeight');
+    var style = 'position: relative; height: ' + height + 'px';
+    return style.htmlSafe();
+  }),
+
+  windowResized: function(scope) {
+    return function() {
+      Ember.run.later(function() {
+        var currentWidth = $("#" + scope.get('containerId')).width();
+        var windowHeight = $(window).height();
+        var relativeHeight = windowHeight - 220;
+        if(relativeHeight < scope.get('minHeight')) {
+          relativeHeight = scope.get('minHeight');
+        }
+        scope.set('currentWidth', currentWidth);
+        scope.set('currentHeight', relativeHeight);
+      });
+    };
+  },
+
+  didInsertElement: function() {
+    var func = this.windowResized(this);
+    func();
+  },
+
+  actions: {
+    rotateSort: function(column) {
+      if(!column['sortable'] || this.get('sortEnabled') !== true) {
+        return false;
+      }
+      var sortOptions = this.get('sortOptions');
+      // Resetting the current sort order
+      this.get('columnsConfig').forEach(function(entry) {
+        if(entry['key'] !== column['key']) {
+          Ember.set(entry, 'sortOrder', sortOptions[1]);
+        }
+      });
+      var currentSortOrder = column['sortOrder'];
+      var currentSortOrderIndex = sortOptions.indexOf(currentSortOrder);
+      var nextSortOrderIndex = (currentSortOrderIndex + 1) % sortOptions.length;
+      Ember.set(column, 'sortOrder', sortOptions[nextSortOrderIndex]);
+      this.sendAction('sortAction', column);
+    }
+  }
+});

+ 0 - 50
contrib/views/files/src/main/resources/ui/app/components/mkdirInput.js

@@ -1,50 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.MkdirInputComponent = Em.Component.extend({
-  layoutName:'components/mkdirInput',
-  newDirName:'',
-  isMkdir:false,
-  path:'',
-  actions:{
-    create:function () {
-      var name = this.get('newDirName');
-
-      if (Em.isEmpty(name)) {
-        return false;
-      }
-      newDir = [this.get('path'),name].join('/').replace('//','/');
-
-      this.sendAction('create',newDir);
-      this.setProperties({'newDirName':'','isMkdir':false});
-    },
-    edit:function () {
-      this.set('isMkdir',true);
-    },
-    cancel:function () {
-      this.setProperties({'newDirName':'','isMkdir':false});
-    }
-  },
-  focusOnInput: function () {
-    Em.run.next(this,function() {
-      this.$('.mkdir-input').focus();
-    });
-  }.observes('isMkdir'),
-});

+ 126 - 0
contrib/views/files/src/main/resources/ui/app/components/move-modal.js

@@ -0,0 +1,126 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import OperationModal from '../mixins/operation-modal';
+
+export default Ember.Component.extend(OperationModal, {
+  closeOnEscape: true,
+  fileSelectionService: Ember.inject.service('files-selection'),
+  fileOperationService: Ember.inject.service('file-operation'),
+  selectedFiles: Ember.computed.alias('fileSelectionService.files'),
+  selected: Ember.computed('selectedFiles', function () {
+    return this.get('selectedFiles').objectAt(0);
+  }),
+  selectionName: '/',
+  isUpdating: false,
+  browseError: false,
+  browseErrorMessege: '',
+  hasError: false,
+  shouldRetry: false,
+  currentFailedPath: '',
+  currentUnprocessedPaths: [],
+  currentFailureMessage: '',
+
+  movePaths: function (paths, destination) {
+    this.set('isUpdating', true);
+
+    this.get('fileOperationService').movePaths(paths, destination).then(
+      (response) => {
+        this.set('isUpdating', false);
+        this.send('close');
+        this.sendAction('refreshAction');
+      }, (error) => {
+        this.set('isUpdating', false);
+        if (error.unprocessable === true) {
+          this.set('hasError', true);
+          this.set('currentFailedPath', error.failed);
+          this.set('currentFailureMessage', error.message);
+          this.set('shouldRetry', error.retry);
+          this.set('currentUnprocessedPaths', error.unprocessed);
+        } else {
+          this.set('isUpdating', false);
+          this.get('logger').danger("Failed to delete files and folders.", error);
+          this.send('close');
+        }
+      });
+  },
+  reset: function () {
+    this.set('browseError', false);
+    this.set('browseErrorMessege', '');
+    this.set('selectionName', '/');
+    this.set('hasError', false);
+    this.set('shouldRetry', false);
+    this.set('isUpdating', false);
+    this.set('currentFailedPath', '');
+    this.set('currentFailureMessage', '');
+    this.set('currentUnprocessedPaths', '');
+  },
+  actions: {
+
+    didOpenModal: function () {
+      this.reset();
+      console.log("Move modal opened");
+    },
+
+    didCloseModal: function () {
+      console.log("Move Modal did close.");
+    },
+
+    move: function () {
+      var currentPathsToMove = this.get('selectedFiles').map((entry) => {
+        return entry.get('path')
+      });
+      var destinationPath = (this.get('selectionName') !== '') ? this.get('selectionName') : '/';
+      this.movePaths(currentPathsToMove, destinationPath);
+    },
+
+    retryError: function () {
+      var newPaths = [this.get('currentFailedPath')];
+      if (Ember.isArray(this.get('currentUnprocessedPaths'))) {
+        newPaths.pushObjects(this.get('currentUnprocessedPaths'));
+      }
+      var destinationPath = (this.get('selectionName') !== '') ? this.get('selectionName') : '/';
+      this.movePaths(newPaths, destinationPath);
+    },
+
+    skipAndRetry: function () {
+      var destinationPath = (this.get('selectionName') !== '') ? this.get('selectionName') : '/';
+      this.movePaths(this.get('currentUnprocessedPaths'), destinationPath);
+    },
+
+    skipAll: function () {
+      this.send('close');
+      this.sendAction('refreshAction');
+    },
+
+    pathSelected: function (path) {
+      console.log(path);
+      this.set('selectionName', path);
+      this.set('browseError', false);
+
+    },
+
+    browseError: function (error) {
+      this.set('browseError', true);
+      this.set('browseErrorMessage', error.message);
+    }
+  }
+
+
+});

+ 75 - 0
contrib/views/files/src/main/resources/ui/app/components/new-directory.js

@@ -0,0 +1,75 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import OperationModal from '../mixins/operation-modal';
+
+export default Ember.Component.extend(OperationModal, {
+  modalEventBus: Ember.inject.service('modal-event-bus'),
+  fileOperationService: Ember.inject.service('file-operation'),
+  closeOnEscape: true,
+  tagName: 'span',
+  name: 'ctx-new-directory',
+  hasError: false,
+  errorMessage: '',
+  folderName: '',
+  didInitAttrs: function() {
+    this.get('modalEventBus').registerModal("ctx-new-directory");
+  },
+  willDestroyElement() {
+    this.get('modalEventBus').resetModal("ctx-new-directory");
+  },
+  resetError: Ember.observer('folderName', function() {
+    this.set('hasError', false);
+    this.set('errorMessage', '');
+  }),
+  setError: function(message) {
+    this.set('hasError', true);
+    this.set('errorMessage', message);
+  },
+  actions: {
+    didOpenModal: function() {
+      this.set('folderName');
+      Ember.run.later(() => {
+        this.$('input').focus();
+      }, 500);
+    },
+    create: function() {
+      if(Ember.isBlank(this.get('folderName'))) {
+        this.setError('Cannot be empty');
+        return false;
+      }
+
+      if(this.get('fileOperationService').isExistsInCurrentPath(this.get('folderName'))) {
+        this.setError('Name already exists');
+        return false;
+      }
+
+      this.get('fileOperationService').createNewFolder(this.get('path'), this.get('folderName')).then(
+        (response) => {
+          this.send('close');
+          this.sendAction('refreshAction');
+        }, (error) => {
+          this.send('close');
+        });
+    },
+    openModal : function() {
+      this.get('modalEventBus').showModal('ctx-new-directory');
+    }
+  }
+});

+ 56 - 0
contrib/views/files/src/main/resources/ui/app/components/open-preview-modal.js

@@ -0,0 +1,56 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import OperationModal from '../mixins/operation-modal';
+
+export default Ember.Component.extend(OperationModal, {
+  closeOnEscape: true,
+  filePreviewService: Ember.inject.service('file-preview'),
+  selectedFilePath: '',
+  modalGuardChanged: Ember.observer('modalGuard', function () {
+    if (this.get('modalGuard')) {
+      console.log("Modal Guard set");
+    } else {
+      console.log("Modal Guard not set");
+    }
+  }),
+
+  actions: {
+    // Actions to preview modal HTML.
+    didOpenModal: function () {
+      this.set('selectedFilePath', this.get('filePreviewService.selectedFilePath'));
+      this.get('filePreviewService').getNextContent();
+      var _self = this;
+      this.$('.preview-content').on('scroll', function () {
+        if (Ember.$(this).scrollTop() + Ember.$(this).innerHeight() >= this.scrollHeight) {
+          _self.get('filePreviewService').getNextContent();
+        }
+      });
+    },
+    didCloseModal: function () {
+      this.$('.preview-content').off('scroll');
+      this.get('filePreviewService').reset();
+    },
+    download: function () {
+      this.get('filePreviewService').download();
+    }
+  }
+
+});
+

+ 116 - 0
contrib/views/files/src/main/resources/ui/app/components/permission-modal.js

@@ -0,0 +1,116 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import OperationModal from '../mixins/operation-modal';
+
+export default Ember.Component.extend(OperationModal, {
+  fileSelectionService: Ember.inject.service('files-selection'),
+  fileOperationService: Ember.inject.service('file-operation'),
+  closeOnEscape: true,
+  isUpdating: false,
+  selected: Ember.computed('fileSelectionService.files', function() {
+    return this.get('fileSelectionService.files').objectAt(0);
+  }),
+  permission: Ember.computed('selected.permission', function() {
+    return this.get('selected.permission');
+  }),
+  setPermissionGuards: function() {
+    var permission = this.get('permission');
+    this.set('usrR', this.isSet(permission, 'user', "read"));
+    this.set('usrW', this.isSet(permission, 'user', "write"));
+    this.set('usrE', this.isSet(permission, 'user', "execute"));
+
+    this.set('grpR', this.isSet(permission, 'group', "read"));
+    this.set('grpW', this.isSet(permission, 'group', "write"));
+    this.set('grpE', this.isSet(permission, 'group', "execute"));
+
+    this.set('othR', this.isSet(permission, 'other', "read"));
+    this.set('othW', this.isSet(permission, 'other', "write"));
+    this.set('othE', this.isSet(permission, 'other', "execute"));
+  },
+
+  isSet: function(permission, userType, permissionType) {
+    var checkValueAtLocation = function(index, value) {
+      return permission[index] === value;
+    };
+
+    var checkValueForPermissionType = function(startIndex, permissionType) {
+      switch(permissionType) {
+        case 'read':
+          return checkValueAtLocation(startIndex, 'r');
+        case 'write':
+          return checkValueAtLocation(startIndex + 1, 'w');
+        case 'execute':
+          return checkValueAtLocation(startIndex + 2, 'x');
+      }
+    };
+    switch(userType) {
+      case "user":
+        return checkValueForPermissionType(1, permissionType);
+      case "group":
+        return checkValueForPermissionType(4, permissionType);
+      case "other":
+        return checkValueForPermissionType(7, permissionType);
+    }
+  },
+
+  getPermissionFromGuards: function() {
+    var oldPermission = this.get('permission');
+    var replaceAt = function(index, value) {
+      return oldPermission.substring(0, index) + value + oldPermission.substring(index + value.length);
+    };
+    oldPermission = this.get('usrR') ? replaceAt(1, 'r') : replaceAt(1, '-');
+    oldPermission = this.get('usrW') ? replaceAt(2, 'w') : replaceAt(2, '-');
+    oldPermission = this.get('usrE') ? replaceAt(3, 'x') : replaceAt(3, '-');
+    oldPermission = this.get('grpR') ? replaceAt(4, 'r') : replaceAt(4, '-');
+    oldPermission = this.get('grpW') ? replaceAt(5, 'w') : replaceAt(5, '-');
+    oldPermission = this.get('grpE') ? replaceAt(6, 'x') : replaceAt(6, '-');
+    oldPermission = this.get('othR') ? replaceAt(7, 'r') : replaceAt(7, '-');
+    oldPermission = this.get('othW') ? replaceAt(8, 'w') : replaceAt(8, '-');
+    oldPermission = this.get('othE') ? replaceAt(9, 'x') : replaceAt(9, '-');
+    return oldPermission;
+  },
+  actions: {
+    didOpenModal: function() {
+      this.setPermissionGuards();
+    },
+
+    chmod: function() {
+      var newPermission = this.getPermissionFromGuards();
+      if(newPermission === this.get('permission')) {
+        return false;
+      }
+      this.set('isUpdating', true);
+      this.get('fileOperationService').chmod(this.get('selected').get('path'), newPermission).then((response) => {
+        this.get('selected').set('permission', response.permission);
+        this.set('isUpdating', false);
+        this.send('close');
+      }, (error) => {
+        this.set('isUpdating', false);
+        this.send('close');
+      });
+    },
+
+    togglePermission: function(propertyName) {
+      Ember.run.later(() => {
+        this.set(propertyName, !this.get(propertyName));
+      });
+    }
+  }
+});

+ 0 - 68
contrib/views/files/src/main/resources/ui/app/components/popoverDelete.js

@@ -1,68 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-Em.BsPopoverComponent.reopen({
-  willClearRender:function () {
-    var triggers = this.triggers.split(' ');
-
-      for (var i = triggers.length; i--;) {
-          var trigger = triggers[i];
-
-          if (trigger == 'click') {
-              this.$element.off('click');
-          } else if (trigger != 'manual') {
-              var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focus';
-              var eventOut = trigger == 'hover' ? 'mouseleave' : 'blur';
-
-              this.$element.off(eventIn);
-              this.$element.off(eventOut);
-          }
-      }
-  }
-});
-
-App.PopoverDeleteComponent = Em.Component.extend({
-  popover:Em.computed.alias('childViews.firstObject'),
-  layoutName:'components/deletePopover',
-  deleteForever:false,
-  actions:{
-    confirm:function (deleteForever) {
-      this.sendAction('confirm',this.get('deleteForever'));
-    },
-    close:function () {
-      this.set('popover.isVisible',false);
-    }
-  },
-  didInsertElement:function () {
-    $('body').on('click.popover', Em.run.bind(this,this.hideMultiply));
-  },
-  hideMultiply:function (e) {
-    if (!this.$()) {
-      return;
-    }
-    if (!this.$().is(e.target) && this.$().has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
-          this.set('popover.isVisible',false);
-    }
-  },
-  willClearRender:function () {
-    this.get('popover').$element.off('click');
-    $('body').off('click.popover');
-  }
-});

+ 77 - 0
contrib/views/files/src/main/resources/ui/app/components/rename-modal.js

@@ -0,0 +1,77 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import OperationModal from '../mixins/operation-modal';
+
+export default Ember.Component.extend(OperationModal, {
+  closeOnEscape: true,
+  hasError: false,
+  errorMessage: '',
+  isUpdating: false,
+  renameService: Ember.inject.service('file-rename'),
+  fileSelectionService: Ember.inject.service('files-selection'),
+  selectedFiles: Ember.computed.alias('fileSelectionService.files'),
+  selected: Ember.computed('selectedFiles', function() {
+    return this.get('selectedFiles').objectAt(0);
+  }),
+  selectionName: Ember.computed.oneWay('selected.name'),
+  hasErrorReset: Ember.observer('selectionName', 'selected.name', function() {
+    if (this.get('hasError') && (this.get('selectionName') !== this.get('selected.name'))) {
+      this.set('hasError', false);
+    }
+  }),
+
+  actions: {
+    didOpenModal: function() {
+      this.set('selectionName', this.get('selected.name'));
+      // This was required as the DOM may not be visible due to animation in bootstrap modal
+      Ember.run.later(() => {
+        this.$('input').focus();
+      }, 500);
+
+    },
+
+    rename: function() {
+      if(Ember.isBlank(this.get('selectionName'))) {
+        return false;
+      }
+
+      if(this.get('selected.name') === this.get('selectionName')) {
+        this.set('hasError', true);
+        this.set('errorMessage', 'Name should be different');
+        return false;
+      }
+      this.set('isUpdating', true);
+      this.get('renameService').rename(this.get('selected.path'), this.get('selectionName'))
+      .then((response) => {
+        this.set('isUpdating', false);
+        this.send('close');
+        this.sendAction('refreshAction');
+      }, (error) => {
+        this.set('isUpdating', false);
+        if(error.retry) {
+          this.set('hasError', true);
+          this.set('errorMessage', error.message);
+        } else {
+          this.send('close');
+        }
+      });
+    }
+  }
+});

+ 0 - 92
contrib/views/files/src/main/resources/ui/app/components/renameInput.js

@@ -1,92 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-var App = require('app');
-
-App.RenameInputComponent = Ember.Component.extend({
-  tagName:'span',
-  layoutName:'components/renameInput',
-  actions:{
-    rename:function (opt) {
-      var tmpName;
-
-      switch (opt) {
-        case 'edit': this.set('isRenaming',true); break;
-        case 'cancel': this.set('isRenaming',false); break;
-        case 'confirm':
-          tmpName = this.get('tmpName');
-          if (tmpName.length ===0) {
-            break;
-          }
-          this.sendAction('confirm',this.get('filePath'),tmpName);
-          this.set('isRenaming',false);
-          break;
-
-        default: this.toggleProperty('isRenaming');
-      }
-    }
-  },
-
-  /**
-   * passed params
-   */
-  file:null,
-  actionName:null,
-  isRenaming:false,
-
-  fileName:function () {
-    var file = this.get('file');
-    return (file instanceof DS.Model)?file.get('name'):file.substr(file.lastIndexOf('/')+1);
-  }.property('file'),
-
-  filePath:function () {
-    var file = this.get('file');
-    return (file instanceof DS.Model)?file.get('path'):file;
-  }.property('file'),
-
-  setTmpName:function () {
-    if (this.get('isRenaming')) {
-      this.set('tmpName',this.get('fileName'));
-    } else {
-      this.set('tmpName','');
-    }
-  }.observes('isRenaming'),
-
-  onFileChange:function () {
-    this.set('isRenaming',false);
-  }.observes('file'),
-
-  renameInputView: Em.TextField.extend({
-    controller:null,
-    didInsertElement:function () {
-      var element = $(this.get('element'));
-      element.focus().val(this.value);
-    },
-    keyUp: function(e) {
-      var target = this.get('targetObject');
-      if (e.keyCode==13) {
-        return target.send('rename', 'confirm');
-      }
-
-      if (e.keyCode==27) {
-        return target.send('rename', 'cancel');
-      }
-    }
-  })
-});

+ 0 - 34
contrib/views/files/src/main/resources/ui/app/components/sortArrow.js

@@ -1,34 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.SortArrowComponent = Em.Component.extend({
-  layout:Ember.Handlebars.compile('<i {{bind-attr class=":fa asc:fa-chevron-down:fa-chevron-up cur::fa-gr view.cur::fa-rotate-270" }} ></i>'),
-  classNames:['pull-right'],
-  tagName:'span',
-  sPs:[],
-  sA:false,
-  sP:null,
-  asc:true,
-  cur:false,
-  sorting:function () {
-    var isSp = this.get('sPs.firstObject') == this.get('sP');
-    this.setProperties({'asc':(isSp)?this.get('sA'):true,'cur':isSp});
-  }.observes('sPs','sA').on('didInsertElement')
-});

+ 0 - 79
contrib/views/files/src/main/resources/ui/app/components/toggleContext.js

@@ -1,79 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-function _shake (element) {
-  var l = 5;
-  for ( var i = 0; i < 4; i++ ) {
-    element.animate( (l>0) ? {'margin-left':(l=-l)+'px','padding-left':0}:{'padding-left':+(l=-l)+'px','margin-left':0}, 50, function (el) {
-      element.css({'padding-left':0,'margin-left':0});
-    });
-  }
-}
-
-App.ToggleContextComponent = Em.Component.extend({
-  didInsertElement:function () {
-    var fileRow = this.$().parents('tr'),
-        beforeHandler = Ember.run.bind(this, this.setContext),
-        itemHandler = Ember.run.bind(this, this.itemHandler);
-
-    fileRow.on('click',Ember.run.bind(this, this.openOnClick));
-
-    fileRow.contextmenu({
-      target:'#context-menu',
-      before:beforeHandler,
-      onItem:itemHandler
-    });
-  },
-  setContext:function(e) {
-    if (this.get('targetObject.isMoving')) {
-      return false;
-    }
-    this.set('targetObject.parentController.targetContextMenu',this.get('targetObject'));
-    return true;
-  },
-  itemHandler:function (t,e) {
-    if (e.target.dataset.disabled) {
-      return false;
-    }
-  },
-  openOnClick:function (e) {
-    if($(e.target).is('td') || $(e.target).hasClass('allow-open')){
-      this.get('targetObject').send('open');
-    }
-  },
-  willClearRender:function () {
-    var fileRow = this.$().parents('tr');
-    fileRow.off('click');
-    fileRow.data('context').closemenu();
-    fileRow.data('context').destroy();
-  }
-});
-
-App.FileShakerComponent = Em.Component.extend({
-  action:'',
-  isValid:false,
-  click:function () {
-    if (this.get('isValid')) {
-      this.sendAction('action');
-    } else {
-      _shake(this.$());
-    }
-  }
-});

+ 92 - 0
contrib/views/files/src/main/resources/ui/app/components/upload-file.js

@@ -0,0 +1,92 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import OperationModal from '../mixins/operation-modal';
+import FileUploader from '../utils/file-uploader';
+
+export default Ember.Component.extend(OperationModal, {
+  modalEventBus: Ember.inject.service('modal-event-bus'),
+  fileOperationService: Ember.inject.service('file-operation'),
+  logger: Ember.inject.service('alert-messages'),
+  tagName: "span",
+  closeOnEscape: true,
+  name: 'ctx-uploader',
+  path: '',
+  isUploading: false,
+  uploadFileName: '',
+  uploadPercent: '0%',
+  uploadPercentStyle: Ember.computed('uploadPercent', function() {
+    var style = 'width: ' + this.get('uploadPercent') + ';';
+    return style.htmlSafe();
+  }),
+  didInitAttrs: function() {
+    this.get('modalEventBus').registerModal("ctx-uploader");
+  },
+  willDestroyElement() {
+    this.get('modalEventBus').resetModal("ctx-uploader");
+  },
+  setUploadPercent: function(percent) {
+    var intValue = Math.round(percent);
+    this.set('uploadPercent', `${intValue}%`);
+  },
+
+  setUploading: function(fileName) {
+    this.set('uploadFileName', fileName);
+    this.set('isUploading', true);
+    this.set('closeOnEscape', false);
+  },
+
+  actions: {
+    openModal : function() {
+      this.get('modalEventBus').showModal('ctx-uploader');
+    },
+    didOpenModal: function() {
+      this.set('isUploading', false);
+      this.set('uploadFileName', '');
+      this.set('closeOnEscape', true);
+    },
+
+    fileLoaded: function(file) {
+      var url = this.get('fileOperationService').getUploadUrl();
+      var uploader = FileUploader.create({
+        url: url
+      });
+      if(!Ember.isEmpty(file)) {
+        uploader.upload(file, {path: this.get('path')});
+        this.setUploading(file.name);
+        uploader.on('progress', (e) => {
+          this.setUploadPercent(e.percent);
+        });
+        uploader.on('didUpload', (e) => {
+          this.send('close');
+          this.sendAction('refreshAction');
+        });
+        uploader.on('didError', (jqXHR, textStatus, errorThrown) => {
+          var error = Ember.$.parseJSON(jqXHR.responseText);
+          this.get('logger').danger(`Failed to upload ${file.name} to ${this.get('path')}`, error);
+          this.send('close');
+          return false;
+        });
+      }
+
+    }
+
+  }
+});
+

+ 0 - 111
contrib/views/files/src/main/resources/ui/app/components/uploader.js

@@ -1,111 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-
-var App = require('app');
-
-App.FileUploaderComponent = Ember.Component.extend({
-  didInsertElement:function () {
-    var _this = this;
-    this.uploader.reopen({
-      sendAlert:function (e) {
-        _this.sendAction('alert',e);
-      }
-    });
-    this.fileInput.reopen({
-      filesDidChange: function() {
-        var files = this.get('files');
-        if (!files) {
-          this.set('parentView.files',null);
-          this.set('parentView.controlInput.value','');
-          this.set('value','');
-          return;
-        }
-        var numFiles = files ? files.length : 1;
-        var label = this.get('value').replace(/\\/g, '/').replace(/.*\//, '');
-        var log = numFiles > 1 ? numFiles + ' files selected' : label;
-
-        this.set('parentView.controlInput.value',log);
-        this.set('parentView.files',files);
-
-      }.observes('files')
-    });
-  },
-  actions:{
-    upload:function () {
-      this.uploadFile();
-    },
-    clear:function () {
-      this.set('fileInput.files',null);
-    }
-  },
-  uploader: null,
-  layoutName:'components/uploader',
-  path:'',
-  info:'',
-  files:null,
-  isFiles:function () {
-    return !this.get('files.length');
-  }.property('files'),
-  uploadFile:function () {
-    var path = this.get('path');
-    var uploader = this.get('uploader');
-    var uploadBtn = Ladda.create(this.uploadButton.get('element'));
-    var reset = function () {
-      uploadBtn.stop();
-      this.send('clear');
-    };
-    if (!uploader.get('isUploading')) {
-      if (!Ember.isEmpty(this.get('files'))) {
-        var file = this.get('files')[0];
-        uploadBtn.start();
-        uploader.on('progress',function (e) {
-          uploadBtn.setProgress(e.percent/100);
-        });
-        uploader.upload(file,{path:path}).finally(Em.run.bind(this,reset));
-      }
-    }
-  },
-  uploadButton: Em.View.createWithMixins(Ember.TargetActionSupport, {
-    tagName:'button',
-    target: Ember.computed.alias('controller'),
-    classNames:['btn','ladda-button'],
-    classNameBindings:['isFiles:hide','target.isError:btn-danger:btn-success'],
-    attributeBindings: ["data-style","data-size"],
-    action:'upload',
-    click: function() {
-      this.triggerAction();
-    }
-  }),
-  fileInput : Ember.TextField.create({
-    type: 'file',
-    attributeBindings: ['multiple'],
-    multiple: false,
-    files:null,
-    change: function(e) {
-      var input = e.target;
-      if (!Ember.isEmpty(input.files)) {
-        this.set('files', input.files);
-      }
-    }
-  }),
-  controlInput:Ember.TextField.create({
-    readonly:true,
-    classNames:['form-control']
-  })
-});

+ 73 - 0
contrib/views/files/src/main/resources/ui/app/config/files-columns.js

@@ -0,0 +1,73 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+  Static configuration for the columns to be shown.
+*/
+var columnsConfig = [
+  {
+    title: 'Name',
+    key: 'name',
+    isVisible: true,
+    sortable: true,
+    sortOrder: 0,
+    columnClass: 'col-md-4 col-xs-4'
+  },
+  {
+    title: 'Size',
+    key: 'size',
+    isVisible: true,
+    sortable: true,
+    sortOrder: 0,
+    columnClass: 'col-md-1 col-xs-1'
+  },
+  {
+    title: 'Last Modified',
+    key: 'date',
+    isVisible: true,
+    sortable: true,
+    sortOrder: 0,
+    columnClass: 'col-md-2 col-xs-2'
+  },
+  {
+    title: 'Owner',
+    key: 'owner',
+    isVisible: true,
+    sortable: true,
+    sortOrder: 0,
+    columnClass: 'col-md-2 col-xs-2'
+  },
+  {
+    title: 'Group',
+    key: 'group',
+    isVisible: true,
+    sortable: true,
+    sortOrder: 0,
+    columnClass: 'col-md-1 col-xs-1'
+  },
+  {
+    title: 'Permission',
+    key: 'permission',
+    isVisible: true,
+    sortable: false,
+    sortOrder: 0,
+    columnClass: 'col-md-2 col-xs-2'
+  }
+];
+
+export default columnsConfig;

+ 0 - 0
contrib/views/files/src/main/resources/ui/app/controllers/.gitkeep


+ 4 - 3
contrib/views/files/src/main/resources/ui/app/views/filesAlert.js → contrib/views/files/src/main/resources/ui/app/controllers/application.js

@@ -16,8 +16,9 @@
  * limitations under the License.
  */
 
-var App = require('app');
+import Ember from 'ember';
 
-App.FilesAlertView = Em.View.extend({
-  templateName:'util/errorRow'
+export default Ember.Controller.extend({
+  firstLoad: true,
+  isLoading: false
 });

+ 0 - 49
contrib/views/files/src/main/resources/ui/app/controllers/chmodModal.js

@@ -1,49 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-var _permissionsProp = function(n, l) {
-  return function (arg,val) {
-    if (arguments.length > 1) {
-      this.set('permissions', this.replaceAt(n,(val)?l:'-'));
-      return val;
-    }
-    return this.get('permissions')[n]===l;
-  };
-};
-
-App.ChmodModalController = Em.ObjectController.extend({
-  needs:['files'],
-  classNames:'chmod-row',
-  file:Em.computed.alias('content'),
-  permissions:Em.computed.alias('file.permission'),
-  usrR:_permissionsProp(1, 'r').property('permissions'),
-  usrW:_permissionsProp(2, 'w').property('permissions'),
-  usrE:_permissionsProp(3, 'x').property('permissions'),
-  grpR:_permissionsProp(4, 'r').property('permissions'),
-  grpW:_permissionsProp(5, 'w').property('permissions'),
-  grpE:_permissionsProp(6, 'x').property('permissions'),
-  otrR:_permissionsProp(7, 'r').property('permissions'),
-  otrW:_permissionsProp(8, 'w').property('permissions'),
-  otrE:_permissionsProp(9, 'x').property('permissions'),
-  replaceAt:function (index,p) {
-    var perm = this.get('permissions');
-    return perm.substr(0, index) + p + perm.substr(index + p.length);
-  }
-});

+ 0 - 49
contrib/views/files/src/main/resources/ui/app/controllers/error.js

@@ -1,49 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-App.ErrorController = Ember.ObjectController.extend({
-  actions: {
-    toggleStackTrace:function () {
-      var value = this.get('isExpanded');
-      this.set('isExpanded', !value);
-    }
-  },
-
-  isExpanded: false,
-
-  publicMessage:function () {
-    var content = this.get('content');
-    var text = content.statusText;
-    if (content && content.responseText) {
-      var json = JSON.parse(content.responseText);
-      text = json.message;
-    } else if (content && content.message) {
-      text = content.message;
-    }
-    return text;
-  }.property('content'),
-  stackTrace:function () {
-    var content = this.get('content');
-    var trace = null;
-    if (content && content.responseText) {
-      var json = JSON.parse(content.responseText);
-      trace = json.trace;
-    }
-    return trace;
-  }.property('content')
-});

+ 0 - 117
contrib/views/files/src/main/resources/ui/app/controllers/file.js

@@ -1,117 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.FileController = Ember.ObjectController.extend({
-  needs:['files'],
-  actions:{
-    confirmPreview:function (file) {
-      this.downloadFile(file, "browse");
-    },
-    download:function (option) {
-      this.downloadFile(this.get('content'), option);
-    },
-    preview:function (option) {
-      this.send('showPreviewModal',this.get('content'));
-    },
-    showChmod:function () {
-      this.send('showChmodModal',this.get('content'));
-    },
-    rename:function (opt,name) {
-      var file = this.get('content'),
-          path = file.get('path'),
-          newPath;
-
-      if (name === file.get('name') || Em.isEmpty(name)) {
-        return this.set('isRenaming',!Em.isEmpty(name));
-      }
-
-      newPath = path.substring(0,path.lastIndexOf('/')+1)+name;
-
-      this.store.move(file,newPath)
-        .then(Em.run.bind(this,this.set,'isRenaming',false),Em.run.bind(this,this.sendAlert));
-    },
-    editName:function () {
-      this.set('isRenaming',true);
-    },
-    open:function (file) {
-      if (this.get('content.isDirectory')) {
-        return this.transitionToRoute('files',{queryParams: {path: this.get('content.id')}});
-      } else{
-        //return this.send('download');
-        return this.send('preview');
-      }
-    },
-    deleteFile:function (deleteForever) {
-      this.store
-        .remove(this.get('content'),!deleteForever)
-        .then(null,Em.run.bind(this,this.deleteErrorCallback,this.get('content')));
-    }
-  },
-  selected:false,
-  isRenaming:false,
-  isMovingToTrash:false,
-  chmodVisible:false,
-  targetContextMenu:null,
-  isPermissionsDirty:function () {
-    var file = this.get('content');
-    var diff = file.changedAttributes();
-    return !!diff.permission;
-  }.property('content.permission'),
-  isMoving:function () {
-    var movingFile = this.get('parentController.movingFile.path');
-    var thisFile = this.get('content.id');
-    return movingFile === thisFile;
-  }.property('parentController.movingFile'),
-
-  setSelected:function (controller,observer) {
-    this.set('selected',this.get(observer));
-  }.observes('content.selected'),
-
-  renameSuccessCallback:function (record,error) {
-    record.rollback();
-    this.sendAlert(error);
-  },
-
-  dirInfo: Em.computed.alias('controllers.files.content.meta'),
-
-  deleteErrorCallback:function (record,error) {
-    this.get('parentController.model').pushRecord(record);
-    this.send('showAlert',error);
-  },
-
-  sendAlert:function (error) {
-    this.send('showAlert',error);
-  },
-  downloadFile: function(files, option) {
-    var _this = this;
-    this.store.linkFor([files], option, false, true).then(function(link) {
-      var that = _this;
-      Ember.$.get(link).done(function(data) {
-        if(data.allowed) {
-          that.store.linkFor([files],option).then(function (link) {
-            window.location.href = link;
-          },Em.run.bind(that,that.sendAlert));
-        }
-      }).fail(function(jqXHR, textStatus, errorThrown) {
-        that.send('showAlert', jqXHR);
-      });
-    }, Em.run.bind(this,this.sendAlert));
-  }
-});

+ 126 - 173
contrib/views/files/src/main/resources/ui/app/controllers/files.js

@@ -16,201 +16,154 @@
  * limitations under the License.
  */
 
-var App = require('app');
-var bind = Ember.run.bind;
-
-App.FilesController = Ember.ArrayController.extend({
-  actions:{
-    moveFile:function (opt,fileArg) {
-      var src, title,
-          file = fileArg || this.get('selectedFiles.firstObject'),
-          moving = this.get('movingFile');
-
-      if (opt == 'cut') {
-        src = file.toJSON({includeId: true});
-        src = Em.merge(src,{name:file.get('name'),path:file.get('path')});
-        this.set('movingFile',src);
-      }
-
-      if (opt == 'move') {
-        this.store.move(moving.path,[this.get('path'),moving.name].join('/').replace('//','/'))
-          .then(bind(this,this.set,'movingFile',null),bind(this,this.throwAlert));
-      }
+import Ember from 'ember';
+import columnConfig from '../config/files-columns';
+
+export default Ember.Controller.extend({
+  fileSelectionService: Ember.inject.service('files-selection'),
+  lastSelectedFile: Ember.computed.oneWay('fileSelectionService.lastFileSelected'),
+  selectedFilesCount: Ember.computed.oneWay('fileSelectionService.filesCount'),
+  selectedFolderCount: Ember.computed.oneWay('fileSelectionService.folderCount'),
+  isSelected: Ember.computed('selectedFilesCount', 'selectedFolderCount', function() {
+    return (this.get('selectedFilesCount') + this.get('selectedFolderCount')) !== 0;
+  }),
 
-      if (opt == 'cancel') {
-        this.set('movingFile',null);
-      }
-    },
-    showRenameInput:function () {
-      this.toggleProperty('isRenaming');
-    },
-    renameDir:function (path,newName) {
-      var _this = this,
-          basedir = path.substring(0,path.lastIndexOf('/')+1);
-          newPath = basedir + newName;
-
-      if (path === newPath) {
-        return false;
-      }
-
-      this.store.listdir(basedir).then(function (listdir) {
-        var recordExists = listdir.isAny('id',newPath);
-
-        listdir.forEach(function (file) {
-          _this.store.unloadRecord(file);
-        });
+  queryParams: ['path'],
+  path: '/',
+  columns: columnConfig,
+
+  hasHomePath: false,
+  hasTrashPath: false,
+
+  currentPathIsTrash: Ember.computed('path', 'trashPath', 'hasTrashPath', function() {
+    return this.get('hasTrashPath') && (this.get('path') === this.get('trashPath'));
+  }),
+
+  // This is required as the validSearchText will be debounced and will not be
+  // called at each change of searchText. searchText is required so that sub
+  // components(file search componenet) UI can be cleared from outside.(i.e, from
+  // the afterModel of the route when the route changes)
+  searchText: '',
+  validSearchText: '',
+
+  sortProperty: [],
+  sortEnabled: Ember.computed('fileSelectionService.files.length', function() {
+    return this.get('fileSelectionService.files.length') === 0;
+  }),
+
+  allSelected: Ember.computed('fileSelectionService.files.length', function() {
+    return this.get('fileSelectionService.files.length') !== 0 && this.get('fileSelectionService.files.length') === this.get('model.length');
+  }),
+
+  parentPath: Ember.computed('path', function() {
+    var path = this.get('path');
+    var parentPath = path.substring(0, path.lastIndexOf('/'));
+    if(Ember.isBlank(parentPath)) {
+      parentPath = '/';
+    }
 
-        if (recordExists) {
-          return _this.throwAlert({message:newPath + ' already exists.'});
-        }
+    if(path === '/') {
+      parentPath = '';
+    }
+    return parentPath;
+  }),
 
-        return _this.store.move(path,newPath);
-      }).then(function (newDir) {
-        if (newDir) {
-          _this.store.unloadRecord(newDir);
-          _this.set('path',newPath);
-        }
-      }).catch(bind(this,this.throwAlert));
+  sortedContent: Ember.computed.sort('model', 'sortProperty'),
 
-    },
-    deleteFile:function (deleteForever) {
-      var self = this,
-          selected = this.get('selectedFiles'),
-          moveToTrash = !deleteForever;
-      selected.forEach(function (file) {
-        self.store.remove(file,moveToTrash).then(null,bind(self,self.deleteErrorCallback,file));
+  arrangedContent: Ember.computed('model', 'sortProperty', 'validSearchText', function() {
+    var searchText = this.get('validSearchText');
+    if(!Ember.isBlank(searchText)) {
+      return this.get('sortedContent').filter(function(entry) {
+        return !!entry.get('name').match(searchText);
       });
+    }
+    return this.get('sortedContent');
+  }),
+
+  actions: {
+    sortFiles: function(sortColumn) {
+      if (sortColumn['sortOrder'] !== 0) {
+        var sortProperty = sortColumn['key'] + ':' + this._getSortOrderString(sortColumn);
+        this.set('sortProperty', [sortProperty]);
+      } else {
+        this.set('sortProperty', []);
+      }
     },
-    download:function (option) {
-      var files = this.get('selectedFiles').filterBy('readAccess',true);
-      var content = this.get('content');
-      this.store.linkFor(content, option).then(function (link) {
-        window.location.href = link;
-      });
+
+    searchFiles: function(searchText) {
+      this.set('validSearchText', searchText);
     },
 
-    mkdir:function (newDirName) {
-      this.store.mkdir(newDirName)
-        .then(bind(this,this.mkdirSuccessCalback),bind(this,this.throwAlert));
+    /* Selects a single file. Clears previous selection */
+    selectSingle: function(file) {
+      this.get('fileSelectionService').deselectAll();
+      this.get('fileSelectionService').selectFiles([file]);
     },
-    upload:function (opt) {
-      if (opt === 'open') {
-        this.set('isUploading',true);
-      }
 
-      if (opt === 'close') {
-        this.set('isUploading',false);
+    /*
+      Selects file without clearing the previous selection. If sticky is true
+      then shiftkey was pressed while clicking and we should select all the
+      files in between
+    */
+    selectMultiple: function(file, sticky) {
+      if(!sticky) {
+        if(file.get('isSelected')) {
+          this.get('fileSelectionService').deselectFile(file);
+        } else {
+          this.get('fileSelectionService').selectFiles([file]);
+        }
+      } else {
+        var lastFileSelected = this.get('fileSelectionService.lastFileSelected');
+        var indexRange = this._getIndexRangeBetweenfiles(lastFileSelected, file);
+        if(indexRange[0] === indexRange[1]) {
+          return false;
+        }
+        var filesInRange = this._getFilesInRange(indexRange[0], indexRange[1]);
+        this.get('fileSelectionService').deselectAll();
+        this.get('fileSelectionService').selectFiles(filesInRange);
       }
     },
-    sort:function (pr) {
-      var currentProperty = this.get('sortProperties');
-      if (pr == currentProperty[0] || pr == 'toggle') {
-        this.toggleProperty('sortAscending');
-      } else{
-        this.set('sortProperties',[pr]);
-        this.set('sortAscending',true);
+
+    selectAll: function(selectStatus) {
+      this.get('fileSelectionService').deselectAll();
+      if(selectStatus === false) {
+        this.get('fileSelectionService').selectFiles(this.get('sortedContent'));
       }
     },
-    confirmChmod:function (file) {
-      this.store
-        .chmod(file)
-        .then(null,Em.run.bind(this,this.chmodErrorCallback,file));
-    },
-    confirmPreview:function (file) {
-      //this.send('download');
-      this.store.linkFor(file, "browse").then(function (link) {
-        window.location.href = link;
-      });
-    },
-    clearSearchField:function () {
-      this.set('searchString','');
-    }
-  },
-  init:function () {
-    if (App.testing) {
-      return this._super();
-    }
-    var controller = this;
-    var adapter = controller.store.adapterFor('file');
-    var url = adapter.buildURL('upload');
-    this.uploader.set('url',url);
-    this.uploader.on('didUpload', function (payload) {
-      controller.store.pushPayload('file', {'file': payload });
-    });
-    this._super();
-  },
 
-  sortProperties: ['name'],
-  sortAscending: true,
+    /* Deselects the current selections */
+    deselectAll: function() {
+      this.get('fileSelectionService').deselectAll();
+    },
 
-  needs: ["file"],
-  movingFile:null,
-  uploader:App.Uploader,
-  isRenaming:false,
-  isUploading:false,
-  queryParams: ['path'],
-  path: '/',
-  isRootDir:Ember.computed.equal('path', '/'),
-  hideMoving:function () {
-    return (this.movingFile)?[this.path,this.movingFile.name].join('/').replace('//','/')===this.movingFile.path:false;
-  }.property('movingFile','path'),
-  currentDir:function () {
-    return this.get('path').split('/').get('lastObject') || '/';
-  }.property('path'),
-  selectedOne:Ember.computed.equal('selectedFiles.length', 1),
-  isSelected:Ember.computed.gt('selectedFiles.length', 0),
-  selectedFiles:function () {
-    return this.get('content').filterBy('selected', true);
-  }.property('content.@each.selected'),
-  canConcat:function () {
-    return this.get('selectedFiles').filterProperty('isDirectory').get('length')===0;
-  }.property('selectedFiles.length'),
-
-  isSortPropertyEqualsDate: function() {
-    return this.get('sortProperties').get('firstObject') === 'date';
-  }.property('sortProperties.firstObject'),
-
-  searchString:'',
-  fileList: function () {
-    var fileList = this.get('arrangedContent');
-    var search = this.get('searchString');
-    return (search)?fileList.filter(function (file) {
-      return !!file.get('name').match(search);
-    }):fileList;
-  }.property('arrangedContent','searchString'),
-
-  mkdirSuccessCalback:function (newDir) {
-    if (newDir.get('path') != [this.get('path'),newDir.get('name')].join('/')){
-      newDir.unloadRecord();
-      newDir.store.listdir(this.get('path'));
+    //Context Menu actions
+    openFolder: function(path) {
+      this.transitionToRoute({queryParams: {path: path}});
     }
   },
 
-  clearSearch:function () {
-    this.set('searchString','');
-  }.observes('path'),
-
-  deleteErrorCallback:function (record,error) {
-    this.model.pushRecord(record);
-    this.throwAlert(error);
-  },
-
-  chmodErrorCallback:function (record,error) {
-    record.rollback();
-    this.throwAlert({message:'Permissions change failed'});
-  },
-
-  throwAlert:function (error) {
-    this.send('showAlert',error);
+  _getIndexRangeBetweenfiles: function(startFile, endFile) {
+    var startIndex = this.get('arrangedContent').indexOf(startFile);
+    var endIndex = this.get('arrangedContent').indexOf(endFile);
+    if (startIndex < endIndex) {
+      return [startIndex, endIndex];
+    } else {
+      return [endIndex, startIndex];
+    }
   },
 
-  showSpinner:function () {
-    this.set('isLoadingFiles',true);
+  _getFilesInRange: function(startIndex, endIndex) {
+    var range = Array.apply(null, Array(endIndex - startIndex + 1)).map(function (_, i) {return startIndex + i;});
+    return this.get('arrangedContent').objectsAt(range);
   },
 
-  hideSpinner:function () {
-    this.set('isLoadingFiles',false);
+  _getSortOrderString: function(column) {
+    if (column['sortOrder'] === -1) {
+      return 'desc';
+    } else if (column['sortOrder'] === 1) {
+      return 'asc';
+    } else {
+      return '';
+    }
   }
 });
-
-

+ 11 - 11
contrib/views/files/src/main/resources/ui/app/views/files.js → contrib/views/files/src/main/resources/ui/app/controllers/messages.js

@@ -16,17 +16,17 @@
  * limitations under the License.
  */
 
-var App = require('app');
+import Ember from 'ember';
 
-App.FilesView = Em.View.extend({
-    templateName: 'files',
-    didInsertElement:function () {
-      this.scheduleRebind();
-    },
-    scheduleRebind:function () {
-      Em.run.scheduleOnce('render', this, this.get('reBindTooltips'));
-    },
-    reBindTooltips:function () {
-      this.$().tooltip({selector:'[data-toggle=tooltip]'});
+export default Ember.Controller.extend({
+  filesController: Ember.inject.controller('files'),
+  currentBrowserPath: Ember.computed.oneWay('filesController.path'),
+  isExpanded: true,
+  shortenLength: Ember.computed('isExpanded', function() {
+    if(this.get('isExpanded') === true) {
+      return 200;
+    } else {
+      return 100;
     }
+  })
 });

+ 31 - 0
contrib/views/files/src/main/resources/ui/app/controllers/messages/message.js

@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Controller.extend({
+
+  showStatus: Ember.computed('model', function() {
+    return this.get('model.status') !== -1;
+  }),
+
+  displayBody: Ember.computed('model', function() {
+    return !(Ember.isBlank(this.get('model.responseMessage'))
+      && Ember.isBlank(this.get('model.trace')));
+  })
+});

+ 0 - 91
contrib/views/files/src/main/resources/ui/app/controllers/previewModal.js

@@ -1,91 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.PreviewModalController = Em.ObjectController.extend({
-    needs:['files', 'file'],
-    offset: 3000 ,
-    startIndex:0,
-    file:Em.computed.alias('content'),
-    filePageText:'',
-    reload: false,
-    pagecontent: Ember.computed('file', 'startIndex', 'endIndex', 'reload', function() {
-        var file = this.get('file');
-        var filepath = file.get('path');
-        var filePageText = this.get('filePageText');
-
-        var self = this,
-            defer = Ember.RSVP.defer(),
-            startIndex = this.get('startIndex'),
-            endIndex  = this.get('endIndex');
-
-        var pathName = window.location.pathname;
-        var pathNameArray = pathName.split("/");
-        var ViewVersion = pathNameArray[3];
-        var viewName = pathNameArray[4];
-        var previewServiceURL = "/api/v1/views/FILES/versions/"+ ViewVersion + "/instances/" + viewName + "/resources/files/preview/file" + '?path=' + filepath + '&start='+ startIndex +'&end='+ endIndex;
-
-        var previousText = $('.preview-content').text();
-
-        $.ajax({
-            url: previewServiceURL,
-            dataType: "json",
-            type: 'get',
-            async: false,
-            contentType: 'application/json',
-            success: function( response, textStatus, jQxhr ){
-                self.set('filePageText', previousText + response.data);
-                self.set('isFileEnd',response.isFileEnd);
-            },
-            error: function( jqXhr, textStatus, errorThrown ){
-                console.log( "Preview Fail pagecontent : " + errorThrown );
-              self.send('removePreviewModal');
-              self.send('showAlert', jqXhr);
-              self.set('reload', !self.get('reload'));
-            }
-        });
-
-        if(self.get('isFileEnd') == true){
-           this.set('showNext', false);
-        }
-        return self.get('filePageText');
-    }),
-    endIndex: Ember.computed('startIndex', 'offset', function() {
-        var startIndex = this.get('startIndex'),
-            offset  = this.get('offset');
-        return startIndex + offset;
-    }),
-    showPrev : Ember.computed('startIndex', function() {
-        var startIndex = this.get('startIndex');
-        this.set('showNext', true);
-        return ((startIndex == 0) ? false : true );
-    }),
-    showNext : true,
-    actions:{
-        next: function(){
-            console.log('Next');
-            this.set('startIndex', this.get('startIndex') + this.get('offset'));
-            return this.get('filePageText');
-        },
-        prev: function(){
-            console.log('Prev');
-            this.set('startIndex', (this.get('startIndex') - this.get('offset')) > 0 ? (this.get('startIndex') - this.get('offset')) : 0);
-        }
-    }
-});

+ 0 - 0
contrib/views/files/src/main/resources/ui/app/helpers/.gitkeep


+ 27 - 0
contrib/views/files/src/main/resources/ui/app/helpers/alert-message-context-class.js

@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export function alertMessageContextClass(params) {
+  let messageType = params[0];
+  let prefix = params[1];
+  return `${prefix}${messageType}`;
+}
+
+export default Ember.Helper.helper(alertMessageContextClass);

+ 37 - 0
contrib/views/files/src/main/resources/ui/app/helpers/alert-message-icon-class.js

@@ -0,0 +1,37 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export function alertMessageIconClass(params) {
+  let type = params[0];
+  switch (type) {
+    case 'success':
+      return 'check';
+    case 'info':
+      return 'info';
+    case 'warning':
+      return 'exclamation';
+    case 'danger':
+      return 'times';
+    default:
+      return 'check';
+  }
+}
+
+export default Ember.Helper.helper(alertMessageIconClass);

+ 34 - 0
contrib/views/files/src/main/resources/ui/app/helpers/get-sorting-icon.js

@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export function getSortingIcon(params/*, hash*/) {
+  let sortOrder = params[0];
+  var iconClass;
+  if (sortOrder === 1) {
+    iconClass = "chevron-down";
+  } else if (sortOrder === -1) {
+    iconClass = "chevron-up";
+  } else {
+    iconClass = "chevron-right";
+  }
+  return iconClass;
+}
+
+export default Ember.Helper.helper(getSortingIcon);

+ 34 - 0
contrib/views/files/src/main/resources/ui/app/helpers/get-value-from-columns.js

@@ -0,0 +1,34 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export function getValueFromColumns(params) {
+  let columnsArray = params[0];
+  let key = params[1];
+  let paramKey = params[2];
+
+  var column = columnsArray.filterBy('key', key);
+
+  if(column.length > 0) {
+    return column[0][paramKey];
+  }
+  return "";
+}
+
+export default Ember.Helper.helper(getValueFromColumns);

+ 11 - 12
contrib/views/files/src/main/resources/ui/app/views/file.js → contrib/views/files/src/main/resources/ui/app/helpers/shorten-text.js

@@ -16,18 +16,17 @@
  * limitations under the License.
  */
 
-var App = require('app');
+import Ember from 'ember';
 
-Em.CloakedView.reopen({
-  classNames:['file-row'],
-  classNameBindings:['_containedView.controller.isMoving:isMoving']
-});
+export function shortenText(params) {
+  let text = params[0];
+  let length = params[1];
+  if (text.length < length) {
+    return text;
+  } else {
+    return text.substring(0, length - 3) + '...';
+  }
 
-Ember.CloakedView.reopen({
-  cloak:Em.K
-});
+}
 
-App.FileView = Em.View.extend({
-  templateName: 'util/fileRow',
-  tagName:'tr'
-});
+export default Ember.Helper.helper(shortenText);

+ 27 - 0
contrib/views/files/src/main/resources/ui/app/helpers/show-date.js

@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export function showDate(params) {
+  let date = params[0];
+  let format = params[1];
+  return moment(date).format(format);
+}
+
+export default Ember.Helper.helper(showDate);

+ 33 - 0
contrib/views/files/src/main/resources/ui/app/helpers/size-humanize.js

@@ -0,0 +1,33 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export function sizeHumanize(params) {
+  let fileSizeInBytes = params[0];
+  var i = -1;
+  var byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
+  do {
+      fileSizeInBytes = fileSizeInBytes / 1024;
+      i++;
+  } while (fileSizeInBytes > 1024);
+
+  return Math.max(fileSizeInBytes, 0.1).toFixed(1) + byteUnits[i];
+}
+
+export default Ember.Helper.helper(sizeHumanize);

+ 25 - 0
contrib/views/files/src/main/resources/ui/app/helpers/string-capitalize.js

@@ -0,0 +1,25 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export function stringCapitalize(params/*, hash*/) {
+  return params;
+}
+
+export default Ember.Helper.helper(stringCapitalize);

+ 42 - 0
contrib/views/files/src/main/resources/ui/app/index.html

@@ -0,0 +1,42 @@
+<!--
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <title>HDFS - File Browser</title>
+    <meta name="description" content="">
+
+    {{content-for "head"}}
+
+    <link rel="stylesheet" href="assets/vendor.css">
+    <link rel="stylesheet" href="assets/files-view.css">
+
+    {{content-for "head-footer"}}
+  </head>
+  <body>
+    {{content-for "body"}}
+
+    <script src="assets/vendor.js"></script>
+    <script src="assets/files-view.js"></script>
+
+    {{content-for "body-footer"}}
+  </body>
+</html>

+ 0 - 99
contrib/views/files/src/main/resources/ui/app/initialize.js

@@ -1,99 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-//////////////////////////////////
-// Adapter
-//////////////////////////////////
-
-require('adapter');
-
-//////////////////////////////////
-// Templates
-//////////////////////////////////
-
-require('templates/application');
-require('templates/index');
-require('templates/files');
-require('templates/error');
-require('templates/modal/chmod');
-require('templates/modal/preview');
-require('templates/util/errorRow');
-require('templates/util/fileRow');
-
-require('templates/components/uploader');
-require('templates/components/renameInput');
-require('templates/components/deletePopover');
-require('templates/components/mkdirInput');
-require('templates/components/contextMenu');
-require('templates/components/deleteBulk');
-
-//////////////////////////////////
-// Models
-//////////////////////////////////
-
-require('models/file');
-
-/////////////////////////////////
-// Controllers
-/////////////////////////////////
-
-require('controllers/files');
-require('controllers/file');
-require('controllers/error');
-require('controllers/filesAlert');
-require('controllers/chmodModal');
-require('controllers/previewModal');
-
-/////////////////////////////////
-// Components
-/////////////////////////////////
-
-require('components/uploader');
-require('components/contextMenu');
-require('components/renameInput');
-require('components/bsPopover');
-require('components/confirmDelete');
-require('components/sortArrow');
-require('components/breadCrumbs');
-require('components/popoverDelete');
-require('components/bulkCheckbox');
-require('components/mkdirInput');
-require('components/toggleContext');
-
-/////////////////////////////////
-// Views
-/////////////////////////////////
-
-require('views/file');
-require('views/files');
-require('views/filesAlert');
-require('views/modalChmod');
-require('views/modalPreview');
-
-/////////////////////////////////
-// Routes
-/////////////////////////////////
-
-require('routes/file');
-require('routes/error');
-
-/////////////////////////////////
-// Router
-/////////////////////////////////
-
-require('router');

+ 57 - 0
contrib/views/files/src/main/resources/ui/app/mixins/file-operation.js

@@ -0,0 +1,57 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+/*
+  Base Mixin to be used by different Services to get the common behaviors mixed
+  in to the service.
+*/
+export default Ember.Mixin.create({
+  store: Ember.inject.service('store'),
+
+  getBaseFilesURLPath: function() {
+    // TODO: This has to be changed when it is integrated inside Ambari
+    //var pathName = window.location.pathname;
+    var pathname = '/api/v1/views/FILES/versions/1.0.0/instances/Files/resources/files';
+    return pathname;
+  },
+
+  getBaseDirPath: function(path) {
+    return path.substring(0, path.lastIndexOf('/') + 1);
+  },
+
+  _getBaseURLFragments: function() {
+    var adapter = this.get('store').adapterFor('file');
+    var baseURL = adapter.buildURL('file');
+    return baseURL.split('/');
+  },
+
+  extractError: function(error) {
+    if (Ember.isArray(error.errors) && (error.errors.length >= 0)) {
+      return error.errors[0];
+    }
+    return {};
+  },
+
+  isInvalidError: function(error) {
+    // This seems to a slight hack. But from backend the response of 422 is
+    // always a hash which has success param set and value is false
+    return Ember.isPresent(error.success) && error.success === false;
+  }
+});

+ 82 - 0
contrib/views/files/src/main/resources/ui/app/mixins/operation-modal.js

@@ -0,0 +1,82 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import { EKMixin, keyUp, EKFirstResponderOnFocusMixin } from 'ember-keyboard';
+
+export default Ember.Mixin.create(EKMixin, EKFirstResponderOnFocusMixin, {
+  modalEventBus: Ember.inject.service('modal-event-bus'),
+  name: '',
+  closeOnEscape: false,
+  isModalOpen: false,
+
+  setupKey: Ember.on('init', function() {
+    this.set('keyboardActivated', true);
+  }),
+
+  //disableEscape:
+  closeModalOnEscape: Ember.on(keyUp('Escape'), function() {
+    if (this.get('closeOnEscape')) {
+      this.$('.modal').modal('hide');
+    }
+  }),
+
+  initModal: function() {
+    Ember.defineProperty(this, 'modalGuard', Ember.computed.alias('modalEventBus.' + this.get('name')));
+    this.addObserver('modalGuard', () => {
+      if(this.get('modalGuard')) {
+        this.set('isModalOpen', true);
+        Ember.run.later(this, () => {
+          this.$('.modal').modal({backdrop: 'static', keyboard: false});
+          this.$('.modal').on('hide.bs.modal', () => {
+            this.send('closeModal');
+          });
+          this.send('modalOpened');
+        });
+
+      }
+    });
+  }.on('didInitAttrs'),
+
+  hideModal: Ember.on('willDestroyElement', function() {
+    if (this.get('isModalOpen')) {
+      this.$('.modal').modal('hide');
+    }
+  }),
+
+  actions: {
+    /** close by action in the UI **/
+    close: function() {
+      this.$('.modal').modal('hide');
+    },
+
+    closeModal: function() {
+      this.$('.modal').off('hide.bs.modal');
+      this.set('isModalOpen', false);
+      this.get('modalEventBus').resetModal(this.get('name'));
+      this.send('didCloseModal');
+    },
+
+    modalOpened: function() {
+      this.send('didOpenModal');
+    },
+
+    didCloseModal: function() {},
+    didOpenModal: function() {}
+  }
+});

+ 0 - 0
contrib/views/files/src/main/resources/ui/app/models/.gitkeep


+ 27 - 0
contrib/views/files/src/main/resources/ui/app/models/alert.js

@@ -0,0 +1,27 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import DS from 'ember-data';
+
+export default DS.Model.extend({
+  type: DS.attr('string'),
+  message: DS.attr('string'),
+  responseMessage: DS.attr('string'),
+  status: DS.attr('number'),
+  trace: DS.attr('string')
+});

+ 25 - 25
contrib/views/files/src/main/resources/ui/app/models/file.js

@@ -16,36 +16,36 @@
  * limitations under the License.
  */
 
-var App = require('app');
+import DS from 'ember-data';
+import Ember from 'ember';
 
-var dsa = DS.attr;
+export default DS.Model.extend({
+
+  isDirectory                       : DS.attr('boolean'),
+  readAccess                        : DS.attr('boolean'),
+  writeAccess                       : DS.attr('boolean'),
+  executeAccess                     : DS.attr('boolean'),
+  len                               : DS.attr('number'),
+  owner                             : DS.attr('string'),
+  group                             : DS.attr('string'),
+  permission                        : DS.attr('string'),
+  accessTime                        : DS.attr('iso-date'),
+  modificationTime                  : DS.attr('iso-date'),
+  blockSize                         : DS.attr('number'),
+  replication                       : DS.attr('number'),
+  size                              : Ember.computed.alias('len'),
 
-App.File = DS.Model.extend({
   path: function() {
     return this.get('id');
   }.property('id'),
-  basedir:function () {
-    var path = this.get('id');
-    return path.substring(0,path.lastIndexOf('/'))||'/';
-  }.property('id'),
-  isDirectory: dsa('boolean'),
-  readAccess: dsa('boolean'),
-  writeAccess: dsa('boolean'),
-  executeAccess: dsa('boolean'),
-  len: dsa('number'),
-  owner: dsa('string'),
-  group: dsa('string'),
-  permission: dsa('string'),
-  accessTime: dsa('isodate'),
-  modificationTime: dsa('isodate'),
-  blockSize: dsa('number'),
-  replication: dsa('number'),
-  name:function () {
-    var splitpath = this.get('path').split('/');
-    return splitpath.get(splitpath.length-1);
+
+  name: function() {
+    var splitPath = this.get('path').split('/');
+    return splitPath.get(splitPath.length - 1);
   }.property('path'),
-  date:function () {
+
+  date: function() {
     return parseInt(moment(this.get('modificationTime')).format('X'));
-  }.property('modificationTime'),
-  size: Em.computed.alias('len')
+  }.property('modificationTime')
+
 });

+ 13 - 3
contrib/views/files/src/main/resources/ui/app/router.js

@@ -16,8 +16,18 @@
  * limitations under the License.
  */
 
-App = require('app');
+import Ember from 'ember';
+import config from './config/environment';
 
-App.Router.map(function() {
-  this.route('files', { queryParams:['path'],path: '/',});
+const Router = Ember.Router.extend({
+  location: config.locationType
 });
+
+Router.map(function() {
+  this.route('files');
+  this.route('messages', function() {
+    this.route('message', {path: '/:message_id'});
+  });
+});
+
+export default Router;

+ 0 - 0
contrib/views/files/src/main/resources/ui/app/routes/.gitkeep


+ 78 - 0
contrib/views/files/src/main/resources/ui/app/routes/application.js

@@ -0,0 +1,78 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Route.extend({
+  fileOperationService: Ember.inject.service('file-operation'),
+  model: function() {
+    var promise = {
+      homeDir: this.get('fileOperationService').getHome(),
+      trashDir:  this.get('fileOperationService').getTrash()
+    };
+
+    return Ember.RSVP.hashSettled(promise).then(function(hash) {
+      var response = {
+        homeDir: {path: '', hasError: true},
+        trashDir: {path: '', hasError: true}
+      };
+
+      if(hash.homeDir.state === 'fulfilled'){
+        response.homeDir.path = hash.homeDir.value.path;
+        response.homeDir.hasError = false;
+      }
+
+      if(hash.trashDir.state === 'fulfilled'){
+        response.trashDir.path = hash.trashDir.value.path;
+        response.trashDir.hasError = false;
+      }
+
+      return response;
+    });
+  },
+  setupController: function(controller, hash) {
+    this._super(controller, hash);
+    if(hash.homeDir.hasError === false) {
+      this.controllerFor('files').set('homePath', hash.homeDir.path);
+      this.controllerFor('files').set('hasHomePath', true);
+    }
+
+    if(hash.trashDir.hasError === false) {
+      this.controllerFor('files').set('trashPath', hash.trashDir.path);
+      this.controllerFor('files').set('hasTrashPath', true);
+    }
+  },
+
+  actions: {
+    loading(transition, route) {
+      let startTime = moment();
+      let appController = this.controllerFor('application');
+      // when the application loads up we want the loading template to be
+      // rendered and not the loading spinner in the application template
+      if(appController.get('firstLoad') === false) {
+        appController.set('isLoading', true);
+      }
+      transition.promise.finally(() => {
+        console.log("Loaded in " + (moment() - startTime) + "ms");
+        appController.set('isLoading', false);
+        appController.set('firstLoad', false);
+      });
+      return appController.get('firstLoad');
+    }
+  }
+});

+ 0 - 134
contrib/views/files/src/main/resources/ui/app/routes/file.js

@@ -1,134 +0,0 @@
-/**
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-var App = require('app');
-
-App.FilesRoute = Em.Route.extend({
-  queryParams: {
-    path: {
-      refreshModel: true
-    }
-  },
-  actions:{
-    refreshDir:function () {
-      this.refresh();
-    },
-    loading:function  (argument) {
-      var target = this.controllerFor('files');
-      target.showSpinner();
-      this.router.one('didTransition', target, 'hideSpinner');
-    },
-    error:function (error,transition,e) {
-      this.controllerFor('files').set('isLoadingFiles', false);
-      if (this.router._lookupActiveView('files')) {
-        this.send('showAlert',error);
-      } else {
-        return true;
-      }
-    },
-    dirUp: function () {
-      var currentPath = this.controllerFor('files').get('path');
-      var upDir = currentPath.substring(0,currentPath.lastIndexOf('/'));
-      var target = upDir || '/';
-      return this.transitionTo('files',{queryParams: {path: target}});
-    },
-    willTransition:function (argument) {
-      var hasModal = this.router._lookupActiveView('modal.chmod'),
-          hasAlert = this.router._lookupActiveView('files.alert'),
-          hasPreviewModal = this.router._lookupActiveView('modal.preview');
-
-      Em.run.next(function(){
-        if (hasAlert) this.send('removeAlert');
-        if (hasModal) this.send('removeChmodModal');
-        if (hasPreviewModal) this.send('removePreviewModal');
-      }.bind(this));
-    },
-
-    showChmodModal:function (content) {
-      this.controllerFor('chmodModal').set('content',content);
-      this.render('modal.chmod',{
-        into:'files',
-        outlet:'modal',
-        controller:'chmodModal'
-      });
-    },
-
-    showPreviewModal :function (content) {
-      var controller = this.controllerFor('previewModal');
-      controller.set('reload', true);
-      controller.set('content',content);
-      controller.set('startIndex',0);
-
-      this.render('modal.preview',{
-        into:'files',
-        outlet:'modal',
-        controller:'previewModal'
-      });
-    },
-
-    removeChmodModal:function () {
-      this.disconnectOutlet({
-        outlet: 'modal',
-        parentView: 'files'
-      });
-    },
-    removePreviewModal:function () {
-      this.disconnectOutlet({
-        outlet: 'modal',
-        parentView: 'files'
-      });
-    },
-    showAlert:function (error) {
-      this.controllerFor('filesAlert').set('content',error);
-      this.render('files.alert',{
-        into:'files',
-        outlet:'error',
-        controller:'filesAlert'
-      });
-    },
-    removeAlert:function () {
-      this.disconnectOutlet({
-        outlet: 'error',
-        parentView: 'files'
-      });
-    }
-  },
-  model:function (params) {
-    var path = (Em.isEmpty(params.path))?'/':params.path;
-    var model = this.store.listdir(path);
-    this.set('prevModel',model);
-    return model;
-  },
-  prevModel:null,
-  beforeModel:function () {
-    if (this.get('prevModel.isPending')) {
-      this.get('prevModel').then(function (files) {
-        files.forEach(function (file) {
-          file.store.unloadRecord(file);
-        });
-      });
-    }
-  },
-  afterModel: function (model) {
-    this.store.all('file').forEach(function (file) {
-      if (!model.contains(file)) {
-        file.unloadRecord();
-      }
-    });
-  }
-});

+ 61 - 0
contrib/views/files/src/main/resources/ui/app/routes/files.js

@@ -0,0 +1,61 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import FileOperationMixin from '../mixins/file-operation';
+
+export default Ember.Route.extend(FileOperationMixin, {
+  logger: Ember.inject.service('alert-messages'),
+  fileSelectionService: Ember.inject.service('files-selection'),
+  currentPath: '/',
+  queryParams: {
+    path: {
+      refreshModel: true
+    }
+  },
+  model: function(params) {
+    this.store.unloadAll('file');
+    return this.store.query('file', {path: params.path});
+  },
+
+  setupController: function(controller, model) {
+    this._super(controller, model);
+    controller.set('searchText', '');
+    this.get('fileSelectionService').reset();
+    this.set('currentPath', controller.get('path'));
+  },
+
+  actions: {
+    refreshCurrentRoute: function() {
+      this.refresh();
+    },
+
+    error: function(error, transition) {
+      this.get('fileSelectionService').reset();
+      let path = transition.queryParams.path;
+      var formattedError = this.extractError(error);
+      this.get('logger').danger(`Failed to transition to <strong>${path}</strong>`, formattedError);
+      // Had to do this as we are unloading all files before transitioning
+      this.transitionTo({
+        queryParams: {
+          path: this.get('currentPath')
+        }
+      });
+    }
+  }
+});

+ 5 - 3
contrib/views/files/src/main/resources/ui/generators/controller/controller.js.hbs → contrib/views/files/src/main/resources/ui/app/routes/index.js

@@ -16,8 +16,10 @@
  * limitations under the License.
  */
 
-var App = require('app');
+import Ember from 'ember';
 
-App.{{#camelize}}{{name}}{{/camelize}}Controller = Em.ObjectController.extend({
-    content: null
+export default Ember.Route.extend({
+  beforeModel: function(transition) {
+    this.transitionTo('files');
+  }
 });

+ 11 - 12
contrib/views/files/src/main/resources/ui/app/controllers/filesAlert.js → contrib/views/files/src/main/resources/ui/app/routes/messages.js

@@ -16,17 +16,16 @@
  * limitations under the License.
  */
 
-App.FilesAlertController = App.ErrorController.extend({
-  content:null,
-  output:function () {
-    var error = this.get('content'),
-        message = (error.responseJSON)?error.responseJSON.message:error.message,
-        output;
+import Ember from 'ember';
 
-    if (message) {
-      message = message.split('\n').objectAt(0);
-    }
-
-    return message;
-  }.property('content')
+export default Ember.Route.extend({
+  logger: Ember.inject.service('alert-messages'),
+  model: function() {
+    return this.store.peekAll('alert');
+  },
+  setupController: function(controller, model) {
+    this._super(controller, model);
+    this.get('logger').clearMessages();
+    controller.set('isExpanded', true);
+  }
 });

+ 31 - 0
contrib/views/files/src/main/resources/ui/app/routes/messages/message.js

@@ -0,0 +1,31 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Route.extend({
+  model: function(params) {
+    return this.store.peekRecord('alert', params.message_id);
+  },
+
+  setupController: function(controller, model) {
+    this._super(controller, model);
+    var messagesController = this.controllerFor('messages');
+    messagesController.set('isExpanded', false);
+  }
+});

+ 3 - 3
contrib/views/files/src/main/resources/ui/generators/model/model.js.hbs → contrib/views/files/src/main/resources/ui/app/serializers/file.js

@@ -16,8 +16,8 @@
  * limitations under the License.
  */
 
-var App = require('app');
+import DS from 'ember-data';
 
-App.{{#camelize}}{{name}}{{/camelize}} = DS.Model.extend({
-    
+export default DS.RESTSerializer.extend({
+  primaryKey: 'path'
 });

+ 126 - 0
contrib/views/files/src/main/resources/ui/app/services/alert-messages.js

@@ -0,0 +1,126 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+
+/**
+  Shows alert flash and also creates `alert` objects in store. If creation of
+  `alert` objects in store pass `options.flashOnly` as `true`. The options
+  required for creating the `alert` objects are:
+  ```
+    options.message: message field returned by the API server.
+    options.status : Status XHR request if the message is a response to XHR request. Defaults to -1.
+    options.error: Detailed error to be displayed.
+  ```
+  Options required for ember-cli-flash can also be passed in the alertOptions to override the
+  default behaviour.
+*/
+export default Ember.Service.extend({
+  flashMessages: Ember.inject.service('flash-messages'),
+  store: Ember.inject.service('store'),
+
+  success: function(message, options = {}, alertOptions = {}) {
+    this._processMessage('success', message, options, alertOptions);
+  },
+
+  warn: function(message, options = {}, alertOptions = {}) {
+    this._processMessage('warn', message, options, alertOptions);
+  },
+
+  info: function(message, options = {}, alertOptions = {}) {
+    this._processMessage('info', message, options, alertOptions);
+  },
+
+  danger: function(message, options = {}, alertOptions = {}) {
+    this._processMessage('danger', message, options, alertOptions);
+  },
+
+  clearMessages: function() {
+    this.get('flashMessages').clearMessages();
+  },
+
+  _processMessage: function(type, message, options, alertOptions) {
+    this._clearMessagesIfRequired(alertOptions);
+    let alertRecord = this._createAlert(message, type, options, alertOptions);
+    if(alertRecord) {
+      message = this._addDetailsToMessage(message, alertRecord);
+    }
+    switch (type) {
+      case 'success':
+        this.get('flashMessages').success(message, this._getOptions(alertOptions));
+        break;
+      case 'warn':
+        this.get('flashMessages').warning(message, this._getOptions(alertOptions));
+        break;
+      case 'info':
+        this.get('flashMessages').info(message, this._getOptions(alertOptions));
+        break;
+      case 'danger':
+        this.get('flashMessages').danger(message, this._getOptions(alertOptions));
+    }
+  },
+
+  _addDetailsToMessage: function(message, record) {
+    let id = record.get('id');
+    let suffix = `<a href="#/messages/${id}">(details)</a>`;
+    return message + "  " + suffix;
+  },
+
+  _createAlert: function(message, type, options, alertOptions) {
+    var data = {};
+    data.message = message;
+    data.responseMessage = options.message || '';
+    data.id = this._getNextAlertId();
+    data.type = type;
+    data.status = options.status || -1;
+    data.trace = this._getDetailedError(options.trace);
+    delete options.status;
+    delete options.error;
+
+    if(alertOptions.flashOnly === true) {
+      return;
+    }
+
+    return this.get('store').createRecord('alert', data);
+  },
+
+  _getDetailedError: function(error) {
+    return error || '';
+  },
+
+  _getOptions: function(options = {}) {
+    var defaultOptions = {
+      priority: 100,
+      showProgress: true,
+      timeout: 6000
+    };
+    return Ember.merge(defaultOptions, options);
+  },
+
+  _getNextAlertId: function() {
+    return this.get('store').peekAll('alert').get('length') + 1;
+  },
+
+  _clearMessagesIfRequired: function(options = {}) {
+    var stackMessages = options.stackMessages || false;
+    if(stackMessages !== true) {
+      this.clearMessages();
+    }
+  }
+});

+ 48 - 0
contrib/views/files/src/main/resources/ui/app/services/file-copy.js

@@ -0,0 +1,48 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import FileOperationMixin from '../mixins/file-operation';
+
+export default Ember.Service.extend(FileOperationMixin, {
+  logger: Ember.inject.service('alert-messages'),
+
+  // Returns a promise for the operation. Upon sucess or error, this also
+  // appropriately sends error messages.
+
+  copy: function (srcPath, destName) {
+    return new Ember.RSVP.Promise((resolve, reject) => {
+      var adapter = this.get('store').adapterFor('file');
+      var baseURL = adapter.buildURL('file');
+      var moveUrl = baseURL.substring(0, baseURL.lastIndexOf('/')) + "/copy";
+      var data = {sourcePaths: srcPath, destinationPath: destName};
+      adapter.ajax(moveUrl, "POST", {data: data}).then((response) => {
+        this.get('logger').success(`Successfully copied to ${destName}.`, {}, {flashOnly: true});
+        resolve(response);
+      }, (responseError) => {
+        var error = this.extractError(responseError);
+        this.get('logger').danger(`Failed to copy to ${destName}`, error);
+        reject(error);
+      });
+    });
+  },
+
+  _isDestinationPathExists(destinationPath) {
+    return this.get('store').peekAll('file').isAny('path', destinationPath);
+  }
+});

+ 47 - 0
contrib/views/files/src/main/resources/ui/app/services/file-move.js

@@ -0,0 +1,47 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import FileOperationMixin from '../mixins/file-operation';
+
+export default Ember.Service.extend(FileOperationMixin, {
+  logger: Ember.inject.service('alert-messages'),
+
+  // Returns a promise for the operation. Upon sucess or error, this also
+  // appropriately sends error messages.
+  move: function(srcPath, destName ) {
+    return new Ember.RSVP.Promise((resolve, reject) => {
+      var adapter = this.get('store').adapterFor('file');
+      var baseURL = adapter.buildURL('file');
+      var moveUrl = baseURL.substring(0, baseURL.lastIndexOf('/')) + "/move";
+      var data = {sourcePaths: srcPath, destinationPath: destName};
+      adapter.ajax(moveUrl, "POST", {data: data}).then((response) => {
+        this.get('logger').success(`Successfully moved to ${destName}.`, {}, {flashOnly: true});
+        resolve(response);
+      }, (responseError) => {
+        var error = this.extractError(responseError);
+        this.get('logger').danger(`Failed to move to ${destName}`, error);
+        reject(error);
+      });
+    });
+  },
+
+  _isDestinationPathExists(destinationPath) {
+    return this.get('store').peekAll('file').isAny('path', destinationPath);
+  }
+});

+ 199 - 0
contrib/views/files/src/main/resources/ui/app/services/file-operation.js

@@ -0,0 +1,199 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import FileOperationMixin from '../mixins/file-operation';
+
+export default Ember.Service.extend(FileOperationMixin, {
+  logger: Ember.inject.service('alert-messages'),
+  chmod: function (path, permission) {
+    var adapter = this.get('store').adapterFor('file');
+    var data = {mode: permission, path: path};
+    return new Ember.RSVP.Promise((resolve, reject) => {
+      adapter.ajax(this._getFileOperationUrl('chmod'), "POST", {data: data}).then(
+        (response) => {
+          this.get('logger').success(`Successfully changed permission of ${path}`, {}, {flashOnly: false});
+          return resolve(response);
+        }, (responseError) => {
+          var error = this.extractError(responseError);
+          this.get('logger').danger(`Failed to modify permission of ${path}`, error);
+          return reject(error);
+        });
+    });
+  },
+
+  createNewFolder: function (srcPath, folderName) {
+    var path = (srcPath === '/') ? '' : srcPath;
+
+    if (folderName.slice(0, 1) === '/') {
+      folderName = folderName.slice(0, folderName.length);
+    }
+    var adapter = this.get('store').adapterFor('file');
+    var data = {path: `${path}/${folderName}`};
+    return new Ember.RSVP.Promise((resolve, reject) => {
+      adapter.ajax(this._getFileOperationUrl('mkdir'), "PUT", {data: data}).then(
+        (response) => {
+          this.get('logger').success(`Successfully created <strong>${path}/${folderName}`, {flashOnly: true});
+          return resolve(response);
+        }, (responseError) => {
+          var error = this.extractError(responseError);
+          this.get('logger').danger(`Failed to create ${path}/${folderName}`, error);
+          return reject(error);
+        });
+    });
+  },
+
+  deletePaths: function (paths, deletePermanently = false) {
+    var opsUrl;
+    if (deletePermanently) {
+      opsUrl = this._getFileOperationUrl('remove');
+    } else {
+      opsUrl = this._getFileOperationUrl('moveToTrash');
+    }
+    var data = {
+      paths: paths.map((path) => {
+        return {path: path, recursive: true};
+      })
+    };
+    var adapter = this.get('store').adapterFor('file');
+    return new Ember.RSVP.Promise((resolve, reject) => {
+      adapter.ajax(opsUrl, "DELETE", {data: data}).then(
+        (response) => {
+          return resolve(response);
+        }, (rejectResponse) => {
+          var error = this.extractError(rejectResponse);
+          if (this.isInvalidError(error)) {
+            return reject(this._prepareUnprocessableErrorResponse(error));
+          } else {
+            return reject(Ember.merge({retry: false, unprocessable: false}, error));
+          }
+        });
+    });
+  },
+
+  listPath: function (queryPath, onlyDirectory = true) {
+    let baseUrl = this._getFileOperationUrl('listdir');
+    let url = `${baseUrl}?${queryPath}`;
+    var adapter = this.get('store').adapterFor('file');
+    return new Ember.RSVP.Promise((resolve, reject) => {
+      adapter.ajax(url, "GET").then(
+        (response) => {
+          if (onlyDirectory) {
+            return resolve(response.files.filter((entry) => {
+              return entry.isDirectory;
+            }));
+          } else {
+            return resolve(response.files);
+          }
+        }, (responseError) => {
+          var error = this.extractError(responseError);
+          return reject(error);
+        });
+    });
+  },
+
+  movePaths: function (srcPath, destName) {
+    return new Ember.RSVP.Promise((resolve, reject) => {
+      var adapter = this.get('store').adapterFor('file');
+      var baseURL = adapter.buildURL('file');
+      var moveUrl = baseURL.substring(0, baseURL.lastIndexOf('/')) + "/move";
+      var data = {sourcePaths: srcPath, destinationPath: destName};
+      adapter.ajax(moveUrl, "POST", {data: data}).then((response) => {
+        this.get('logger').success(`Successfully moved to ${destName}.`, {}, {flashOnly: true});
+        resolve(response);
+      }, (rejectResponse) => {
+        var error = this.extractError(rejectResponse);
+        if (this.isInvalidError(error)) {
+          return reject(this._prepareUnprocessableErrorResponse(error));
+        } else {
+          return reject(Ember.merge({retry: false, unprocessable: false}, error));
+        }
+      });
+    });
+  },
+
+  copyPaths: function (srcPath, destName) {
+    return new Ember.RSVP.Promise((resolve, reject) => {
+      var adapter = this.get('store').adapterFor('file');
+      var baseURL = adapter.buildURL('file');
+      var moveUrl = baseURL.substring(0, baseURL.lastIndexOf('/')) + "/copy";
+      var data = {sourcePaths: srcPath, destinationPath: destName};
+      adapter.ajax(moveUrl, "POST", {data: data}).then((response) => {
+        this.get('logger').success(`Successfully copied to ${destName}.`, {}, {flashOnly: true});
+        resolve(response);
+      }, (rejectResponse) => {
+        var error = this.extractError(rejectResponse);
+        if (this.isInvalidError(error)) {
+          return reject(this._prepareUnprocessableErrorResponse(error));
+        } else {
+          return reject(Ember.merge({retry: false, unprocessable: false}, error));
+        }
+      });
+    });
+  },
+
+  _checkIfDeleteRetryIsRequired: function (error) {
+    return error.unprocessed.length >= 1;
+  },
+
+  _prepareUnprocessableErrorResponse: function (error) {
+    var response = {};
+    response.unprocessable = true;
+    if (this._checkIfDeleteRetryIsRequired(error)) {
+      response.retry = true;
+      response.failed = error.failed[0];
+      response.message = error.message;
+      response.unprocessed = error.unprocessed;
+    } else {
+      response.retry = false;
+      response.failed = error.failed[0];
+      response.message = error.message;
+    }
+
+    return response;
+  },
+
+  getHome: function () {
+    var adapter = this.get('store').adapterFor('file');
+    return adapter.ajax(this._getMiscUrl("/help/home"), "GET");
+  },
+
+  getTrash: function () {
+    var adapter = this.get('store').adapterFor('file');
+    return adapter.ajax(this._getMiscUrl("/help/trashDir"), "GET");
+  },
+
+  _getMiscUrl: function (segment) {
+    var urlFragments = this._getBaseURLFragments();
+    return urlFragments.slice(0, urlFragments.length - 2).join('/') + segment;
+  },
+
+  getUploadUrl: function () {
+    return this._getMiscUrl("/upload");
+  },
+
+  _getFileOperationUrl: function (pathFragment) {
+    var adapter = this.get('store').adapterFor('file');
+    var baseURL = adapter.buildURL('file');
+    return baseURL.substring(0, baseURL.lastIndexOf('/')) + `/${pathFragment}`;
+  },
+
+  isExistsInCurrentPath: function (name) {
+    return this.get('store').peekAll('file').isAny('name', name);
+  }
+});

+ 119 - 0
contrib/views/files/src/main/resources/ui/app/services/file-preview.js

@@ -0,0 +1,119 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import FileOperationMixin from '../mixins/file-operation';
+
+export default Ember.Service.extend(FileOperationMixin, {
+  fileSelectionService: Ember.inject.service('files-selection'),
+  selectedFiles: Ember.computed.alias('fileSelectionService.files'),
+  selected: Ember.computed('selectedFiles', function () {
+    return this.get('selectedFiles').objectAt(0);
+  }),
+  selectedFilePath: Ember.computed('selected.path', function () {
+    return this.get('selected.path');
+  }),
+  filesDownloadService: Ember.inject.service('files-download'),
+  fileContent: '',
+  startIndex: 0,
+  offset: 5000,
+  path: '',
+  isLoading: false,
+  fileFetchFinished: false,
+  hasError: false,
+
+  endIndex: function () {
+    return this.get('startIndex') + this.get('offset');
+  }.property('startIndex'),
+
+  reset: function () {
+    this.set('fileContent', '');
+    this.set('startIndex', 0);
+    this.set('offset', 5000);
+    this.set('path', '');
+    this.set('isLoading', false);
+    this.set('hasError', false);
+    this.set('fileFetchFinished', false);
+  },
+
+  getNextContent: function () {
+    return this._getContent();
+  },
+
+  _getContent: function () {
+
+    var _self = this;
+
+    if (this.get('fileFetchFinished')) {
+      return false;
+    }
+
+    var adapter = this.get('store').adapterFor('file');
+    var baseURL = adapter.buildURL('file');
+    var renameUrl = baseURL.substring(0, baseURL.lastIndexOf('/'));
+    var previewUrl = renameUrl.substring(0, renameUrl.lastIndexOf('/')) + "/preview/file?path=";
+
+    var currentFetchPath = previewUrl + this.get('selected.path') + '&start=' + this.get('startIndex') + '&end=' + this.get('endIndex');
+
+    this.set('isLoading', true);
+
+    Ember.$.ajax({
+      url: currentFetchPath,
+      dataType: 'json',
+      type: 'get',
+      contentType: 'application/json',
+      success: this._fetchSuccess,
+      beforeSend: function (xhr) {
+        xhr.setRequestHeader('X-Requested-By', 'ambari');
+        xhr.setRequestHeader('Authorization', 'Basic YWRtaW46YWRtaW4=');
+      },
+      success: function (response, textStatus, jQxhr) {
+        _self.set('fileContent', _self.get('fileContent') + response.data);
+        _self.set('fileFetchFinished', response.isFileEnd);
+        _self.set('isLoading', false);
+
+      },
+      error: function (jQxhr, textStatus, errorThrown) {
+        console.log("Preview Fail pagecontent: " + errorThrown);
+        _self.set('hasError', true);
+        _self.set('isLoading', false);
+      }
+    })
+
+    this.set('startIndex', (this.get('startIndex') + this.get('offset')));
+
+  },
+
+  _fetchSuccess: function (response, textStatus, jQxhr) {
+    this.set('fileContent', this.get('fileContent') + response.data);
+    this.set('fileFetchFinished', response.isFileEnd);
+    this.set('isLoading', false);
+  },
+
+  _fetchError: function (jQxhr, textStatus, errorThrown) {
+    console.log("Preview Fail pagecontent: " + errorThrown);
+    this.set('hasError', true);
+    this.set('isLoading', false);
+  },
+
+  download: function (event) {
+    this.get('filesDownloadService').download();
+  }
+
+});
+

+ 54 - 0
contrib/views/files/src/main/resources/ui/app/services/file-rename.js

@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import FileOperationMixin from '../mixins/file-operation';
+
+export default Ember.Service.extend(FileOperationMixin, {
+  logger: Ember.inject.service('alert-messages'),
+
+  // Returns a promise for the operation. Upon sucess or error, this also
+  // appropriately sends error messages.
+  rename: function(srcPath, destName) {
+    return new Ember.RSVP.Promise((resolve, reject) => {
+      var basePath = this.getBaseDirPath(srcPath);
+      var destPath = basePath + destName;
+      if(this._isDestinationPathExists(destPath)) {
+        var error = {success: false, message: `${destPath} already exists`, retry: true};
+        return reject(error);
+      }
+
+      var adapter = this.get('store').adapterFor('file');
+      var baseURL = adapter.buildURL('file');
+      var renameUrl = baseURL.substring(0, baseURL.lastIndexOf('/')) + "/rename";
+      var data = {src: srcPath, dst: destPath};
+      adapter.ajax(renameUrl, "POST", {data: data}).then((response) => {
+        this.get('logger').success(`Successfully renamed ${srcPath} to ${destPath}.`, {}, {flashOnly: true});
+        resolve(response);
+      }, (responseError) => {
+        var error = this.extractError(responseError);
+        this.get('logger').danger(`Failed to rename ${srcPath} to ${destPath}`, error);
+        reject(error);
+      });
+    });
+  },
+
+  _isDestinationPathExists(destinationPath) {
+    return this.get('store').peekAll('file').isAny('path', destinationPath);
+  }
+});

+ 157 - 0
contrib/views/files/src/main/resources/ui/app/services/files-download.js

@@ -0,0 +1,157 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+import FileOperationMixin from '../mixins/file-operation';
+
+export default Ember.Service.extend(FileOperationMixin, {
+  fileSelectionService: Ember.inject.service('files-selection'),
+  logger: Ember.inject.service('alert-messages'),
+
+  download: function() {
+    var entries = this.get('fileSelectionService.files');
+    if(entries.length === 0) {
+      return this._downloadEmptyError();
+    } else if(entries.length === 1) {
+      return this._downloadSingle(entries);
+    } else {
+      return this._downloadMulti(entries);
+    }
+  },
+
+  concatenate: function() {
+    var entries = this.get('fileSelectionService.files');
+    if(entries.length === 0 || entries.length === 1) {
+      return this._concatenateNotPossibleError();
+    } else {
+      return this._concatenateFiles(entries);
+    }
+  },
+
+  _downloadSingle: function(entries) {
+    var entry = entries[0];
+    if(entry.get('isDirectory')) {
+      // There is no difference between downloading a single directory
+      // or multiple directories and file.
+      return this._downloadMulti(entries);
+    }
+    var adapter = this.get('store').adapterFor('file');
+    var data = {checkperm: true, path: entry.get('path')};
+    return new Ember.RSVP.Promise((resolve, reject) => {
+        adapter.ajax(this._getDownloadBrowseUrl(), "GET", {data: data}).then(
+          (response) => {
+            if(response.allowed) {
+              window.location.href = this._getDownloadUrl(entry.get('path'));
+              resolve();
+            }
+          }, (rejectResponse) => {
+            var error = this.extractError(rejectResponse);
+            this.get('logger').danger("Failed to download file.", error);
+            reject(error);
+          });
+    });
+  },
+
+  _downloadMulti: function(entries) {
+    var entryPaths = entries.map((entry) => {
+      return entry.get('path');
+    });
+    var data = {download: true, entries: entryPaths};
+    var adapter = this.get('store').adapterFor('file');
+    return new Ember.RSVP.Promise((resolve, reject) => {
+      adapter.ajax(this._getDownloadGenLinkUrl(), "POST", {data: data}).then(
+        (response) => {
+          var downloadZipLink = this._getDownloadZipUrl(response.requestId);
+          window.location.href = downloadZipLink;
+          resolve();
+        }, (rejectResponse) => {
+          //TODO: Need to do alerts and logging.
+          var error = this.extractError(rejectResponse);
+          this.get('logger').danger("Failed to download Zip.", error);
+          reject(error);
+        });
+    });
+  },
+
+  _concatenateFiles: function(entries) {
+    var entryPaths = entries.map((entry) => {
+      return entry.get('path');
+    });
+
+    var data = {download: true, entries: entryPaths};
+    var adapter = this.get('store').adapterFor('file');
+    return new Ember.RSVP.Promise((resolve, reject) => {
+      adapter.ajax(this._getConcatGenLinkUrl(), "POST", {data: data}).then(
+        (response) => {
+          var downloadConcatLink = this._getDownloadConcatUrl(response.requestId);
+          window.location.href = downloadConcatLink;
+          resolve();
+        }, (rejectResponse) => {
+          //TODO: Need to do alerts and logging.
+          var error = this.extractError(rejectResponse);
+          this.get('logger').danger("Failed to concatenate files.", error);
+          reject(error);
+        });
+    });
+  },
+
+  _downloadEmptyError: function() {
+    return new Ember.RSVP.Promise(function(resolve, reject) {
+      reject("No files to download.");
+    });
+  },
+  _concatenateNotPossibleError: function() {
+    return new Ember.RSVP.Promise(function(resolve, reject) {
+      reject("Cannot concatenate zero or single file.");
+    });
+  },
+
+  _getDownloadGenLinkUrl: function() {
+    var urlFragments = this._getBaseURLFragments();
+    return urlFragments.slice(0, urlFragments.length - 2).join('/') + "/download/zip/generate-link";
+  },
+
+  _getDownloadZipUrl: function(requestId) {
+    var genLinkUrl = this._getDownloadGenLinkUrl();
+    return genLinkUrl.substring(0, genLinkUrl.lastIndexOf('/')) + "?requestId=" + requestId;
+  },
+
+  _getDownloadBrowseUrl: function() {
+    var urlFragments = this._getBaseURLFragments();
+    return urlFragments.slice(0, urlFragments.length - 2).join('/') + "/download/browse";
+  },
+
+  _getDownloadUrl: function(path) {
+    return this._getDownloadBrowseUrl() + "?path=" + path + "&download=true";
+  },
+
+  _getConcatGenLinkUrl: function() {
+    var urlFragments = this._getBaseURLFragments();
+    return urlFragments.slice(0, urlFragments.length - 2).join('/') + "/download/concat/generate-link";
+  },
+
+  _getDownloadConcatUrl: function(requestId) {
+    var genLinkUrl = this._getConcatGenLinkUrl();
+    return genLinkUrl.substring(0, genLinkUrl.lastIndexOf('/')) + "?requestId=" + requestId;
+  },
+
+  _logError: function(message, error) {
+    this.get('logger').danger(message, error);
+  }
+
+});

+ 64 - 0
contrib/views/files/src/main/resources/ui/app/services/files-selection.js

@@ -0,0 +1,64 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import Ember from 'ember';
+
+export default Ember.Service.extend({
+  files: [],
+  lastFileSelected: null,
+  filesCount: Ember.computed('files.[]', function() {
+    return this.get('files').filterBy('isDirectory', false).length;
+  }),
+  folderCount: Ember.computed('files.[]', 'filesCount', function() {
+    return this.get('files.length') - this.get('filesCount');
+  }),
+
+  selectFiles: function(files) {
+    files.forEach((file) => {
+      file.set('isSelected', true);
+      this.get('files').pushObject(file);
+      this.set('lastFileSelected', file);
+    });
+  },
+
+  deselectFile: function(file) {
+
+    if (file.get('isSelected')) {
+        file.set('isSelected', false);
+    }
+
+    this.set('files', this.get('files').without(file));
+    if(file === this.get('lastFileSelected')) {
+      this.set('lastFileSelected', this.get('files').objectAt(this.get('files.length') - 1));
+    }
+
+  },
+
+  deselectAll: function() {
+    this.get('files').forEach((file) => {
+      file.set('isSelected', false);
+    });
+    this.set('files', []);
+    this.set('lastFileSelected');
+  },
+
+  reset: function() {
+    this.set('files', []);
+    this.set('lastFileSelected');
+  }
+});

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff