]> git.ipfire.org Git - thirdparty/paperless-ngx.git/commitdiff
Enhancement: dashboard improvements, drag-n-drop reorder dashboard views (#4252)
authorshamoon <4887959+shamoon@users.noreply.github.com>
Thu, 28 Sep 2023 17:18:12 +0000 (10:18 -0700)
committerGitHub <noreply@github.com>
Thu, 28 Sep 2023 17:18:12 +0000 (10:18 -0700)
* Updated dashboard

* Make entire screen dropzone on dashboard too

* Floating upload widget status alerts

* Visual tweaks: spacing, borders

* Better empty view widget

* Support drag + drop reorder of dashboard saved views

* Update messages.xlf

* Disable dashbaord dnd if global dnd active

* Remove ngx-file-drop dep, rebuild file-drop & upload files widget

* Revert custom file drop implementation

* Try patch-package fix

* Simplify dropzone transitions to make more reliable

* Update messages.xlf

* Update dashboard.spec.ts

* Fix coverage

45 files changed:
src-ui/e2e/dashboard/dashboard.spec.ts
src-ui/messages.xlf
src-ui/package-lock.json
src-ui/package.json
src-ui/patches/ngx-file-drop+16.0.0.patch [new file with mode: 0644]
src-ui/src/app/app.component.html
src-ui/src/app/app.component.spec.ts
src-ui/src/app/app.component.ts
src-ui/src/app/app.module.ts
src-ui/src/app/components/app-frame/app-frame.component.html
src-ui/src/app/components/app-frame/app-frame.component.scss
src-ui/src/app/components/common/page-header/page-header.component.html
src-ui/src/app/components/common/page-header/page-header.component.spec.ts
src-ui/src/app/components/dashboard/dashboard.component.html
src-ui/src/app/components/dashboard/dashboard.component.spec.ts
src-ui/src/app/components/dashboard/dashboard.component.ts
src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html
src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.scss
src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.spec.ts
src-ui/src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.ts
src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html
src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.spec.ts
src-ui/src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts
src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html
src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.spec.ts
src-ui/src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts
src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html
src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.scss
src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.spec.ts
src-ui/src/app/components/dashboard/widgets/widget-frame/widget-frame.component.ts
src-ui/src/app/components/document-list/document-list.component.html
src-ui/src/app/components/file-drop/file-drop.component.html [new file with mode: 0644]
src-ui/src/app/components/file-drop/file-drop.component.scss [new file with mode: 0644]
src-ui/src/app/components/file-drop/file-drop.component.spec.ts [new file with mode: 0644]
src-ui/src/app/components/file-drop/file-drop.component.ts [new file with mode: 0644]
src-ui/src/app/data/paperless-uisettings.ts
src-ui/src/app/services/consumer-status.service.ts
src-ui/src/app/services/settings.service.spec.ts
src-ui/src/app/services/settings.service.ts
src-ui/src/app/services/upload-documents.service.spec.ts
src-ui/src/app/services/upload-documents.service.ts
src-ui/src/styles.scss
src-ui/src/theme.scss
src/documents/tests/test_api.py
src/documents/views.py

index 34bbc4949bc127e58231f9d94050608004002159..6a06eaceeb9d61f069a957b3babfab42e6dda897 100644 (file)
@@ -29,6 +29,7 @@ test('dashboard saved view show all', async ({ page }) => {
     .locator('pngx-widget-frame')
     .filter({ hasText: 'Inbox' })
     .getByRole('link', { name: 'Show all' })
+    .first()
     .click()
   await expect(page).toHaveURL(/view\/7/)
   await expect(page.locator('pngx-document-list')).toHaveText(/8 documents/)
index d75830e5af8caeeb025294b5301216b6ac2e534e..e5e3ab76520471ca3800487bbee020c2e622fc49 100644 (file)
           <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="7103632680753685326" datatype="html">
-        <source>Drop files to begin upload</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/app.component.html</context>
-          <context context-type="linenumber">7</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="9103526311244275943" datatype="html">
         <source>Document added</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">90</context>
+          <context context-type="linenumber">83</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">100</context>
+          <context context-type="linenumber">93</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9204248378636247318" datatype="html">
         <source>Document <x id="PH" equiv-text="status.filename"/> was added to paperless.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">92</context>
+          <context context-type="linenumber">85</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">102</context>
+          <context context-type="linenumber">95</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1931214133925051574" datatype="html">
         <source>Open document</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">93</context>
+          <context context-type="linenumber">86</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
-          <context context-type="linenumber">46</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8582620835547864448" datatype="html">
         <source>Could not add <x id="PH" equiv-text="status.filename"/>: <x id="PH_1" equiv-text="status.message"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">116</context>
+          <context context-type="linenumber">109</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1710712016675379662" datatype="html">
         <source>New document detected</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">131</context>
+          <context context-type="linenumber">124</context>
         </context-group>
       </trans-unit>
       <trans-unit id="587031278561344416" datatype="html">
         <source>Document <x id="PH" equiv-text="status.filename"/> is being processed by paperless.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">133</context>
+          <context context-type="linenumber">126</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2501522447884928778" datatype="html">
         <source>Prev</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">138</context>
+          <context context-type="linenumber">131</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3885497195825665706" datatype="html">
         <source>Next</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">139</context>
+          <context context-type="linenumber">132</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
         <source>End</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">140</context>
+          <context context-type="linenumber">133</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3909462337752654810" datatype="html">
         <source>The dashboard can be used to show saved views, such as an &apos;Inbox&apos;. Those settings are found under Settings &gt; Saved Views once you have created some.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">146</context>
+          <context context-type="linenumber">139</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9075755296812854717" datatype="html">
         <source>Drag-and-drop documents here to start uploading or place them in the consume folder. You can also drag-and-drop documents anywhere on all other pages of the web app. Once you do, Paperless-ngx will start training its machine learning algorithms.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">153</context>
+          <context context-type="linenumber">146</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7495498057594070122" datatype="html">
         <source>The documents list shows all of your documents and allows for filtering as well as bulk-editing. There are three different view styles: list, small cards and large cards. A list of documents currently opened for editing is shown in the sidebar.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">158</context>
+          <context context-type="linenumber">151</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1334220418719920556" datatype="html">
         <source>The filtering tools allow you to quickly find documents using various searches, dates, tags, etc.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">165</context>
+          <context context-type="linenumber">158</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5427326625898532358" datatype="html">
         <source>Any combination of filters can be saved as a &apos;view&apos; which can then be displayed on the dashboard and / or sidebar.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">171</context>
+          <context context-type="linenumber">164</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2804886236408698479" datatype="html">
         <source>Tags, correspondents, document types and storage paths can all be managed using these pages. They can also be created from the document edit view.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">176</context>
+          <context context-type="linenumber">169</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7851939076947092983" datatype="html">
         <source>Manage e-mail accounts and rules for automatically importing documents.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">184</context>
+          <context context-type="linenumber">177</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1347174817382304718" datatype="html">
         <source>Consumption templates give you finer control over the document ingestion process.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">192</context>
+          <context context-type="linenumber">185</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4680387114119209483" datatype="html">
         <source>File Tasks shows you documents that have been consumed, are waiting to be, or may have failed during the process.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">200</context>
+          <context context-type="linenumber">193</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1453710303796913192" datatype="html">
         <source>Check out the settings for various tweaks to the web app and toggle settings for saved views.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">208</context>
+          <context context-type="linenumber">201</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7172877665285340082" datatype="html">
         <source>Thank you! 🙏</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">209</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7354947513482088740" datatype="html">
         <source>There are &lt;em&gt;tons&lt;/em&gt; more features and info we didn&apos;t cover here, but this should get you started. Check out the documentation or visit the project on GitHub to learn more or to report issues.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">218</context>
+          <context context-type="linenumber">211</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4270528545616947218" datatype="html">
         <source>Lastly, on behalf of every contributor to this community-supported project, thank you for using Paperless-ngx!</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">220</context>
-        </context-group>
-      </trans-unit>
-      <trans-unit id="5749300816154614125" datatype="html">
-        <source>Initiating upload...</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/app.component.ts</context>
-          <context context-type="linenumber">289</context>
+          <context context-type="linenumber">213</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4804785061014590286" datatype="html">
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.html</context>
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">15</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/widget-frame/widget-frame.component.html</context>
-          <context context-type="linenumber">7</context>
+          <context context-type="linenumber">21</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
-          <context context-type="linenumber">9</context>
+          <context context-type="linenumber">17</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
-          <context context-type="linenumber">17</context>
+          <context context-type="linenumber">27</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/document-card-large/document-card-large.component.html</context>
           <context context-type="sourcefile">src/app/components/app-frame/app-frame.component.html</context>
           <context context-type="linenumber">140</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
+          <context context-type="linenumber">55</context>
+        </context-group>
       </trans-unit>
       <trans-unit id="7886570921510760899" datatype="html">
         <source>Tags</source>
           <context context-type="sourcefile">src/app/components/common/input/tags/tags.component.ts</context>
           <context context-type="linenumber">63</context>
         </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
+          <context context-type="linenumber">19</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
+          <context context-type="linenumber">49</context>
+        </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
           <context context-type="linenumber">26</context>
         <source>Hello <x id="PH" equiv-text="this.settingsService.displayName"/>, welcome to Paperless-ngx</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
-          <context context-type="linenumber">23</context>
+          <context context-type="linenumber">48</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5334686081082652461" datatype="html">
         <source>Welcome to Paperless-ngx</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
-          <context context-type="linenumber">25</context>
+          <context context-type="linenumber">50</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="1325877348738783391" datatype="html">
+        <source>Dashboard updated</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
+          <context context-type="linenumber">73</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="3214475953924351473" datatype="html">
+        <source>Error updating dashboard</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/dashboard/dashboard.component.ts</context>
+          <context context-type="linenumber">76</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2946624699882754313" datatype="html">
         <source>Show all</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
-          <context context-type="linenumber">3</context>
+          <context context-type="linenumber">12</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
-          <context context-type="linenumber">27</context>
+          <context context-type="linenumber">29</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5701618810648052610" datatype="html">
         <source>Title</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
-          <context context-type="linenumber">10</context>
+          <context context-type="linenumber">18</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
           <context context-type="linenumber">20</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="2691296884221415710" datatype="html">
+        <source>Correspondent</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
+          <context context-type="linenumber">20</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
+          <context context-type="linenumber">89</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
+          <context context-type="linenumber">38</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
+          <context context-type="linenumber">142</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
+          <context context-type="linenumber">35</context>
+        </context-group>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
+          <context context-type="linenumber">19</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="8911158217491828773" datatype="html">
         <source>View Preview</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
-          <context context-type="linenumber">19</context>
+          <context context-type="linenumber">35</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3099741642167775297" datatype="html">
         <source>Download</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
-          <context context-type="linenumber">29</context>
+          <context context-type="linenumber">45</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
           <context context-type="linenumber">99</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="872092479747931526" datatype="html">
+        <source>No documents</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/dashboard/widgets/saved-view-widget/saved-view-widget.component.html</context>
+          <context context-type="linenumber">57</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="1069523139277190436" datatype="html">
         <source>Statistics</source>
         <context-group purpose="location">
           <context context-type="linenumber">13</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="4369111787961525769" datatype="html">
+        <source>Document Types</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
+          <context context-type="linenumber">61</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5421255270838137624" datatype="html">
+        <source>Storage Paths</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.html</context>
+          <context context-type="linenumber">67</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="8693603235657020323" datatype="html">
         <source>Other</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/statistics-widget/statistics-widget.component.ts</context>
-          <context context-type="linenumber">55</context>
+          <context context-type="linenumber">65</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8187573012244728580" datatype="html">
           <context context-type="linenumber">1</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="1749180330008942007" datatype="html">
-        <source>Dismiss completed</source>
+      <trans-unit id="8161815301131859114" datatype="html">
+        <source>Drop documents anywhere or</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
           <context context-type="linenumber">4</context>
         </context-group>
-        <note priority="1" from="description">This button dismisses all status messages about processed documents on the dashboard (failed and successful)</note>
       </trans-unit>
-      <trans-unit id="118343233500414755" datatype="html">
-        <source>Drop documents here or</source>
+      <trans-unit id="8133800334834354642" datatype="html">
+        <source>Browse files</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
-          <context context-type="linenumber">13</context>
+          <context context-type="linenumber">5</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="8133800334834354642" datatype="html">
-        <source>Browse files</source>
+      <trans-unit id="1749180330008942007" datatype="html">
+        <source>Dismiss completed</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
           <context context-type="linenumber">13</context>
         </context-group>
+        <note priority="1" from="description">This button dismisses all status messages about processed documents on the dashboard (failed and successful)</note>
       </trans-unit>
       <trans-unit id="2330646618997399019" datatype="html">
         <source>{VAR_PLURAL, plural, =1 {One more document} other {<x id="INTERPOLATION"/> more documents}}</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.html</context>
-          <context context-type="linenumber">25</context>
+          <context context-type="linenumber">27</context>
         </context-group>
         <note priority="1" from="description">This is shown as a summary line when there are more than 5 document in the processing pipeline.</note>
       </trans-unit>
         <source>Processing: <x id="PH" equiv-text="countUploadingAndProcessing"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
-          <context context-type="linenumber">39</context>
+          <context context-type="linenumber">44</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9182918211699394982" datatype="html">
         <source>Failed: <x id="PH" equiv-text="countFailed"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
-          <context context-type="linenumber">42</context>
+          <context context-type="linenumber">47</context>
         </context-group>
       </trans-unit>
       <trans-unit id="534116346205124059" datatype="html">
         <source>Added: <x id="PH" equiv-text="countSuccess"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
-          <context context-type="linenumber">45</context>
+          <context context-type="linenumber">50</context>
         </context-group>
       </trans-unit>
       <trans-unit id="760986369763309193" datatype="html">
         <source>, </source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/dashboard/widgets/upload-file-widget/upload-file-widget.component.ts</context>
-          <context context-type="linenumber">48</context>
+          <context context-type="linenumber">53</context>
         </context-group>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.ts</context>
           <context context-type="linenumber">87</context>
         </context-group>
       </trans-unit>
-      <trans-unit id="2691296884221415710" datatype="html">
-        <source>Correspondent</source>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/document-detail/document-detail.component.html</context>
-          <context context-type="linenumber">89</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/document-list/bulk-editor/bulk-editor.component.html</context>
-          <context context-type="linenumber">38</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/document-list/document-list.component.html</context>
-          <context context-type="linenumber">142</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/components/document-list/filter-editor/filter-editor.component.html</context>
-          <context context-type="linenumber">35</context>
-        </context-group>
-        <context-group purpose="location">
-          <context context-type="sourcefile">src/app/services/rest/document.service.ts</context>
-          <context context-type="linenumber">19</context>
-        </context-group>
-      </trans-unit>
       <trans-unit id="5066119607229701477" datatype="html">
         <source>Document type</source>
         <context-group purpose="location">
           <context context-type="linenumber">80</context>
         </context-group>
       </trans-unit>
+      <trans-unit id="7103632680753685326" datatype="html">
+        <source>Drop files to begin upload</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/file-drop/file-drop.component.html</context>
+          <context context-type="linenumber">6</context>
+        </context-group>
+      </trans-unit>
+      <trans-unit id="5749300816154614125" datatype="html">
+        <source>Initiating upload...</source>
+        <context-group purpose="location">
+          <context context-type="sourcefile">src/app/components/file-drop/file-drop.component.ts</context>
+          <context context-type="linenumber">87</context>
+        </context-group>
+      </trans-unit>
       <trans-unit id="7308826808299076537" datatype="html">
         <source>Add Template</source>
         <context-group purpose="location">
         <source>English (US)</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">150</context>
+          <context context-type="linenumber">154</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7318555235181361185" datatype="html">
         <source>Afrikaans</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">156</context>
+          <context context-type="linenumber">160</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6269202464699193298" datatype="html">
         <source>Arabic</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">162</context>
+          <context context-type="linenumber">166</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3098941349689899577" datatype="html">
         <source>Belarusian</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">168</context>
+          <context context-type="linenumber">172</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1001043467371963032" datatype="html">
         <source>Catalan</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">174</context>
+          <context context-type="linenumber">178</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2719780722934172508" datatype="html">
         <source>Czech</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">180</context>
+          <context context-type="linenumber">184</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2924289692679201020" datatype="html">
         <source>Danish</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">186</context>
+          <context context-type="linenumber">190</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1858110241312746425" datatype="html">
         <source>German</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">192</context>
+          <context context-type="linenumber">196</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7067741492320440272" datatype="html">
         <source>Greek</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">198</context>
+          <context context-type="linenumber">202</context>
         </context-group>
       </trans-unit>
       <trans-unit id="6987083569809053351" datatype="html">
         <source>English (GB)</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">204</context>
+          <context context-type="linenumber">208</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5190825892106392539" datatype="html">
         <source>Spanish</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">210</context>
+          <context context-type="linenumber">214</context>
         </context-group>
       </trans-unit>
       <trans-unit id="861663369293303028" datatype="html">
         <source>Finnish</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">216</context>
+          <context context-type="linenumber">220</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7633754075223722162" datatype="html">
         <source>French</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">222</context>
+          <context context-type="linenumber">226</context>
         </context-group>
       </trans-unit>
       <trans-unit id="2935232983274991580" datatype="html">
         <source>Italian</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">228</context>
+          <context context-type="linenumber">232</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1334425850005897370" datatype="html">
         <source>Luxembourgish</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">234</context>
+          <context context-type="linenumber">238</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3071065188816255493" datatype="html">
         <source>Dutch</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">240</context>
+          <context context-type="linenumber">244</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8069284467804715623" datatype="html">
         <source>Norwegian</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">246</context>
+          <context context-type="linenumber">250</context>
         </context-group>
       </trans-unit>
       <trans-unit id="792060551707690640" datatype="html">
         <source>Polish</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">252</context>
+          <context context-type="linenumber">256</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9184513005098760425" datatype="html">
         <source>Portuguese (Brazil)</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">258</context>
+          <context context-type="linenumber">262</context>
         </context-group>
       </trans-unit>
       <trans-unit id="153799456510623899" datatype="html">
         <source>Portuguese</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">264</context>
+          <context context-type="linenumber">268</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8118856427047826368" datatype="html">
         <source>Romanian</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">270</context>
+          <context context-type="linenumber">274</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7137419789978325708" datatype="html">
         <source>Russian</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">276</context>
+          <context context-type="linenumber">280</context>
         </context-group>
       </trans-unit>
       <trans-unit id="9102963095355753902" datatype="html">
         <source>Slovak</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">282</context>
+          <context context-type="linenumber">286</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4287008301409320881" datatype="html">
         <source>Slovenian</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">288</context>
+          <context context-type="linenumber">292</context>
         </context-group>
       </trans-unit>
       <trans-unit id="8608389829607915090" datatype="html">
         <source>Serbian</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">294</context>
+          <context context-type="linenumber">298</context>
         </context-group>
       </trans-unit>
       <trans-unit id="499386805970351976" datatype="html">
         <source>Swedish</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">300</context>
+          <context context-type="linenumber">304</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5682359291233237791" datatype="html">
         <source>Turkish</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">306</context>
+          <context context-type="linenumber">310</context>
         </context-group>
       </trans-unit>
       <trans-unit id="3578644052206125685" datatype="html">
         <source>Ukrainian</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">312</context>
+          <context context-type="linenumber">316</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4689443708886954687" datatype="html">
         <source>Chinese Simplified</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">318</context>
+          <context context-type="linenumber">322</context>
         </context-group>
       </trans-unit>
       <trans-unit id="4912706592792948707" datatype="html">
         <source>ISO 8601</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">335</context>
+          <context context-type="linenumber">339</context>
         </context-group>
       </trans-unit>
       <trans-unit id="313643372755303297" datatype="html">
         <source>Successfully completed one-time migratration of settings to the database!</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">454</context>
+          <context context-type="linenumber">458</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5558341108007064934" datatype="html">
         <source>Unable to migrate settings to the database, please try saving manually.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">455</context>
+          <context context-type="linenumber">459</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1168781785897678748" datatype="html">
         <source>You can restart the tour from the settings page.</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/settings.service.ts</context>
-          <context context-type="linenumber">529</context>
+          <context context-type="linenumber">533</context>
         </context-group>
       </trans-unit>
       <trans-unit id="5037437391296624618" datatype="html">
         <source>Connecting...</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/upload-documents.service.ts</context>
-          <context context-type="linenumber">31</context>
+          <context context-type="linenumber">42</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1245343823699368872" datatype="html">
         <source>Uploading...</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/upload-documents.service.ts</context>
-          <context context-type="linenumber">43</context>
+          <context context-type="linenumber">54</context>
         </context-group>
       </trans-unit>
       <trans-unit id="7446520539098045935" datatype="html">
         <source>Upload complete, waiting...</source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/upload-documents.service.ts</context>
-          <context context-type="linenumber">46</context>
+          <context context-type="linenumber">57</context>
         </context-group>
       </trans-unit>
       <trans-unit id="1405142710727603568" datatype="html">
         <source>HTTP error: <x id="PH" equiv-text="error.status"/> <x id="PH_1" equiv-text="error.statusText"/></source>
         <context-group purpose="location">
           <context context-type="sourcefile">src/app/services/upload-documents.service.ts</context>
-          <context context-type="linenumber">62</context>
+          <context context-type="linenumber">70</context>
         </context-group>
       </trans-unit>
     </body>
index 16d74407e85d2fa06182d1ae9a80f021c1ef71f3..43b8e056c23fb182bbfc8d6fb638f130fd33311c 100644 (file)
@@ -7,6 +7,7 @@
     "": {
       "name": "paperless-ui",
       "version": "0.0.0",
+      "hasInstallScript": true,
       "dependencies": {
         "@angular/common": "~16.2.6",
         "@angular/compiler": "~16.2.6",
@@ -27,6 +28,7 @@
         "ngx-clipboard": "^16.0.0",
         "ngx-color": "^9.0.0",
         "ngx-cookie-service": "^16.0.1",
+        "ngx-drag-drop": "^16.1.0",
         "ngx-file-drop": "^16.0.0",
         "ngx-ui-tour-ng-bootstrap": "^13.0.4",
         "rxjs": "^7.8.1",
@@ -55,6 +57,7 @@
         "jest-environment-jsdom": "^29.7.0",
         "jest-preset-angular": "^13.1.1",
         "jest-websocket-mock": "^2.5.0",
+        "patch-package": "^8.0.0",
         "ts-node": "~10.9.1",
         "typescript": "^5.1.6",
         "wait-on": "^7.0.1"
       "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
       "dev": true
     },
+    "node_modules/at-least-node": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+      "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 4.0.0"
+      }
+    },
     "node_modules/autoprefixer": {
       "version": "10.4.14",
       "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz",
         "node": ">=8"
       }
     },
+    "node_modules/find-yarn-workspace-root": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz",
+      "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==",
+      "dev": true,
+      "dependencies": {
+        "micromatch": "^4.0.2"
+      }
+    },
     "node_modules/flat": {
       "version": "5.0.2",
       "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
       "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
       "dev": true
     },
+    "node_modules/json-stable-stringify": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.2.tgz",
+      "integrity": "sha512-eunSSaEnxV12z+Z73y/j5N37/In40GK4GmsSy+tEHJMxknvqnA7/djeYtAgW0GsWHUfg+847WJjKaEylk2y09g==",
+      "dev": true,
+      "dependencies": {
+        "jsonify": "^0.0.1"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/json-stable-stringify-without-jsonify": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
         "graceful-fs": "^4.1.6"
       }
     },
+    "node_modules/jsonify": {
+      "version": "0.0.1",
+      "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.1.tgz",
+      "integrity": "sha512-2/Ki0GcmuqSrgFyelQq9M05y7PS0mEwuIzrf3f1fPqkVDVRvZrPZtVSMHxdgo8Aq0sxAOb/cr2aqqA3LeWHVPg==",
+      "dev": true,
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
     "node_modules/jsonparse": {
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz",
         "node": ">=0.10.0"
       }
     },
+    "node_modules/klaw-sync": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz",
+      "integrity": "sha512-nIeuVSzdCCs6TDPTqI8w1Yre34sSq7AkZ4B3sfOBbI2CgVSB4Du4aLQijFU2+lhAFCwt9+42Hel6lQNIv6AntQ==",
+      "dev": true,
+      "dependencies": {
+        "graceful-fs": "^4.1.11"
+      }
+    },
     "node_modules/kleur": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
         "@angular/core": "^16.0.0"
       }
     },
+    "node_modules/ngx-drag-drop": {
+      "version": "16.1.0",
+      "resolved": "https://registry.npmjs.org/ngx-drag-drop/-/ngx-drag-drop-16.1.0.tgz",
+      "integrity": "sha512-y2l9pJGD7OupsIRkCElN/JqTgzjg2V9ZxymKGQR7ZjjcdjaP1wKkiFWIgVEvLNtb8wgm10U+9tkGwLClGaHkQA==",
+      "dependencies": {
+        "tslib": "^2.3.0"
+      },
+      "peerDependencies": {
+        "@angular/common": "^16.0.0",
+        "@angular/core": "^16.0.0"
+      }
+    },
     "node_modules/ngx-file-drop": {
       "version": "16.0.0",
       "resolved": "https://registry.npmjs.org/ngx-file-drop/-/ngx-file-drop-16.0.0.tgz",
         "node": ">= 0.8"
       }
     },
+    "node_modules/patch-package": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/patch-package/-/patch-package-8.0.0.tgz",
+      "integrity": "sha512-da8BVIhzjtgScwDJ2TtKsfT5JFWz1hYoBl9rUQ1f38MC2HwnEIkK8VN3dKMKcP7P7bvvgzNDbfNHtx3MsQb5vA==",
+      "dev": true,
+      "dependencies": {
+        "@yarnpkg/lockfile": "^1.1.0",
+        "chalk": "^4.1.2",
+        "ci-info": "^3.7.0",
+        "cross-spawn": "^7.0.3",
+        "find-yarn-workspace-root": "^2.0.0",
+        "fs-extra": "^9.0.0",
+        "json-stable-stringify": "^1.0.2",
+        "klaw-sync": "^6.0.0",
+        "minimist": "^1.2.6",
+        "open": "^7.4.2",
+        "rimraf": "^2.6.3",
+        "semver": "^7.5.3",
+        "slash": "^2.0.0",
+        "tmp": "^0.0.33",
+        "yaml": "^2.2.2"
+      },
+      "bin": {
+        "patch-package": "index.js"
+      },
+      "engines": {
+        "node": ">=14",
+        "npm": ">5"
+      }
+    },
+    "node_modules/patch-package/node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dev": true,
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/patch-package/node_modules/chalk": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+      "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+      "dev": true,
+      "dependencies": {
+        "ansi-styles": "^4.1.0",
+        "supports-color": "^7.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/chalk?sponsor=1"
+      }
+    },
+    "node_modules/patch-package/node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dev": true,
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/patch-package/node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+      "dev": true
+    },
+    "node_modules/patch-package/node_modules/fs-extra": {
+      "version": "9.1.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+      "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+      "dev": true,
+      "dependencies": {
+        "at-least-node": "^1.0.0",
+        "graceful-fs": "^4.2.0",
+        "jsonfile": "^6.0.1",
+        "universalify": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/patch-package/node_modules/glob": {
+      "version": "7.2.3",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+      "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+      "dev": true,
+      "dependencies": {
+        "fs.realpath": "^1.0.0",
+        "inflight": "^1.0.4",
+        "inherits": "2",
+        "minimatch": "^3.1.1",
+        "once": "^1.3.0",
+        "path-is-absolute": "^1.0.0"
+      },
+      "engines": {
+        "node": "*"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/patch-package/node_modules/has-flag": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+      "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/patch-package/node_modules/open": {
+      "version": "7.4.2",
+      "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
+      "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
+      "dev": true,
+      "dependencies": {
+        "is-docker": "^2.0.0",
+        "is-wsl": "^2.1.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/patch-package/node_modules/rimraf": {
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
+      "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
+      "dev": true,
+      "dependencies": {
+        "glob": "^7.1.3"
+      },
+      "bin": {
+        "rimraf": "bin.js"
+      }
+    },
+    "node_modules/patch-package/node_modules/slash": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz",
+      "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/patch-package/node_modules/supports-color": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+      "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+      "dev": true,
+      "dependencies": {
+        "has-flag": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/patch-package/node_modules/tmp": {
+      "version": "0.0.33",
+      "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+      "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+      "dev": true,
+      "dependencies": {
+        "os-tmpdir": "~1.0.2"
+      },
+      "engines": {
+        "node": ">=0.6.0"
+      }
+    },
     "node_modules/path-exists": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
       "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
       "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
     },
+    "node_modules/yaml": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.2.tgz",
+      "integrity": "sha512-N/lyzTPaJasoDmfV7YTrYCI0G/3ivm/9wdG0aHuheKowWQwGTsK0Eoiw6utmzAnI6pkJa0DUVygvp3spqqEKXg==",
+      "dev": true,
+      "engines": {
+        "node": ">= 14"
+      }
+    },
     "node_modules/yargs": {
       "version": "17.7.2",
       "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
index d70a5966696d3f16ef8c6a6b97dcd414090a1d84..d0f4af1af5fe7d9c5fa4e77c1cc2f8081b58ec81 100644 (file)
@@ -6,7 +6,8 @@
     "start": "ng serve",
     "build": "ng build",
     "test": "ng test --no-watch --coverage",
-    "lint": "ng lint"
+    "lint": "ng lint",
+    "postinstall": "patch-package"
   },
   "private": true,
   "dependencies": {
@@ -29,6 +30,7 @@
     "ngx-clipboard": "^16.0.0",
     "ngx-color": "^9.0.0",
     "ngx-cookie-service": "^16.0.1",
+    "ngx-drag-drop": "^16.1.0",
     "ngx-file-drop": "^16.0.0",
     "ngx-ui-tour-ng-bootstrap": "^13.0.4",
     "rxjs": "^7.8.1",
@@ -57,6 +59,7 @@
     "jest-environment-jsdom": "^29.7.0",
     "jest-preset-angular": "^13.1.1",
     "jest-websocket-mock": "^2.5.0",
+    "patch-package": "^8.0.0",
     "ts-node": "~10.9.1",
     "typescript": "^5.1.6",
     "wait-on": "^7.0.1"
diff --git a/src-ui/patches/ngx-file-drop+16.0.0.patch b/src-ui/patches/ngx-file-drop+16.0.0.patch
new file mode 100644 (file)
index 0000000..759ea75
--- /dev/null
@@ -0,0 +1,206 @@
+diff --git a/node_modules/ngx-file-drop/esm2022/lib/dom.types.mjs b/node_modules/ngx-file-drop/esm2022/lib/dom.types.mjs
+index 825469b..85bdc89 100644
+--- a/node_modules/ngx-file-drop/esm2022/lib/dom.types.mjs
++++ b/node_modules/ngx-file-drop/esm2022/lib/dom.types.mjs
+@@ -1,2 +1,4 @@
+-export {};
+-//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZG9tLnR5cGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvbmd4LWZpbGUtZHJvcC9zcmMvbGliL2RvbS50eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiXHJcbmV4cG9ydCBpbnRlcmZhY2UgRmlsZVN5c3RlbUVudHJ5IHtcclxuICBuYW1lOiBzdHJpbmcsXHJcbiAgaXNEaXJlY3Rvcnk6IGJvb2xlYW5cclxuICBpc0ZpbGU6IGJvb2xlYW5cclxufVxyXG5cclxuZXhwb3J0IGludGVyZmFjZSBGaWxlU3lzdGVtRW50cnlNZXRhZGF0YSB7XHJcbiAgbW9kaWZpY2F0aW9uVGltZT86IERhdGUsXHJcbiAgc2l6ZT86IG51bWJlclxyXG59XHJcblxyXG5leHBvcnQgaW50ZXJmYWNlIEZpbGVTeXN0ZW1EaXJlY3RvcnlSZWFkZXIge1xyXG4gIHJlYWRFbnRyaWVzKFxyXG4gICAgc3VjY2Vzc0NhbGxiYWNrOiAocmVzdWx0OiBGaWxlU3lzdGVtRW50cnlbXSkgPT4gdm9pZCxcclxuICApOiB2b2lkXHJcbn1cclxuXHJcbmV4cG9ydCBpbnRlcmZhY2UgRmlsZVN5c3RlbUZsYWdzIHtcclxuICBjcmVhdGU/OiBib29sZWFuXHJcbiAgZXhjbHVzaXZlPzogYm9vbGVhblxyXG59XHJcblxyXG5leHBvcnQgaW50ZXJmYWNlIEZpbGVTeXN0ZW1EaXJlY3RvcnlFbnRyeSBleHRlbmRzIEZpbGVTeXN0ZW1FbnRyeSB7XHJcbiAgaXNEaXJlY3Rvcnk6IHRydWVcclxuICBpc0ZpbGU6IGZhbHNlXHJcbiAgY3JlYXRlUmVhZGVyKCk6IEZpbGVTeXN0ZW1EaXJlY3RvcnlSZWFkZXJcclxufVxyXG5cclxuZXhwb3J0IGludGVyZmFjZSBGaWxlU3lzdGVtRmlsZUVudHJ5IGV4dGVuZHMgRmlsZVN5c3RlbUVudHJ5IHtcclxuICBpc0RpcmVjdG9yeTogZmFsc2VcclxuICBpc0ZpbGU6IHRydWVcclxuICBmaWxlPFQ+KGNhbGxiYWNrOiAoZmlsZTogRmlsZSkgPT4gVCk6IFRcclxufVxyXG4iXX0=
+\ No newline at end of file
++export function isDataTransferItem(item) {
++    return "webkitGetAsEntry" in item && "getAsFile" in item;
++}
++//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZG9tLnR5cGVzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vcHJvamVjdHMvbmd4LWZpbGUtZHJvcC9zcmMvbGliL2RvbS50eXBlcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFtQ0EsTUFBTSxVQUFVLGtCQUFrQixDQUFDLElBQTZCO0lBQzlELE9BQU8sa0JBQWtCLElBQUksSUFBSSxJQUFJLFdBQVcsSUFBSSxJQUFJLENBQUM7QUFDM0QsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIlxuZXhwb3J0IGludGVyZmFjZSBGaWxlU3lzdGVtRW50cnkge1xuICBuYW1lOiBzdHJpbmcsXG4gIGlzRGlyZWN0b3J5OiBib29sZWFuXG4gIGlzRmlsZTogYm9vbGVhblxufVxuXG5leHBvcnQgaW50ZXJmYWNlIEZpbGVTeXN0ZW1FbnRyeU1ldGFkYXRhIHtcbiAgbW9kaWZpY2F0aW9uVGltZT86IERhdGUsXG4gIHNpemU/OiBudW1iZXJcbn1cblxuZXhwb3J0IGludGVyZmFjZSBGaWxlU3lzdGVtRGlyZWN0b3J5UmVhZGVyIHtcbiAgcmVhZEVudHJpZXMoXG4gICAgc3VjY2Vzc0NhbGxiYWNrOiAocmVzdWx0OiBGaWxlU3lzdGVtRW50cnlbXSkgPT4gdm9pZCxcbiAgKTogdm9pZFxufVxuXG5leHBvcnQgaW50ZXJmYWNlIEZpbGVTeXN0ZW1GbGFncyB7XG4gIGNyZWF0ZT86IGJvb2xlYW5cbiAgZXhjbHVzaXZlPzogYm9vbGVhblxufVxuXG5leHBvcnQgaW50ZXJmYWNlIEZpbGVTeXN0ZW1EaXJlY3RvcnlFbnRyeSBleHRlbmRzIEZpbGVTeXN0ZW1FbnRyeSB7XG4gIGlzRGlyZWN0b3J5OiB0cnVlXG4gIGlzRmlsZTogZmFsc2VcbiAgY3JlYXRlUmVhZGVyKCk6IEZpbGVTeXN0ZW1EaXJlY3RvcnlSZWFkZXJcbn1cblxuZXhwb3J0IGludGVyZmFjZSBGaWxlU3lzdGVtRmlsZUVudHJ5IGV4dGVuZHMgRmlsZVN5c3RlbUVudHJ5IHtcbiAgaXNEaXJlY3Rvcnk6IGZhbHNlXG4gIGlzRmlsZTogdHJ1ZVxuICBmaWxlPFQ+KGNhbGxiYWNrOiAoZmlsZTogRmlsZSkgPT4gVCk6IFRcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGlzRGF0YVRyYW5zZmVySXRlbShpdGVtOiBEYXRhVHJhbnNmZXJJdGVtIHwgRmlsZSk6IGl0ZW0gaXMgRGF0YVRyYW5zZmVySXRlbSB7XG4gIHJldHVybiBcIndlYmtpdEdldEFzRW50cnlcIiBpbiBpdGVtICYmIFwiZ2V0QXNGaWxlXCIgaW4gaXRlbTtcbn1cbiJdfQ==
+\ No newline at end of file
+diff --git a/node_modules/ngx-file-drop/esm2022/lib/ngx-file-drop-entry.mjs b/node_modules/ngx-file-drop/esm2022/lib/ngx-file-drop-entry.mjs
+index 2cd97e8..c89ace2 100644
+--- a/node_modules/ngx-file-drop/esm2022/lib/ngx-file-drop-entry.mjs
++++ b/node_modules/ngx-file-drop/esm2022/lib/ngx-file-drop-entry.mjs
+@@ -9,4 +9,4 @@ export class NgxFileDropEntry {
+         this.fileEntry = fileEntry;
+     }
+ }
+-//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LWZpbGUtZHJvcC1lbnRyeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL25neC1maWxlLWRyb3Avc3JjL2xpYi9uZ3gtZmlsZS1kcm9wLWVudHJ5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBOzs7O0dBSUc7QUFDSCxNQUFNLE9BQU8sZ0JBQWdCO0lBQ3pCLFlBQ1csWUFBb0IsRUFDcEIsU0FBMEI7UUFEMUIsaUJBQVksR0FBWixZQUFZLENBQVE7UUFDcEIsY0FBUyxHQUFULFNBQVMsQ0FBaUI7SUFFckMsQ0FBQztDQUNKIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRmlsZVN5c3RlbUVudHJ5LCBGaWxlU3lzdGVtRmlsZUVudHJ5LCBGaWxlU3lzdGVtRGlyZWN0b3J5RW50cnkgfSBmcm9tICcuL2RvbS50eXBlcyc7XHJcblxyXG4vKipcclxuICogZmlsZUVudHJ5IGlzIGFuIGluc3RhbmNlIG9mIHtAbGluayBGaWxlU3lzdGVtRmlsZUVudHJ5fSBvciB7QGxpbmsgRmlsZVN5c3RlbURpcmVjdG9yeUVudHJ5fS5cclxuICogV2hpY2ggb25lIGlzIGl0IGNhbiBiZSBjaGVja2VkIHVzaW5nIHtAbGluayBGaWxlU3lzdGVtRW50cnkuaXNGaWxlfSBvciB7QGxpbmsgRmlsZVN5c3RlbUVudHJ5LmlzRGlyZWN0b3J5fVxyXG4gKiBwcm9wZXJ0aWVzIG9mIHRoZSBnaXZlbiB7QGxpbmsgRmlsZVN5c3RlbUVudHJ5fS5cclxuICovXHJcbmV4cG9ydCBjbGFzcyBOZ3hGaWxlRHJvcEVudHJ5IHtcclxuICAgIGNvbnN0cnVjdG9yKFxyXG4gICAgICAgIHB1YmxpYyByZWxhdGl2ZVBhdGg6IHN0cmluZyxcclxuICAgICAgICBwdWJsaWMgZmlsZUVudHJ5OiBGaWxlU3lzdGVtRW50cnlcclxuICAgICkge1xyXG4gICAgfVxyXG59XHJcbiJdfQ==
+\ No newline at end of file
++//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LWZpbGUtZHJvcC1lbnRyeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3Byb2plY3RzL25neC1maWxlLWRyb3Avc3JjL2xpYi9uZ3gtZmlsZS1kcm9wLWVudHJ5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUVBOzs7O0dBSUc7QUFDSCxNQUFNLE9BQU8sZ0JBQWdCO0lBQ3pCLFlBQ1csWUFBb0IsRUFDcEIsU0FBMEI7UUFEMUIsaUJBQVksR0FBWixZQUFZLENBQVE7UUFDcEIsY0FBUyxHQUFULFNBQVMsQ0FBaUI7SUFFckMsQ0FBQztDQUNKIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRmlsZVN5c3RlbUVudHJ5LCBGaWxlU3lzdGVtRmlsZUVudHJ5LCBGaWxlU3lzdGVtRGlyZWN0b3J5RW50cnkgfSBmcm9tICcuL2RvbS50eXBlcyc7XG5cbi8qKlxuICogZmlsZUVudHJ5IGlzIGFuIGluc3RhbmNlIG9mIHtAbGluayBGaWxlU3lzdGVtRmlsZUVudHJ5fSBvciB7QGxpbmsgRmlsZVN5c3RlbURpcmVjdG9yeUVudHJ5fS5cbiAqIFdoaWNoIG9uZSBpcyBpdCBjYW4gYmUgY2hlY2tlZCB1c2luZyB7QGxpbmsgRmlsZVN5c3RlbUVudHJ5LmlzRmlsZX0gb3Ige0BsaW5rIEZpbGVTeXN0ZW1FbnRyeS5pc0RpcmVjdG9yeX1cbiAqIHByb3BlcnRpZXMgb2YgdGhlIGdpdmVuIHtAbGluayBGaWxlU3lzdGVtRW50cnl9LlxuICovXG5leHBvcnQgY2xhc3MgTmd4RmlsZURyb3BFbnRyeSB7XG4gICAgY29uc3RydWN0b3IoXG4gICAgICAgIHB1YmxpYyByZWxhdGl2ZVBhdGg6IHN0cmluZyxcbiAgICAgICAgcHVibGljIGZpbGVFbnRyeTogRmlsZVN5c3RlbUVudHJ5XG4gICAgKSB7XG4gICAgfVxufVxuIl19
+\ No newline at end of file
+diff --git a/node_modules/ngx-file-drop/esm2022/lib/ngx-file-drop.component.mjs b/node_modules/ngx-file-drop/esm2022/lib/ngx-file-drop.component.mjs
+index 7ef2b4b..c180621 100644
+--- a/node_modules/ngx-file-drop/esm2022/lib/ngx-file-drop.component.mjs
++++ b/node_modules/ngx-file-drop/esm2022/lib/ngx-file-drop.component.mjs
+@@ -1,6 +1,7 @@
+ import { Component, ContentChild, EventEmitter, Input, Output, TemplateRef, ViewChild } from '@angular/core';
+ import { timer } from 'rxjs';
+ import { NgxFileDropEntry } from './ngx-file-drop-entry';
++import { isDataTransferItem } from './dom.types';
+ import { NgxFileDropContentTemplateDirective } from './ngx-templates.directive';
+ import * as i0 from "@angular/core";
+ import * as i1 from "@angular/common";
+@@ -134,17 +135,8 @@ export class NgxFileDropComponent {
+         if (!item) {
+             return;
+         }
+-        // if ("getAsFile" in item) {
+-        //   const file = item.getAsFile();
+-        //   if (file) {
+-        //     this.addToQueue(
+-        //       this.getFakeDropEntry(file)
+-        //     );
+-        //     return;
+-        //   }
+-        // }
+-        if ("webkitGetAsEntry" in item) {
+-            let entry = item.webkitGetAsEntry();
++        if (isDataTransferItem(item)) {
++            const entry = item.webkitGetAsEntry();
+             if (entry) {
+                 if (entry.isFile) {
+                     const toUpload = new NgxFileDropEntry(entry.name, entry);
+@@ -155,6 +147,11 @@ export class NgxFileDropComponent {
+                 }
+                 return;
+             }
++            const file = item.getAsFile();
++            if (file) {
++                this.addToQueue(this.getFakeDropEntry(file));
++            }
++            return;
+         }
+         this.addToQueue(this.getFakeDropEntry(item));
+     }
+@@ -266,11 +263,11 @@ export class NgxFileDropComponent {
+         event.preventDefault();
+     }
+     static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgxFileDropComponent, deps: [{ token: i0.NgZone }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); }
+-    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.1", type: NgxFileDropComponent, selector: "ngx-file-drop", inputs: { accept: "accept", directory: "directory", multiple: "multiple", dropZoneLabel: "dropZoneLabel", dropZoneClassName: "dropZoneClassName", useDragEnter: "useDragEnter", contentClassName: "contentClassName", showBrowseBtn: "showBrowseBtn", browseBtnClassName: "browseBtnClassName", browseBtnLabel: "browseBtnLabel", disabled: "disabled" }, outputs: { onFileDrop: "onFileDrop", onFileOver: "onFileOver", onFileLeave: "onFileLeave" }, queries: [{ propertyName: "contentTemplate", first: true, predicate: NgxFileDropContentTemplateDirective, descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "fileSelector", first: true, predicate: ["fileSelector"], descendants: true, static: true }], ngImport: i0, template: "<div [className]=\"dropZoneClassName\"\r\n     [class.ngx-file-drop__drop-zone--over]=\"isDraggingOverDropZone\"\r\n     (drop)=\"dropFiles($event)\"\r\n     (dragover)=\"onDragOver($event)\"\r\n     (dragenter)=\"onDragEnter($event)\"\r\n     (dragleave)=\"onDragLeave($event)\">\r\n  <div [className]=\"contentClassName\">\r\n    <input \r\n      type=\"file\" \r\n      #fileSelector \r\n      [accept]=\"accept\" \r\n      [attr.directory]=\"directory || undefined\" \r\n      [attr.webkitdirectory]=\"directory || undefined\"\r\n      [attr.mozdirectory]=\"directory || undefined\"\r\n      [attr.msdirectory]=\"directory || undefined\"\r\n      [attr.odirectory]=\"directory || undefined\"\r\n      [multiple]=\"multiple\"\r\n      (change)=\"uploadFiles($event)\" \r\n      class=\"ngx-file-drop__file-input\" \r\n    />\r\n\r\n    <ng-template #defaultContentTemplate>\r\n      <div *ngIf=\"dropZoneLabel\" class=\"ngx-file-drop__drop-zone-label\">{{dropZoneLabel}}</div>\r\n      <div *ngIf=\"showBrowseBtn\">\r\n        <input type=\"button\" [className]=\"browseBtnClassName\" value=\"{{browseBtnLabel}}\" (click)=\"openFileSelector($event)\" />\r\n      </div>\r\n    </ng-template>\r\n\r\n    <ng-template\r\n      [ngTemplateOutlet]=\"contentTemplate || defaultContentTemplate\"\r\n      [ngTemplateOutletContext]=\"{ openFileSelector: openFileSelector }\">\r\n    </ng-template>\r\n  </div>\r\n</div>\r\n", styles: [".ngx-file-drop__drop-zone{height:100px;margin:auto;border:2px dotted #0782d0;border-radius:30px}.ngx-file-drop__drop-zone--over{background-color:#93939380}.ngx-file-drop__content{display:flex;align-items:center;justify-content:center;height:100px;color:#0782d0}.ngx-file-drop__drop-zone-label{text-align:center}.ngx-file-drop__file-input{display:none}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); }
++    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.1", type: NgxFileDropComponent, selector: "ngx-file-drop", inputs: { accept: "accept", directory: "directory", multiple: "multiple", dropZoneLabel: "dropZoneLabel", dropZoneClassName: "dropZoneClassName", useDragEnter: "useDragEnter", contentClassName: "contentClassName", showBrowseBtn: "showBrowseBtn", browseBtnClassName: "browseBtnClassName", browseBtnLabel: "browseBtnLabel", disabled: "disabled" }, outputs: { onFileDrop: "onFileDrop", onFileOver: "onFileOver", onFileLeave: "onFileLeave" }, queries: [{ propertyName: "contentTemplate", first: true, predicate: NgxFileDropContentTemplateDirective, descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "fileSelector", first: true, predicate: ["fileSelector"], descendants: true, static: true }], ngImport: i0, template: "<div [className]=\"dropZoneClassName\"\n     [class.ngx-file-drop__drop-zone--over]=\"isDraggingOverDropZone\"\n     (drop)=\"dropFiles($event)\"\n     (dragover)=\"onDragOver($event)\"\n     (dragenter)=\"onDragEnter($event)\"\n     (dragleave)=\"onDragLeave($event)\">\n  <div [className]=\"contentClassName\">\n    <input \n      type=\"file\" \n      #fileSelector \n      [accept]=\"accept\" \n      [attr.directory]=\"directory || undefined\" \n      [attr.webkitdirectory]=\"directory || undefined\"\n      [attr.mozdirectory]=\"directory || undefined\"\n      [attr.msdirectory]=\"directory || undefined\"\n      [attr.odirectory]=\"directory || undefined\"\n      [multiple]=\"multiple\"\n      (change)=\"uploadFiles($event)\" \n      class=\"ngx-file-drop__file-input\" \n    />\n\n    <ng-template #defaultContentTemplate>\n      <div *ngIf=\"dropZoneLabel\" class=\"ngx-file-drop__drop-zone-label\">{{dropZoneLabel}}</div>\n      <div *ngIf=\"showBrowseBtn\">\n        <input type=\"button\" [className]=\"browseBtnClassName\" value=\"{{browseBtnLabel}}\" (click)=\"openFileSelector($event)\" />\n      </div>\n    </ng-template>\n\n    <ng-template\n      [ngTemplateOutlet]=\"contentTemplate || defaultContentTemplate\"\n      [ngTemplateOutletContext]=\"{ openFileSelector: openFileSelector }\">\n    </ng-template>\n  </div>\n</div>\n", styles: [".ngx-file-drop__drop-zone{height:100px;margin:auto;border:2px dotted #0782d0;border-radius:30px}.ngx-file-drop__drop-zone--over{background-color:#93939380}.ngx-file-drop__content{display:flex;align-items:center;justify-content:center;height:100px;color:#0782d0}.ngx-file-drop__drop-zone-label{text-align:center}.ngx-file-drop__file-input{display:none}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); }
+ }
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgxFileDropComponent, decorators: [{
+             type: Component,
+-            args: [{ selector: 'ngx-file-drop', template: "<div [className]=\"dropZoneClassName\"\r\n     [class.ngx-file-drop__drop-zone--over]=\"isDraggingOverDropZone\"\r\n     (drop)=\"dropFiles($event)\"\r\n     (dragover)=\"onDragOver($event)\"\r\n     (dragenter)=\"onDragEnter($event)\"\r\n     (dragleave)=\"onDragLeave($event)\">\r\n  <div [className]=\"contentClassName\">\r\n    <input \r\n      type=\"file\" \r\n      #fileSelector \r\n      [accept]=\"accept\" \r\n      [attr.directory]=\"directory || undefined\" \r\n      [attr.webkitdirectory]=\"directory || undefined\"\r\n      [attr.mozdirectory]=\"directory || undefined\"\r\n      [attr.msdirectory]=\"directory || undefined\"\r\n      [attr.odirectory]=\"directory || undefined\"\r\n      [multiple]=\"multiple\"\r\n      (change)=\"uploadFiles($event)\" \r\n      class=\"ngx-file-drop__file-input\" \r\n    />\r\n\r\n    <ng-template #defaultContentTemplate>\r\n      <div *ngIf=\"dropZoneLabel\" class=\"ngx-file-drop__drop-zone-label\">{{dropZoneLabel}}</div>\r\n      <div *ngIf=\"showBrowseBtn\">\r\n        <input type=\"button\" [className]=\"browseBtnClassName\" value=\"{{browseBtnLabel}}\" (click)=\"openFileSelector($event)\" />\r\n      </div>\r\n    </ng-template>\r\n\r\n    <ng-template\r\n      [ngTemplateOutlet]=\"contentTemplate || defaultContentTemplate\"\r\n      [ngTemplateOutletContext]=\"{ openFileSelector: openFileSelector }\">\r\n    </ng-template>\r\n  </div>\r\n</div>\r\n", styles: [".ngx-file-drop__drop-zone{height:100px;margin:auto;border:2px dotted #0782d0;border-radius:30px}.ngx-file-drop__drop-zone--over{background-color:#93939380}.ngx-file-drop__content{display:flex;align-items:center;justify-content:center;height:100px;color:#0782d0}.ngx-file-drop__drop-zone-label{text-align:center}.ngx-file-drop__file-input{display:none}\n"] }]
++            args: [{ selector: 'ngx-file-drop', template: "<div [className]=\"dropZoneClassName\"\n     [class.ngx-file-drop__drop-zone--over]=\"isDraggingOverDropZone\"\n     (drop)=\"dropFiles($event)\"\n     (dragover)=\"onDragOver($event)\"\n     (dragenter)=\"onDragEnter($event)\"\n     (dragleave)=\"onDragLeave($event)\">\n  <div [className]=\"contentClassName\">\n    <input \n      type=\"file\" \n      #fileSelector \n      [accept]=\"accept\" \n      [attr.directory]=\"directory || undefined\" \n      [attr.webkitdirectory]=\"directory || undefined\"\n      [attr.mozdirectory]=\"directory || undefined\"\n      [attr.msdirectory]=\"directory || undefined\"\n      [attr.odirectory]=\"directory || undefined\"\n      [multiple]=\"multiple\"\n      (change)=\"uploadFiles($event)\" \n      class=\"ngx-file-drop__file-input\" \n    />\n\n    <ng-template #defaultContentTemplate>\n      <div *ngIf=\"dropZoneLabel\" class=\"ngx-file-drop__drop-zone-label\">{{dropZoneLabel}}</div>\n      <div *ngIf=\"showBrowseBtn\">\n        <input type=\"button\" [className]=\"browseBtnClassName\" value=\"{{browseBtnLabel}}\" (click)=\"openFileSelector($event)\" />\n      </div>\n    </ng-template>\n\n    <ng-template\n      [ngTemplateOutlet]=\"contentTemplate || defaultContentTemplate\"\n      [ngTemplateOutletContext]=\"{ openFileSelector: openFileSelector }\">\n    </ng-template>\n  </div>\n</div>\n", styles: [".ngx-file-drop__drop-zone{height:100px;margin:auto;border:2px dotted #0782d0;border-radius:30px}.ngx-file-drop__drop-zone--over{background-color:#93939380}.ngx-file-drop__content{display:flex;align-items:center;justify-content:center;height:100px;color:#0782d0}.ngx-file-drop__drop-zone-label{text-align:center}.ngx-file-drop__file-input{display:none}\n"] }]
+         }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: i0.Renderer2 }]; }, propDecorators: { accept: [{
+                 type: Input
+             }], directory: [{
+@@ -306,4 +303,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImpor
+             }], disabled: [{
+                 type: Input
+             }] } });
+-//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LWZpbGUtZHJvcC5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtZmlsZS1kcm9wL3NyYy9saWIvbmd4LWZpbGUtZHJvcC5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtZmlsZS1kcm9wL3NyYy9saWIvbmd4LWZpbGUtZHJvcC5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsU0FBUyxFQUNULFlBQVksRUFFWixZQUFZLEVBQ1osS0FBSyxFQUdMLE1BQU0sRUFFTixXQUFXLEVBQ1gsU0FBUyxFQUNWLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFBZ0IsS0FBSyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBRTNDLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBRXpELE9BQU8sRUFBRSxtQ0FBbUMsRUFBRSxNQUFNLDJCQUEyQixDQUFDOzs7QUFPaEYsTUFBTSxPQUFPLG9CQUFvQjtJQStEL0IsSUFBVyxRQUFRLEtBQWMsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztJQUV6RCxJQUNXLFFBQVEsQ0FBQyxLQUFjO1FBQ2hDLElBQUksQ0FBQyxTQUFTLEdBQUcsQ0FBQyxLQUFLLElBQUksSUFBSSxJQUFJLEdBQUcsS0FBSyxFQUFFLEtBQUssT0FBTyxDQUFDLENBQUM7SUFDN0QsQ0FBQztJQUVELFlBQ1UsSUFBWSxFQUNaLFFBQW1CO1FBRG5CLFNBQUksR0FBSixJQUFJLENBQVE7UUFDWixhQUFRLEdBQVIsUUFBUSxDQUFXO1FBckV0QixXQUFNLEdBQVcsR0FBRyxDQUFDO1FBR3JCLGNBQVMsR0FBWSxLQUFLLENBQUM7UUFHM0IsYUFBUSxHQUFZLElBQUksQ0FBQztRQUd6QixrQkFBYSxHQUFXLEVBQUUsQ0FBQztRQUczQixzQkFBaUIsR0FBVywwQkFBMEIsQ0FBQztRQUd2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Z0JBSUMsWUFBWTtzQkFEbEIsS0FBSztnQkFJQyxnQkFBZ0I7c0JBRHRCLEtBQUs7Z0JBSUMsYUFBYTtzQkFEbkIsS0FBSztnQkFJQyxrQkFBa0I7c0JBRHhCLEtBQUs7Z0JBSUMsY0FBYztzQkFEcEIsS0FBSztnQkFJQyxVQUFVO3NCQURoQixNQUFNO2dCQUlBLFVBQVU7c0JBRGhCLE1BQU07Z0JBSUEsV0FBVztzQkFEakIsTUFBTTtnQkFJbUUsZUFBZTtzQkFBeEYsWUFBWTt1QkFBQyxtQ0FBbUMsRUFBRSxFQUFFLElBQUksRUFBRSxXQUFXLEVBQUU7Z0JBR2pFLFlBQVk7c0JBRGxCLFNBQVM7dUJBQUMsY0FBYyxFQUFFLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRTtnQkFzQmhDLFFBQVE7c0JBRGxCLEtBQUsiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQge1xyXG4gIENvbXBvbmVudCxcclxuICBDb250ZW50Q2hpbGQsXHJcbiAgRWxlbWVudFJlZixcclxuICBFdmVudEVtaXR0ZXIsXHJcbiAgSW5wdXQsXHJcbiAgTmdab25lLFxyXG4gIE9uRGVzdHJveSxcclxuICBPdXRwdXQsXHJcbiAgUmVuZGVyZXIyLFxyXG4gIFRlbXBsYXRlUmVmLFxyXG4gIFZpZXdDaGlsZFxyXG59IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xyXG5pbXBvcnQgeyBTdWJzY3JpcHRpb24sIHRpbWVyIH0gZnJvbSAncnhqcyc7XHJcblxyXG5pbXBvcnQgeyBOZ3hGaWxlRHJvcEVudHJ5IH0gZnJvbSAnLi9uZ3gtZmlsZS1kcm9wLWVudHJ5JztcclxuaW1wb3J0IHsgRmlsZVN5c3RlbURpcmVjdG9yeUVudHJ5LCBGaWxlU3lzdGVtRW50cnksIEZpbGVTeXN0ZW1GaWxlRW50cnkgfSBmcm9tICcuL2RvbS50eXBlcyc7XHJcbmltcG9ydCB7IE5neEZpbGVEcm9wQ29udGVudFRlbXBsYXRlRGlyZWN0aXZlIH0gZnJvbSAnLi9uZ3gtdGVtcGxhdGVzLmRpcmVjdGl2ZSc7XHJcblxyXG5AQ29tcG9uZW50KHtcclxuICBzZWxlY3RvcjogJ25neC1maWxlLWRyb3AnLFxyXG4gIHRlbXBsYXRlVXJsOiAnLi9uZ3gtZmlsZS1kcm9wLmNvbXBvbmVudC5odG1sJyxcclxuICBzdHlsZVVybHM6IFsnLi9uZ3gtZmlsZS1kcm9wLmNvbXBvbmVudC5zY3NzJ10sXHJcbn0pXHJcbmV4cG9ydCBjbGFzcyBOZ3hGaWxlRHJvcENvbXBvbmVudCBpbXBsZW1lbnRzIE9uRGVzdHJveSB7XHJcblxyXG4gIEBJbnB1dCgpXHJcbiAgcHVibGljIGFjY2VwdDogc3RyaW5nID0gJyonO1xyXG5cclxuICBASW5wdXQoKVxyXG4gIHB1YmxpYyBkaXJlY3Rvcnk6IGJvb2xlYW4gPSBmYWxzZTtcclxuXHJcbiAgQElucHV0KClcclxuICBwdWJsaWMgbXVsdGlwbGU6IGJvb2xlYW4gPSB0cnVlO1xyXG5cclxuICBASW5wdXQoKVxyXG4gIHB1YmxpYyBkcm9wWm9uZUxhYmVsOiBzdHJpbmcgPSAnJztcclxuXHJcbiAgQElucHV0KClcclxuICBwdWJsaWMgZHJvcFpvbmVDbGFzc05hbWU6IHN0cmluZyA9ICduZ3gtZmlsZS1kcm9wX19kcm9wLXpvbmUnO1xyXG5cclxuICBASW5wdXQoKVxyXG4gIHB1YmxpYyB1c2VEcmFnRW50ZXI6IGJvb2xlYW4gPSBmYWxzZTtcclxuXHJcbiAgQElucHV0KClcclxuICBwdWJsaWMgY29udGVudENsYXNzTmFtZTogc3RyaW5nID0gJ25neC1maWxlLWRyb3BfX2NvbnRlbnQnO1xyXG5cclxuICBASW5wdXQoKVxyXG4gIHB1YmxpYyBzaG93QnJvd3NlQnRuOiBib29sZWFuID0gZmFsc2U7XHJcblxyXG4gIEBJbnB1dCgpXHJcbiAgcHVibGljIGJyb3dzZUJ0bkNsYXNzTmFtZTogc3RyaW5nID0gJ2J0biBidG4tcHJpbWFyeSBidG4teHMgbmd4LWZpbGUtZHJvcF9fYnJvd3NlLWJ0bic7XHJcblxyXG4gIEBJbnB1dCgpXHJcbiAgcHVibGljIGJyb3dzZUJ0bkxhYmVsOiBzdHJpbmcgPSAnQnJvd3NlIGZpbGVzJztcclxuXHJcbiAgQE91dHB1dCgpXHJcbiAgcHVibGljIG9uRmlsZURyb3A6IEV2ZW50RW1pdHRlcjxOZ3hGaWxlRHJvcEVudHJ5W10+ID0gbmV3IEV2ZW50RW1pdHRlcigpO1xyXG5cclxuICBAT3V0cHV0KClcclxuICBwdWJsaWMgb25GaWxlT3ZlcjogRXZlbnRFbWl0dGVyPGFueT4gPSBuZXcgRXZlbnRFbWl0dGVyKCk7XHJcblxyXG4gIEBPdXRwdXQoKVxyXG4gIHB1YmxpYyBvbkZpbGVMZWF2ZTogRXZlbnRFbWl0dGVyPGFueT4gPSBuZXcgRXZlbnRFbWl0dGVyKCk7XHJcblxyXG4gIC8vIGN1c3RvbSB0ZW1wbGF0ZXNcclxuICBAQ29udGVudENoaWxkKE5neEZpbGVEcm9wQ29udGVudFRlbXBsYXRlRGlyZWN0aXZlLCB7IHJlYWQ6IFRlbXBsYXRlUmVmIH0pIGNvbnRlbnRUZW1wbGF0ZT86IFRlbXBsYXRlUmVmPGFueT47XHJcblxyXG4gIEBWaWV3Q2hpbGQoJ2ZpbGVTZWxlY3RvcicsIHsgc3RhdGljOiB0cnVlIH0pXHJcbiAgcHVibGljIGZpbGVTZWxlY3Rvcj86IEVsZW1lbnRSZWY7XHJcblxyXG4gIHB1YmxpYyBpc0RyYWdnaW5nT3ZlckRyb3Bab25lOiBib29sZWFuID0gZmFsc2U7XHJcblxyXG4gIHByaXZhdGUgZ2xvYmFsRHJhZ2dpbmdJblByb2dyZXNzOiBib29sZWFuID0gZmFsc2U7XHJcbiAgcHJpdmF0ZSByZWFkb25seSBnbG9iYWxEcmFnU3RhcnRMaXN0ZW5lcjogKCkgPT4gdm9pZDtcclxuICBwcml2YXRlIHJlYWRvbmx5IGdsb2JhbERyYWdFbmRMaXN0ZW5lcjogKCkgPT4gdm9pZDtcclxuXHJcbiAgcHJpdmF0ZSBmaWxlczogTmd4RmlsZURyb3BFbnRyeVtdID0gW107XHJcbiAgcHJpdmF0ZSBudW1PZkFjdGl2ZVJlYWRFbnRyaWVzOiBudW1iZXIgPSAwO1xyXG5cclxuICBwcml2YXRlIGhlbHBlckZvcm1FbDogSFRNTEZvcm1FbGVtZW50IHwgbnVsbCA9IG51bGw7XHJcbiAgcHJpdmF0ZSBmaWxlSW5wdXRQbGFjZWhvbGRlckVsOiBIVE1MRGl2RWxlbWVudCB8IG51bGwgPSBudWxsO1xyXG5cclxuICBwcml2YXRlIGRyb3BFdmVudFRpbWVyU3Vic2NyaXB0aW9uOiBTdWJzY3JpcHRpb24gfCBudWxsID0gbnVsbDtcclxuXHJcbiAgcHJpdmF0ZSBfZGlzYWJsZWQ6IGJvb2xlYW4gPSBmYWxzZTtcclxuXHJcbiAgcHVibGljIGdldCBkaXNhYmxlZCgpOiBib29sZWFuIHsgcmV0dXJuIHRoaXMuX2Rpc2FibGVkOyB9XHJcblxyXG4gIEBJbnB1dCgpXHJcbiAgcHVibGljIHNldCBkaXNhYmxlZCh2YWx1ZTogYm9vbGVhbikge1xyXG4gICAgdGhpcy5fZGlzYWJsZWQgPSAodmFsdWUgIT0gbnVsbCAmJiBgJHt2YWx1ZX1gICE9PSAnZmFsc2UnKTtcclxuICB9XHJcblxyXG4gIGNvbnN0cnVjdG9yKFxyXG4gICAgcHJpdmF0ZSB6b25lOiBOZ1pvbmUsXHJcbiAgICBwcml2YXRlIHJlbmRlcmVyOiBSZW5kZXJlcjJcclxuICApIHtcclxuICAgIHRoaXMuZ2xvYmFsRHJhZ1N0YXJ0TGlzdGVuZXIgPSB0aGlzLnJlbmRlcmVyLmxpc3RlbignZG9jdW1lbnQnLCAnZHJhZ3N0YXJ0JywgKGV2dDogRXZlbnQpID0+IHtcclxuICAgICAgdGhpcy5nbG9iYWxEcmFnZ2luZ0luUHJvZ3Jlc3MgPSB0cnVlO1xyXG4gICAgfSk7XHJcbiAgICB0aGlzLmdsb2JhbERyYWdFbmRMaXN0ZW5lciA9IHRoaXMucmVuZGVyZXIubGlzdGVuKCdkb2N1bWVudCcsICdkcmFnZW5kJywgKGV2dDogRXZlbnQpID0+IHtcclxuICAgICAgdGhpcy5nbG9iYWxEcmFnZ2luZ0luUHJvZ3Jlc3MgPSBmYWxzZTtcclxuICAgIH0pO1xyXG4gIH1cclxuXHJcbiAgcHVibGljIG5nT25EZXN0cm95KCk6IHZvaWQge1xyXG4gICAgaWYgKHRoaXMuZHJvcEV2ZW50VGltZXJTdWJzY3JpcHRpb24pIHtcclxuICAgICAgdGhpcy5kcm9wRXZlbnRUaW1lclN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpO1xyXG4gICAgICB0aGlzLmRyb3BFdmVudFRpbWVyU3Vic2NyaXB0aW9uID0gbnVsbDtcclxuICAgIH1cclxuICAgIHRoaXMuZ2xvYmFsRHJhZ1N0YXJ0TGlzdGVuZXIoKTtcclxuICAgIHRoaXMuZ2xvYmFsRHJhZ0VuZExpc3RlbmVyKCk7XHJcbiAgICB0aGlzLmZpbGVzID0gW107XHJcbiAgICB0aGlzLmhlbHBlckZvcm1FbCA9IG51bGw7XHJcbiAgICB0aGlzLmZpbGVJbnB1dFBsYWNlaG9sZGVyRWwgPSBudWxsO1xyXG4gIH1cclxuXHJcbiAgcHVibGljIG9uRHJhZ092ZXIoZXZlbnQ6IERyYWdFdmVudCk6IHZvaWQge1xyXG4gICAgaWYgKHRoaXMudXNlRHJhZ0VudGVyKSB7XHJcbiAgICAgIHRoaXMucHJldmVudEFuZFN0b3AoZXZlbnQpO1xyXG4gICAgICBpZiAoZXZlbnQuZGF0YVRyYW5zZmVyKSB7XHJcbiAgICAgICAgZXZlbnQuZGF0YVRyYW5zZmVyLmRyb3BFZmZlY3QgPSAnY29weSc7XHJcbiAgICAgIH1cclxuICAgIH0gZWxzZSBpZiAoIXRoaXMuaXNEcm9wem9uZURpc2FibGVkKCkgJiYgIXRoaXMudXNlRHJhZ0VudGVyICYmIGV2ZW50LmRhdGFUcmFuc2Zlcikge1xyXG4gICAgICBpZiAoIXRoaXMuaXNEcmFnZ2luZ092ZXJEcm9wWm9uZSkge1xyXG4gICAgICAgIHRoaXMuaXNEcmFnZ2luZ092ZXJEcm9wWm9uZSA9IHRydWU7XHJcbiAgICAgICAgdGhpcy5vbkZpbGVPdmVyLmVtaXQoZXZlbnQpO1xyXG4gICAgICB9XHJcbiAgICAgIHRoaXMucHJldmVudEFuZFN0b3AoZXZlbnQpO1xyXG4gICAgICBldmVudC5kYXRhVHJhbnNmZXIuZHJvcEVmZmVjdCA9ICdjb3B5JztcclxuICAgIH1cclxuICB9XHJcblxyXG4gIHB1YmxpYyBvbkRyYWdFbnRlcihldmVudDogRXZlbnQpOiB2b2lkIHtcclxuICAgIGlmICghdGhpcy5pc0Ryb3B6b25lRGlzYWJsZWQoKSAmJiB0aGlzLnVzZURyYWdFbnRlcikge1xyXG4gICAgICBpZiAoIXRoaXMuaXNEcmFnZ2luZ092ZXJEcm9wWm9uZSkge1xyXG4gICAgICAgIHRoaXMuaXNEcmFnZ2luZ092ZXJEcm9wWm9uZSA9IHRydWU7XHJcbiAgICAgICAgdGhpcy5vbkZpbGVPdmVyLmVtaXQoZXZlbnQpO1xyXG4gICAgICB9XHJcbiAgICAgIHRoaXMucHJldmVudEFuZFN0b3AoZXZlbnQpO1xyXG4gICAgfVxyXG4gIH1cclxuXHJcbiAgcHVibGljIG9uRHJhZ0xlYXZlKGV2ZW50OiBFdmVudCk6IHZvaWQge1xyXG4gICAgaWYgKCF0aGlzLmlzRHJvcHpvbmVEaXNhYmxlZCgpKSB7XHJcbiAgICAgIGlmICh0aGlzLmlzRHJhZ2dpbmdPdmVyRHJvcFpvbmUpIHtcclxuICAgICAgICB0aGlzLmlzRHJhZ2dpbmdPdmVyRHJvcFpvbmUgPSBmYWxzZTtcclxuICAgICAgICB0aGlzLm9uRmlsZUxlYXZlLmVtaXQoZXZlbnQpO1xyXG4gICAgICB9XHJcbiAgICAgIHRoaXMucHJldmVudEFuZFN0b3AoZXZlbnQpO1xyXG4gICAgfVxyXG4gIH1cclxuXHJcbiAgcHVibGljIGRyb3BGaWxlcyhldmVudDogRHJhZ0V2ZW50KTogdm9pZCB7XHJcbiAgICBpZiAodGhpcy5pc0Ryb3B6b25lRGlzYWJsZWQoKSkge1xyXG4gICAgICByZXR1cm47XHJcbiAgICB9XHJcbiAgICB0aGlzLmlzRHJhZ2dpbmdPdmVyRHJvcFpvbmUgPSBmYWxzZTtcclxuICAgIGlmIChldmVudC5kYXRhVHJhbnNmZXIpIHtcclxuICAgICAgbGV0IGl0ZW1zOiBGaWxlTGlzdCB8IERhdGFUcmFuc2Zlckl0ZW1MaXN0O1xyXG4gICAgICBpZiAoZXZlbnQuZGF0YVRyYW5zZmVyLml0ZW1zKSB7XHJcbiAgICAgICAgaXRlbXMgPSBldmVudC5kYXRhVHJhbnNmZXIuaXRlbXM7XHJcbiAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgaXRlbXMgPSBldmVudC5kYXRhVHJhbnNmZXIuZmlsZXM7XHJcbiAgICAgIH1cclxuICAgICAgdGhpcy5wcmV2ZW50QW5kU3RvcChldmVudCk7XHJcbiAgICAgIHRoaXMuY2hlY2tGaWxlcyhpdGVtcyk7XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICBwdWJsaWMgb3BlbkZpbGVTZWxlY3RvciA9IChldmVudD86IE1vdXNlRXZlbnQpOiB2b2lkID0+IHtcclxuICAgIGlmICh0aGlzLmZpbGVTZWxlY3RvciAmJiB0aGlzLmZpbGVTZWxlY3Rvci5uYXRpdmVFbGVtZW50KSB7XHJcbiAgICAgICh0aGlzLmZpbGVTZWxlY3Rvci5uYXRpdmVFbGVtZW50IGFzIEhUTUxJbnB1dEVsZW1lbnQpLmNsaWNrKCk7XHJcbiAgICB9XHJcbiAgfTtcclxuXHJcbiAgLyoqXHJcbiAgICogUHJvY2Vzc2VzIHRoZSBjaGFuZ2UgZXZlbnQgb2YgdGhlIGZpbGUgaW5wdXQgYW5kIGFkZHMgdGhlIGdpdmVuIGZpbGVzLlxyXG4gICAqIEBwYXJhbSBFdmVudCBldmVudFxyXG4gICAqL1xyXG4gIHB1YmxpYyB1cGxvYWRGaWxlcyhldmVudDogRXZlbnQpOiB2b2lkIHtcclxuICAgIGlmICh0aGlzLmlzRHJvcHpvbmVEaXNhYmxlZCgpKSB7XHJcbiAgICAgIHJldHVybjtcclxuICAgIH1cclxuICAgIGlmIChldmVudC50YXJnZXQpIHtcclxuICAgICAgY29uc3QgaXRlbXMgPSAoZXZlbnQudGFyZ2V0IGFzIEhUTUxJbnB1dEVsZW1lbnQpLmZpbGVzIHx8IChbXSBhcyBhbnkpO1xyXG4gICAgICB0aGlzLmNoZWNrRmlsZXMoaXRlbXMpO1xyXG4gICAgICB0aGlzLnJlc2V0RmlsZUlucHV0KCk7XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICBwcml2YXRlIGdldEZha2VEcm9wRW50cnkoZmlsZTogRmlsZSk6IE5neEZpbGVEcm9wRW50cnkge1xyXG4gICAgY29uc3QgZmFrZUZpbGVFbnRyeTogRmlsZVN5c3RlbUZpbGVFbnRyeSA9IHtcclxuICAgICAgbmFtZTogZmlsZS5uYW1lLFxyXG4gICAgICBpc0RpcmVjdG9yeTogZmFsc2UsXHJcbiAgICAgIGlzRmlsZTogdHJ1ZSxcclxuICAgICAgZmlsZTogPFQ+KGNhbGxiYWNrOiAoZmlsZWE6IEZpbGUpID0+IFQpID0+IGNhbGxiYWNrKGZpbGUpLFxyXG4gICAgfTtcclxuICAgIHJldHVybiBuZXcgTmd4RmlsZURyb3BFbnRyeShmYWtlRmlsZUVudHJ5Lm5hbWUsIGZha2VGaWxlRW50cnkpO1xyXG4gIH1cclxuXHJcbiAgcHJpdmF0ZSBjaGVja0ZpbGUoaXRlbTogRGF0YVRyYW5zZmVySXRlbSB8IEZpbGUpOiB2b2lkIHtcclxuICAgIGlmICghaXRlbSkge1xyXG4gICAgICByZXR1cm47XHJcbiAgICB9XHJcbiAgICAvLyBpZiAoXCJnZXRBc0ZpbGVcIiBpbiBpdGVtKSB7XHJcbiAgICAvLyAgIGNvbnN0IGZpbGUgPSBpdGVtLmdldEFzRmlsZSgpO1xyXG4gICAgLy8gICBpZiAoZmlsZSkge1xyXG4gICAgLy8gICAgIHRoaXMuYWRkVG9RdWV1ZShcclxuICAgIC8vICAgICAgIHRoaXMuZ2V0RmFrZURyb3BFbnRyeShmaWxlKVxyXG4gICAgLy8gICAgICk7XHJcbiAgICAvLyAgICAgcmV0dXJuO1xyXG4gICAgLy8gICB9XHJcbiAgICAvLyB9XHJcbiAgICBpZiAoXCJ3ZWJraXRHZXRBc0VudHJ5XCIgaW4gaXRlbSkge1xyXG4gICAgICBsZXQgZW50cnkgPSBpdGVtLndlYmtpdEdldEFzRW50cnkoKTtcclxuICAgICAgaWYgKGVudHJ5KSB7XHJcbiAgICAgICAgaWYgKGVudHJ5LmlzRmlsZSkge1xyXG4gICAgICAgICAgY29uc3QgdG9VcGxvYWQ6IE5neEZpbGVEcm9wRW50cnkgPSBuZXcgTmd4RmlsZURyb3BFbnRyeShlbnRyeS5uYW1lLCBlbnRyeSk7XHJcbiAgICAgICAgICB0aGlzLmFkZFRvUXVldWUodG9VcGxvYWQpO1xyXG5cclxuICAgICAgICB9IGVsc2UgaWYgKGVudHJ5LmlzRGlyZWN0b3J5KSB7XHJcbiAgICAgICAgICB0aGlzLnRyYXZlcnNlRmlsZVRyZWUoZW50cnksIGVudHJ5Lm5hbWUpO1xyXG4gICAgICAgIH1cclxuICAgICAgICByZXR1cm47XHJcbiAgICAgIH1cclxuICAgIH1cclxuICAgIHRoaXMuYWRkVG9RdWV1ZSh0aGlzLmdldEZha2VEcm9wRW50cnkoKGl0ZW0gYXMgRmlsZSkpKTtcclxuICB9XHJcblxyXG4gIHByaXZhdGUgY2hlY2tGaWxlcyhpdGVtczogRmlsZUxpc3QgfCBEYXRhVHJhbnNmZXJJdGVtTGlzdCk6IHZvaWQge1xyXG4gICAgZm9yIChsZXQgaSA9IDA7IGkgPCBpdGVtcy5sZW5ndGg7IGkrKykge1xyXG4gICAgICB0aGlzLmNoZWNrRmlsZShpdGVtc1tpXSk7XHJcbiAgICB9XHJcblxyXG4gICAgaWYgKHRoaXMuZHJvcEV2ZW50VGltZXJTdWJzY3JpcHRpb24pIHtcclxuICAgICAgdGhpcy5kcm9wRXZlbnRUaW1lclN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpO1xyXG4gICAgfVxyXG4gICAgdGhpcy5kcm9wRXZlbnRUaW1lclN1YnNjcmlwdGlvbiA9IHRpbWVyKDIwMCwgMjAwKVxyXG4gICAgICAuc3Vic2NyaWJlKCgpID0+IHtcclxuICAgICAgICBpZiAodGhpcy5maWxlcy5sZW5ndGggPiAwICYmIHRoaXMubnVtT2ZBY3RpdmVSZWFkRW50cmllcyA9PT0gMCkge1xyXG4gICAgICAgICAgY29uc3QgZmlsZXMgPSB0aGlzLmZpbGVzO1xyXG4gICAgICAgICAgdGhpcy5maWxlcyA9IFtdO1xyXG4gICAgICAgICAgdGhpcy5vbkZpbGVEcm9wLmVtaXQoZmlsZXMpO1xyXG4gICAgICAgIH1cclxuICAgICAgfSk7XHJcbiAgfVxyXG5cclxuICBwcml2YXRlIHRyYXZlcnNlRmlsZVRyZWUoaXRlbTogRmlsZVN5c3RlbUVudHJ5LCBwYXRoOiBzdHJpbmcpOiB2b2lkIHtcclxuICAgIGlmIChpdGVtLmlzRmlsZSkge1xyXG4gICAgICBjb25zdCB0b1VwbG9hZDogTmd4RmlsZURyb3BFbnRyeSA9IG5ldyBOZ3hGaWxlRHJvcEVudHJ5KHBhdGgsIGl0ZW0pO1xyXG4gICAgICB0aGlzLmZpbGVzLnB1c2godG9VcGxvYWQpO1xyXG5cclxuICAgIH0gZWxzZSB7XHJcbiAgICAgIHBhdGggPSBwYXRoICsgJy8nO1xyXG4gICAgICBjb25zdCBkaXJSZWFkZXIgPSAoaXRlbSBhcyBGaWxlU3lzdGVtRGlyZWN0b3J5RW50cnkpLmNyZWF0ZVJlYWRlcigpO1xyXG4gICAgICBsZXQgZW50cmllczogRmlsZVN5c3RlbUVudHJ5W10gPSBbXTtcclxuXHJcbiAgICAgIGNvbnN0IHJlYWRFbnRyaWVzID0gKCkgPT4ge1xyXG4gICAgICAgIHRoaXMubnVtT2ZBY3RpdmVSZWFkRW50cmllcysrO1xyXG4gICAgICAgIGRpclJlYWRlci5yZWFkRW50cmllcygocmVzdWx0KSA9PiB7XHJcbiAgICAgICAgICBpZiAoIXJlc3VsdC5sZW5ndGgpIHtcclxuICAgICAgICAgICAgLy8gYWRkIGVtcHR5IGZvbGRlcnNcclxuICAgICAgICAgICAgaWYgKGVudHJpZXMubGVuZ3RoID09PSAwKSB7XHJcbiAgICAgICAgICAgICAgY29uc3QgdG9VcGxvYWQ6IE5neEZpbGVEcm9wRW50cnkgPSBuZXcgTmd4RmlsZURyb3BFbnRyeShwYXRoLCBpdGVtKTtcclxuICAgICAgICAgICAgICB0aGlzLnpvbmUucnVuKCgpID0+IHtcclxuICAgICAgICAgICAgICAgIHRoaXMuYWRkVG9RdWV1ZSh0b1VwbG9hZCk7XHJcbiAgICAgICAgICAgICAgfSk7XHJcblxyXG4gICAgICAgICAgICB9IGVsc2Uge1xyXG4gICAgICAgICAgICAgIGZvciAobGV0IGkgPSAwOyBpIDwgZW50cmllcy5sZW5ndGg7IGkrKykge1xyXG4gICAgICAgICAgICAgICAgdGhpcy56b25lLnJ1bigoKSA9PiB7XHJcbiAgICAgICAgICAgICAgICAgIHRoaXMudHJhdmVyc2VGaWxlVHJlZShlbnRyaWVzW2ldLCBwYXRoICsgZW50cmllc1tpXS5uYW1lKTtcclxuICAgICAgICAgICAgICAgIH0pO1xyXG4gICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgfVxyXG5cclxuICAgICAgICAgIH0gZWxzZSB7XHJcbiAgICAgICAgICAgIC8vIGNvbnRpbnVlIHdpdGggdGhlIHJlYWRpbmdcclxuICAgICAgICAgICAgZW50cmllcyA9IGVudHJpZXMuY29uY2F0KHJlc3VsdCk7XHJcbiAgICAgICAgICAgIHJlYWRFbnRyaWVzKCk7XHJcbiAgICAgICAgICB9XHJcblxyXG4gICAgICAgICAgdGhpcy5udW1PZkFjdGl2ZVJlYWRFbnRyaWVzLS07XHJcbiAgICAgICAgfSk7XHJcbiAgICAgIH07XHJcblxyXG4gICAgICByZWFkRW50cmllcygpO1xyXG4gICAgfVxyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogQ2xlYXJzIGFueSBhZGRlZCBmaWxlcyBmcm9tIHRoZSBmaWxlIGlucHV0IGVsZW1lbnQgc28gdGhlIHNhbWUgZmlsZSBjYW4gc3Vic2VxdWVudGx5IGJlIGFkZGVkIG11bHRpcGxlIHRpbWVzLlxyXG4gICAqL1xyXG4gIHByaXZhdGUgcmVzZXRGaWxlSW5wdXQoKTogdm9pZCB7XHJcbiAgICBpZiAodGhpcy5maWxlU2VsZWN0b3IgJiYgdGhpcy5maWxlU2VsZWN0b3IubmF0aXZlRWxlbWVudCkge1xyXG4gICAgICBjb25zdCBmaWxlSW5wdXRFbCA9IHRoaXMuZmlsZVNlbGVjdG9yLm5hdGl2ZUVsZW1lbnQgYXMgSFRNTElucHV0RWxlbWVudDtcclxuICAgICAgY29uc3QgZmlsZUlucHV0Q29udGFpbmVyRWwgPSBmaWxlSW5wdXRFbC5wYXJlbnRFbGVtZW50O1xyXG4gICAgICBjb25zdCBoZWxwZXJGb3JtRWwgPSB0aGlzLmdldEhlbHBlckZvcm1FbGVtZW50KCk7XHJcbiAgICAgIGNvbnN0IGZpbGVJbnB1dFBsYWNlaG9sZGVyRWwgPSB0aGlzLmdldEZpbGVJbnB1dFBsYWNlaG9sZGVyRWxlbWVudCgpO1xyXG5cclxuICAgICAgLy8gSnVzdCBhIHF1aWNrIGNoZWNrIHNvIHdlIGRvIG5vdCBtZXNzIHVwIHRoZSBET00gKHdpbGwgbmV2ZXIgaGFwcGVuIHRob3VnaCkuXHJcbiAgICAgIGlmIChmaWxlSW5wdXRDb250YWluZXJFbCAhPT0gaGVscGVyRm9ybUVsKSB7XHJcbiAgICAgICAgLy8gSW5zZXJ0IHRoZSBmb3JtIGlucHV0IHBsYWNlaG9sZGVyIGluIHRoZSBET00gYmVmb3JlIHRoZSBmb3JtIGlucHV0IGVsZW1lbnQuXHJcbiAgICAgICAgdGhpcy5yZW5kZXJlci5pbnNlcnRCZWZvcmUoZmlsZUlucHV0Q29udGFpbmVyRWwsIGZpbGVJbnB1dFBsYWNlaG9sZGVyRWwsIGZpbGVJbnB1dEVsKTtcclxuICAgICAgICAvLyBBZGQgdGhlIGZvcm0gaW5wdXQgYXMgY2hpbGQgb2YgdGhlIHRlbXBvcmFyeSBmb3JtIGVsZW1lbnQsIHJlbW92aW5nIHRoZSBmb3JtIGlucHV0IGZyb20gdGhlIERPTS5cclxuICAgICAgICB0aGlzLnJlbmRlcmVyLmFwcGVuZENoaWxkKGhlbHBlckZvcm1FbCwgZmlsZUlucHV0RWwpO1xyXG4gICAgICAgIC8vIFJlc2V0IHRoZSBmb3JtLCB0aHVzIGNsZWFyaW5nIHRoZSBpbnB1dCBlbGVtZW50IG9mIGFueSBmaWxlcy5cclxuICAgICAgICBoZWxwZXJGb3JtRWwucmVzZXQoKTtcclxuICAgICAgICAvLyBBZGQgdGhlIGZpbGUgaW5wdXQgYmFjayB0byB0aGUgRE9NIGluIHBsYWNlIG9mIHRoZSBmaWxlIGlucHV0IHBsYWNlaG9sZGVyIGVsZW1lbnQuXHJcbiAgICAgICAgdGhpcy5yZW5kZXJlci5pbnNlcnRCZWZvcmUoZmlsZUlucHV0Q29udGFpbmVyRWwsIGZpbGVJbnB1dEVsLCBmaWxlSW5wdXRQbGFjZWhvbGRlckVsKTtcclxuICAgICAgICAvLyBSZW1vdmUgdGhlIGlucHV0IHBsYWNlaG9sZGVyIGZyb20gdGhlIERPTVxyXG4gICAgICAgIHRoaXMucmVuZGVyZXIucmVtb3ZlQ2hpbGQoZmlsZUlucHV0Q29udGFpbmVyRWwsIGZpbGVJbnB1dFBsYWNlaG9sZGVyRWwpO1xyXG4gICAgICB9XHJcbiAgICB9XHJcbiAgfVxyXG5cclxuICAvKipcclxuICAgKiBHZXQgYSBjYWNoZWQgSFRNTCBmb3JtIGVsZW1lbnQgYXMgYSBoZWxwZXIgZWxlbWVudCB0byBjbGVhciB0aGUgZmlsZSBpbnB1dCBlbGVtZW50LlxyXG4gICAqL1xyXG4gIHByaXZhdGUgZ2V0SGVscGVyRm9ybUVsZW1lbnQoKTogSFRNTEZvcm1FbGVtZW50IHtcclxuICAgIGlmICghdGhpcy5oZWxwZXJGb3JtRWwpIHtcclxuICAgICAgdGhpcy5oZWxwZXJGb3JtRWwgPSB0aGlzLnJlbmRlcmVyLmNyZWF0ZUVsZW1lbnQoJ2Zvcm0nKSBhcyBIVE1MRm9ybUVsZW1lbnQ7XHJcbiAgICB9XHJcblxyXG4gICAgcmV0dXJuIHRoaXMuaGVscGVyRm9ybUVsO1xyXG4gIH1cclxuXHJcbiAgLyoqXHJcbiAgICogR2V0IGEgY2FjaGVkIEhUTUwgZGl2IGVsZW1lbnQgdG8gYmUgdXNlZCBhcyBwbGFjZWhvbGRlciBmb3IgdGhlIGZpbGUgaW5wdXQgZWxlbWVudCB3aGVuIGNsZWFyaW5nIHNhaWQgZWxlbWVudC5cclxuICAgKi9cclxuICBwcml2YXRlIGdldEZpbGVJbnB1dFBsYWNlaG9sZGVyRWxlbWVudCgpOiBIVE1MRGl2RWxlbWVudCB7XHJcbiAgICBpZiAoIXRoaXMuZmlsZUlucHV0UGxhY2Vob2xkZXJFbCkge1xyXG4gICAgICB0aGlzLmZpbGVJbnB1dFBsYWNlaG9sZGVyRWwgPSB0aGlzLnJlbmRlcmVyLmNyZWF0ZUVsZW1lbnQoJ2RpdicpIGFzIEhUTUxEaXZFbGVtZW50O1xyXG4gICAgfVxyXG5cclxuICAgIHJldHVybiB0aGlzLmZpbGVJbnB1dFBsYWNlaG9sZGVyRWw7XHJcbiAgfVxyXG5cclxuICBwcml2YXRlIGlzRHJvcHpvbmVEaXNhYmxlZCgpOiBib29sZWFuIHtcclxuICAgIHJldHVybiAodGhpcy5nbG9iYWxEcmFnZ2luZ0luUHJvZ3Jlc3MgfHwgdGhpcy5kaXNhYmxlZCk7XHJcbiAgfVxyXG5cclxuICBwcml2YXRlIGFkZFRvUXVldWUoaXRlbTogTmd4RmlsZURyb3BFbnRyeSk6IHZvaWQge1xyXG4gICAgdGhpcy5maWxlcy5wdXNoKGl0ZW0pO1xyXG4gIH1cclxuXHJcbiAgcHJpdmF0ZSBwcmV2ZW50QW5kU3RvcChldmVudDogRXZlbnQpOiB2b2lkIHtcclxuICAgIGV2ZW50LnN0b3BQcm9wYWdhdGlvbigpO1xyXG4gICAgZXZlbnQucHJldmVudERlZmF1bHQoKTtcclxuICB9XHJcbn1cclxuIiwiPGRpdiBbY2xhc3NOYW1lXT1cImRyb3Bab25lQ2xhc3NOYW1lXCJcclxuICAgICBbY2xhc3Mubmd4LWZpbGUtZHJvcF9fZHJvcC16b25lLS1vdmVyXT1cImlzRHJhZ2dpbmdPdmVyRHJvcFpvbmVcIlxyXG4gICAgIChkcm9wKT1cImRyb3BGaWxlcygkZXZlbnQpXCJcclxuICAgICAoZHJhZ292ZXIpPVwib25EcmFnT3ZlcigkZXZlbnQpXCJcclxuICAgICAoZHJhZ2VudGVyKT1cIm9uRHJhZ0VudGVyKCRldmVudClcIlxyXG4gICAgIChkcmFnbGVhdmUpPVwib25EcmFnTGVhdmUoJGV2ZW50KVwiPlxyXG4gIDxkaXYgW2NsYXNzTmFtZV09XCJjb250ZW50Q2xhc3NOYW1lXCI+XHJcbiAgICA8aW5wdXQgXHJcbiAgICAgIHR5cGU9XCJmaWxlXCIgXHJcbiAgICAgICNmaWxlU2VsZWN0b3IgXHJcbiAgICAgIFthY2NlcHRdPVwiYWNjZXB0XCIgXHJcbiAgICAgIFthdHRyLmRpcmVjdG9yeV09XCJkaXJlY3RvcnkgfHwgdW5kZWZpbmVkXCIgXHJcbiAgICAgIFthdHRyLndlYmtpdGRpcmVjdG9yeV09XCJkaXJlY3RvcnkgfHwgdW5kZWZpbmVkXCJcclxuICAgICAgW2F0dHIubW96ZGlyZWN0b3J5XT1cImRpcmVjdG9yeSB8fCB1bmRlZmluZWRcIlxyXG4gICAgICBbYXR0ci5tc2RpcmVjdG9yeV09XCJkaXJlY3RvcnkgfHwgdW5kZWZpbmVkXCJcclxuICAgICAgW2F0dHIub2RpcmVjdG9yeV09XCJkaXJlY3RvcnkgfHwgdW5kZWZpbmVkXCJcclxuICAgICAgW211bHRpcGxlXT1cIm11bHRpcGxlXCJcclxuICAgICAgKGNoYW5nZSk9XCJ1cGxvYWRGaWxlcygkZXZlbnQpXCIgXHJcbiAgICAgIGNsYXNzPVwibmd4LWZpbGUtZHJvcF9fZmlsZS1pbnB1dFwiIFxyXG4gICAgLz5cclxuXHJcbiAgICA8bmctdGVtcGxhdGUgI2RlZmF1bHRDb250ZW50VGVtcGxhdGU+XHJcbiAgICAgIDxkaXYgKm5nSWY9XCJkcm9wWm9uZUxhYmVsXCIgY2xhc3M9XCJuZ3gtZmlsZS1kcm9wX19kcm9wLXpvbmUtbGFiZWxcIj57e2Ryb3Bab25lTGFiZWx9fTwvZGl2PlxyXG4gICAgICA8ZGl2ICpuZ0lmPVwic2hvd0Jyb3dzZUJ0blwiPlxyXG4gICAgICAgIDxpbnB1dCB0eXBlPVwiYnV0dG9uXCIgW2NsYXNzTmFtZV09XCJicm93c2VCdG5DbGFzc05hbWVcIiB2YWx1ZT1cInt7YnJvd3NlQnRuTGFiZWx9fVwiIChjbGljayk9XCJvcGVuRmlsZVNlbGVjdG9yKCRldmVudClcIiAvPlxyXG4gICAgICA8L2Rpdj5cclxuICAgIDwvbmctdGVtcGxhdGU+XHJcblxyXG4gICAgPG5nLXRlbXBsYXRlXHJcbiAgICAgIFtuZ1RlbXBsYXRlT3V0bGV0XT1cImNvbnRlbnRUZW1wbGF0ZSB8fCBkZWZhdWx0Q29udGVudFRlbXBsYXRlXCJcclxuICAgICAgW25nVGVtcGxhdGVPdXRsZXRDb250ZXh0XT1cInsgb3BlbkZpbGVTZWxlY3Rvcjogb3BlbkZpbGVTZWxlY3RvciB9XCI+XHJcbiAgICA8L25nLXRlbXBsYXRlPlxyXG4gIDwvZGl2PlxyXG48L2Rpdj5cclxuIl19
+\ No newline at end of file
++//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LWZpbGUtZHJvcC5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtZmlsZS1kcm9wL3NyYy9saWIvbmd4LWZpbGUtZHJvcC5jb21wb25lbnQudHMiLCIuLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtZmlsZS1kcm9wL3NyYy9saWIvbmd4LWZpbGUtZHJvcC5jb21wb25lbnQuaHRtbCJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQ0wsU0FBUyxFQUNULFlBQVksRUFFWixZQUFZLEVBQ1osS0FBSyxFQUdMLE1BQU0sRUFFTixXQUFXLEVBQ1gsU0FBUyxFQUNWLE1BQU0sZUFBZSxDQUFDO0FBQ3ZCLE9BQU8sRUFBZ0IsS0FBSyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBRTNDLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxNQUFNLHVCQUF1QixDQUFDO0FBQ3pELE9BQU8sRUFBa0Usa0JBQWtCLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDakgsT0FBTyxFQUFFLG1DQUFtQyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7OztBQU9oRixNQUFNLE9BQU8sb0JBQW9CO0lBK0QvQixJQUFXLFFBQVEsS0FBYyxPQUFPLElBQUksQ0FBQyxTQUFTLENBQUMsQ0FBQyxDQUFDO0lBRXpELElBQ1csUUFBUSxDQUFDLEtBQWM7UUFDaEMsSUFBSSxDQUFDLFNBQVMsR0FBRyxDQUFDLEtBQUssSUFBSSxJQUFJLElBQUksR0FBRyxLQUFLLEVBQUUsS0FBSyxPQUFPLENBQUMsQ0FBQztJQUM3RCxDQUFDO0lBRUQsWUFDVSxJQUFZLEVBQ1osUUFBbUI7UUFEbkIsU0FBSSxHQUFKLElBQUksQ0FBUTtRQUNaLGFBQVEsR0FBUixRQUFRLENBQVc7UUFyRXRCLFdBQU0sR0FBVyxHQUFHLENBQUM7UUFHckIsY0FBUyxHQUFZLEtBQUssQ0FBQztRQUczQixhQUFRLEdBQVksSUFBSSxDQUFDO1FBR3pCLGtCQUFhLEdBQVcs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SztnQkFJQyxTQUFTO3NCQURmLEtBQUs7Z0JBSUMsUUFBUTtzQkFEZCxLQUFLO2dCQUlDLGFBQWE7c0JBRG5CLEtBQUs7Z0JBSUMsaUJBQWlCO3NCQUR2QixLQUFLO2dCQUlDLFlBQVk7c0JBRGxCLEtBQUs7Z0JBSUMsZ0JBQWdCO3NCQUR0QixLQUFLO2dCQUlDLGFBQWE7c0JBRG5CLEtBQUs7Z0JBSUMsa0JBQWtCO3NCQUR4QixLQUFLO2dCQUlDLGNBQWM7c0JBRHBCLEtBQUs7Z0JBSUMsVUFBVTtzQkFEaEIsTUFBTTtnQkFJQSxVQUFVO3NCQURoQixNQUFNO2dCQUlBLFdBQVc7c0JBRGpCLE1BQU07Z0JBSW1FLGVBQWU7c0JBQXhGLFlBQVk7dUJBQUMsbUNBQW1DLEVBQUUsRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFO2dCQUdqRSxZQUFZO3NCQURsQixTQUFTO3VCQUFDLGNBQWMsRUFBRSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUU7Z0JBc0JoQyxRQUFRO3NCQURsQixLQUFLIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHtcbiAgQ29tcG9uZW50LFxuICBDb250ZW50Q2hpbGQsXG4gIEVsZW1lbnRSZWYsXG4gIEV2ZW50RW1pdHRlcixcbiAgSW5wdXQsXG4gIE5nWm9uZSxcbiAgT25EZXN0cm95LFxuICBPdXRwdXQsXG4gIFJlbmRlcmVyMixcbiAgVGVtcGxhdGVSZWYsXG4gIFZpZXdDaGlsZFxufSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IFN1YnNjcmlwdGlvbiwgdGltZXIgfSBmcm9tICdyeGpzJztcblxuaW1wb3J0IHsgTmd4RmlsZURyb3BFbnRyeSB9IGZyb20gJy4vbmd4LWZpbGUtZHJvcC1lbnRyeSc7XG5pbXBvcnQgeyBGaWxlU3lzdGVtRGlyZWN0b3J5RW50cnksIEZpbGVTeXN0ZW1FbnRyeSwgRmlsZVN5c3RlbUZpbGVFbnRyeSwgaXNEYXRhVHJhbnNmZXJJdGVtIH0gZnJvbSAnLi9kb20udHlwZXMnO1xuaW1wb3J0IHsgTmd4RmlsZURyb3BDb250ZW50VGVtcGxhdGVEaXJlY3RpdmUgfSBmcm9tICcuL25neC10ZW1wbGF0ZXMuZGlyZWN0aXZlJztcblxuQENvbXBvbmVudCh7XG4gIHNlbGVjdG9yOiAnbmd4LWZpbGUtZHJvcCcsXG4gIHRlbXBsYXRlVXJsOiAnLi9uZ3gtZmlsZS1kcm9wLmNvbXBvbmVudC5odG1sJyxcbiAgc3R5bGVVcmxzOiBbJy4vbmd4LWZpbGUtZHJvcC5jb21wb25lbnQuc2NzcyddLFxufSlcbmV4cG9ydCBjbGFzcyBOZ3hGaWxlRHJvcENvbXBvbmVudCBpbXBsZW1lbnRzIE9uRGVzdHJveSB7XG5cbiAgQElucHV0KClcbiAgcHVibGljIGFjY2VwdDogc3RyaW5nID0gJyonO1xuXG4gIEBJbnB1dCgpXG4gIHB1YmxpYyBkaXJlY3Rvcnk6IGJvb2xlYW4gPSBmYWxzZTtcblxuICBASW5wdXQoKVxuICBwdWJsaWMgbXVsdGlwbGU6IGJvb2xlYW4gPSB0cnVlO1xuXG4gIEBJbnB1dCgpXG4gIHB1YmxpYyBkcm9wWm9uZUxhYmVsOiBzdHJpbmcgPSAnJztcblxuICBASW5wdXQoKVxuICBwdWJsaWMgZHJvcFpvbmVDbGFzc05hbWU6IHN0cmluZyA9ICduZ3gtZmlsZS1kcm9wX19kcm9wLXpvbmUnO1xuXG4gIEBJbnB1dCgpXG4gIHB1YmxpYyB1c2VEcmFnRW50ZXI6IGJvb2xlYW4gPSBmYWxzZTtcblxuICBASW5wdXQoKVxuICBwdWJsaWMgY29udGVudENsYXNzTmFtZTogc3RyaW5nID0gJ25neC1maWxlLWRyb3BfX2NvbnRlbnQnO1xuXG4gIEBJbnB1dCgpXG4gIHB1YmxpYyBzaG93QnJvd3NlQnRuOiBib29sZWFuID0gZmFsc2U7XG5cbiAgQElucHV0KClcbiAgcHVibGljIGJyb3dzZUJ0bkNsYXNzTmFtZTogc3RyaW5nID0gJ2J0biBidG4tcHJpbWFyeSBidG4teHMgbmd4LWZpbGUtZHJvcF9fYnJvd3NlLWJ0bic7XG5cbiAgQElucHV0KClcbiAgcHVibGljIGJyb3dzZUJ0bkxhYmVsOiBzdHJpbmcgPSAnQnJvd3NlIGZpbGVzJztcblxuICBAT3V0cHV0KClcbiAgcHVibGljIG9uRmlsZURyb3A6IEV2ZW50RW1pdHRlcjxOZ3hGaWxlRHJvcEVudHJ5W10+ID0gbmV3IEV2ZW50RW1pdHRlcigpO1xuXG4gIEBPdXRwdXQoKVxuICBwdWJsaWMgb25GaWxlT3ZlcjogRXZlbnRFbWl0dGVyPGFueT4gPSBuZXcgRXZlbnRFbWl0dGVyKCk7XG5cbiAgQE91dHB1dCgpXG4gIHB1YmxpYyBvbkZpbGVMZWF2ZTogRXZlbnRFbWl0dGVyPGFueT4gPSBuZXcgRXZlbnRFbWl0dGVyKCk7XG5cbiAgLy8gY3VzdG9tIHRlbXBsYXRlc1xuICBAQ29udGVudENoaWxkKE5neEZpbGVEcm9wQ29udGVudFRlbXBsYXRlRGlyZWN0aXZlLCB7IHJlYWQ6IFRlbXBsYXRlUmVmIH0pIGNvbnRlbnRUZW1wbGF0ZT86IFRlbXBsYXRlUmVmPGFueT47XG5cbiAgQFZpZXdDaGlsZCgnZmlsZVNlbGVjdG9yJywgeyBzdGF0aWM6IHRydWUgfSlcbiAgcHVibGljIGZpbGVTZWxlY3Rvcj86IEVsZW1lbnRSZWY7XG5cbiAgcHVibGljIGlzRHJhZ2dpbmdPdmVyRHJvcFpvbmU6IGJvb2xlYW4gPSBmYWxzZTtcblxuICBwcml2YXRlIGdsb2JhbERyYWdnaW5nSW5Qcm9ncmVzczogYm9vbGVhbiA9IGZhbHNlO1xuICBwcml2YXRlIHJlYWRvbmx5IGdsb2JhbERyYWdTdGFydExpc3RlbmVyOiAoKSA9PiB2b2lkO1xuICBwcml2YXRlIHJlYWRvbmx5IGdsb2JhbERyYWdFbmRMaXN0ZW5lcjogKCkgPT4gdm9pZDtcblxuICBwcml2YXRlIGZpbGVzOiBOZ3hGaWxlRHJvcEVudHJ5W10gPSBbXTtcbiAgcHJpdmF0ZSBudW1PZkFjdGl2ZVJlYWRFbnRyaWVzOiBudW1iZXIgPSAwO1xuXG4gIHByaXZhdGUgaGVscGVyRm9ybUVsOiBIVE1MRm9ybUVsZW1lbnQgfCBudWxsID0gbnVsbDtcbiAgcHJpdmF0ZSBmaWxlSW5wdXRQbGFjZWhvbGRlckVsOiBIVE1MRGl2RWxlbWVudCB8IG51bGwgPSBudWxsO1xuXG4gIHByaXZhdGUgZHJvcEV2ZW50VGltZXJTdWJzY3JpcHRpb246IFN1YnNjcmlwdGlvbiB8IG51bGwgPSBudWxsO1xuXG4gIHByaXZhdGUgX2Rpc2FibGVkOiBib29sZWFuID0gZmFsc2U7XG5cbiAgcHVibGljIGdldCBkaXNhYmxlZCgpOiBib29sZWFuIHsgcmV0dXJuIHRoaXMuX2Rpc2FibGVkOyB9XG5cbiAgQElucHV0KClcbiAgcHVibGljIHNldCBkaXNhYmxlZCh2YWx1ZTogYm9vbGVhbikge1xuICAgIHRoaXMuX2Rpc2FibGVkID0gKHZhbHVlICE9IG51bGwgJiYgYCR7dmFsdWV9YCAhPT0gJ2ZhbHNlJyk7XG4gIH1cblxuICBjb25zdHJ1Y3RvcihcbiAgICBwcml2YXRlIHpvbmU6IE5nWm9uZSxcbiAgICBwcml2YXRlIHJlbmRlcmVyOiBSZW5kZXJlcjJcbiAgKSB7XG4gICAgdGhpcy5nbG9iYWxEcmFnU3RhcnRMaXN0ZW5lciA9IHRoaXMucmVuZGVyZXIubGlzdGVuKCdkb2N1bWVudCcsICdkcmFnc3RhcnQnLCAoZXZ0OiBFdmVudCkgPT4ge1xuICAgICAgdGhpcy5nbG9iYWxEcmFnZ2luZ0luUHJvZ3Jlc3MgPSB0cnVlO1xuICAgIH0pO1xuICAgIHRoaXMuZ2xvYmFsRHJhZ0VuZExpc3RlbmVyID0gdGhpcy5yZW5kZXJlci5saXN0ZW4oJ2RvY3VtZW50JywgJ2RyYWdlbmQnLCAoZXZ0OiBFdmVudCkgPT4ge1xuICAgICAgdGhpcy5nbG9iYWxEcmFnZ2luZ0luUHJvZ3Jlc3MgPSBmYWxzZTtcbiAgICB9KTtcbiAgfVxuXG4gIHB1YmxpYyBuZ09uRGVzdHJveSgpOiB2b2lkIHtcbiAgICBpZiAodGhpcy5kcm9wRXZlbnRUaW1lclN1YnNjcmlwdGlvbikge1xuICAgICAgdGhpcy5kcm9wRXZlbnRUaW1lclN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpO1xuICAgICAgdGhpcy5kcm9wRXZlbnRUaW1lclN1YnNjcmlwdGlvbiA9IG51bGw7XG4gICAgfVxuICAgIHRoaXMuZ2xvYmFsRHJhZ1N0YXJ0TGlzdGVuZXIoKTtcbiAgICB0aGlzLmdsb2JhbERyYWdFbmRMaXN0ZW5lcigpO1xuICAgIHRoaXMuZmlsZXMgPSBbXTtcbiAgICB0aGlzLmhlbHBlckZvcm1FbCA9IG51bGw7XG4gICAgdGhpcy5maWxlSW5wdXRQbGFjZWhvbGRlckVsID0gbnVsbDtcbiAgfVxuXG4gIHB1YmxpYyBvbkRyYWdPdmVyKGV2ZW50OiBEcmFnRXZlbnQpOiB2b2lkIHtcbiAgICBpZiAodGhpcy51c2VEcmFnRW50ZXIpIHtcbiAgICAgIHRoaXMucHJldmVudEFuZFN0b3AoZXZlbnQpO1xuICAgICAgaWYgKGV2ZW50LmRhdGFUcmFuc2Zlcikge1xuICAgICAgICBldmVudC5kYXRhVHJhbnNmZXIuZHJvcEVmZmVjdCA9ICdjb3B5JztcbiAgICAgIH1cbiAgICB9IGVsc2UgaWYgKCF0aGlzLmlzRHJvcHpvbmVEaXNhYmxlZCgpICYmICF0aGlzLnVzZURyYWdFbnRlciAmJiBldmVudC5kYXRhVHJhbnNmZXIpIHtcbiAgICAgIGlmICghdGhpcy5pc0RyYWdnaW5nT3ZlckRyb3Bab25lKSB7XG4gICAgICAgIHRoaXMuaXNEcmFnZ2luZ092ZXJEcm9wWm9uZSA9IHRydWU7XG4gICAgICAgIHRoaXMub25GaWxlT3Zlci5lbWl0KGV2ZW50KTtcbiAgICAgIH1cbiAgICAgIHRoaXMucHJldmVudEFuZFN0b3AoZXZlbnQpO1xuICAgICAgZXZlbnQuZGF0YVRyYW5zZmVyLmRyb3BFZmZlY3QgPSAnY29weSc7XG4gICAgfVxuICB9XG5cbiAgcHVibGljIG9uRHJhZ0VudGVyKGV2ZW50OiBFdmVudCk6IHZvaWQge1xuICAgIGlmICghdGhpcy5pc0Ryb3B6b25lRGlzYWJsZWQoKSAmJiB0aGlzLnVzZURyYWdFbnRlcikge1xuICAgICAgaWYgKCF0aGlzLmlzRHJhZ2dpbmdPdmVyRHJvcFpvbmUpIHtcbiAgICAgICAgdGhpcy5pc0RyYWdnaW5nT3ZlckRyb3Bab25lID0gdHJ1ZTtcbiAgICAgICAgdGhpcy5vbkZpbGVPdmVyLmVtaXQoZXZlbnQpO1xuICAgICAgfVxuICAgICAgdGhpcy5wcmV2ZW50QW5kU3RvcChldmVudCk7XG4gICAgfVxuICB9XG5cbiAgcHVibGljIG9uRHJhZ0xlYXZlKGV2ZW50OiBFdmVudCk6IHZvaWQge1xuICAgIGlmICghdGhpcy5pc0Ryb3B6b25lRGlzYWJsZWQoKSkge1xuICAgICAgaWYgKHRoaXMuaXNEcmFnZ2luZ092ZXJEcm9wWm9uZSkge1xuICAgICAgICB0aGlzLmlzRHJhZ2dpbmdPdmVyRHJvcFpvbmUgPSBmYWxzZTtcbiAgICAgICAgdGhpcy5vbkZpbGVMZWF2ZS5lbWl0KGV2ZW50KTtcbiAgICAgIH1cbiAgICAgIHRoaXMucHJldmVudEFuZFN0b3AoZXZlbnQpO1xuICAgIH1cbiAgfVxuXG4gIHB1YmxpYyBkcm9wRmlsZXMoZXZlbnQ6IERyYWdFdmVudCk6IHZvaWQge1xuICAgIGlmICh0aGlzLmlzRHJvcHpvbmVEaXNhYmxlZCgpKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIHRoaXMuaXNEcmFnZ2luZ092ZXJEcm9wWm9uZSA9IGZhbHNlO1xuICAgIGlmIChldmVudC5kYXRhVHJhbnNmZXIpIHtcbiAgICAgIGxldCBpdGVtczogRmlsZUxpc3QgfCBEYXRhVHJhbnNmZXJJdGVtTGlzdDtcbiAgICAgIGlmIChldmVudC5kYXRhVHJhbnNmZXIuaXRlbXMpIHtcbiAgICAgICAgaXRlbXMgPSBldmVudC5kYXRhVHJhbnNmZXIuaXRlbXM7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICBpdGVtcyA9IGV2ZW50LmRhdGFUcmFuc2Zlci5maWxlcztcbiAgICAgIH1cbiAgICAgIHRoaXMucHJldmVudEFuZFN0b3AoZXZlbnQpO1xuICAgICAgdGhpcy5jaGVja0ZpbGVzKGl0ZW1zKTtcbiAgICB9XG4gIH1cblxuICBwdWJsaWMgb3BlbkZpbGVTZWxlY3RvciA9IChldmVudD86IE1vdXNlRXZlbnQpOiB2b2lkID0+IHtcbiAgICBpZiAodGhpcy5maWxlU2VsZWN0b3IgJiYgdGhpcy5maWxlU2VsZWN0b3IubmF0aXZlRWxlbWVudCkge1xuICAgICAgKHRoaXMuZmlsZVNlbGVjdG9yLm5hdGl2ZUVsZW1lbnQgYXMgSFRNTElucHV0RWxlbWVudCkuY2xpY2soKTtcbiAgICB9XG4gIH07XG5cbiAgLyoqXG4gICAqIFByb2Nlc3NlcyB0aGUgY2hhbmdlIGV2ZW50IG9mIHRoZSBmaWxlIGlucHV0IGFuZCBhZGRzIHRoZSBnaXZlbiBmaWxlcy5cbiAgICogQHBhcmFtIEV2ZW50IGV2ZW50XG4gICAqL1xuICBwdWJsaWMgdXBsb2FkRmlsZXMoZXZlbnQ6IEV2ZW50KTogdm9pZCB7XG4gICAgaWYgKHRoaXMuaXNEcm9wem9uZURpc2FibGVkKCkpIHtcbiAgICAgIHJldHVybjtcbiAgICB9XG4gICAgaWYgKGV2ZW50LnRhcmdldCkge1xuICAgICAgY29uc3QgaXRlbXMgPSAoZXZlbnQudGFyZ2V0IGFzIEhUTUxJbnB1dEVsZW1lbnQpLmZpbGVzIHx8IChbXSBhcyBhbnkpO1xuICAgICAgdGhpcy5jaGVja0ZpbGVzKGl0ZW1zKTtcbiAgICAgIHRoaXMucmVzZXRGaWxlSW5wdXQoKTtcbiAgICB9XG4gIH1cblxuICBwcml2YXRlIGdldEZha2VEcm9wRW50cnkoZmlsZTogRmlsZSk6IE5neEZpbGVEcm9wRW50cnkge1xuICAgIGNvbnN0IGZha2VGaWxlRW50cnk6IEZpbGVTeXN0ZW1GaWxlRW50cnkgPSB7XG4gICAgICBuYW1lOiBmaWxlLm5hbWUsXG4gICAgICBpc0RpcmVjdG9yeTogZmFsc2UsXG4gICAgICBpc0ZpbGU6IHRydWUsXG4gICAgICBmaWxlOiA8VD4oY2FsbGJhY2s6IChmaWxlYTogRmlsZSkgPT4gVCkgPT4gY2FsbGJhY2soZmlsZSksXG4gICAgfTtcbiAgICByZXR1cm4gbmV3IE5neEZpbGVEcm9wRW50cnkoZmFrZUZpbGVFbnRyeS5uYW1lLCBmYWtlRmlsZUVudHJ5KTtcbiAgfVxuXG4gIHByaXZhdGUgY2hlY2tGaWxlKGl0ZW06IERhdGFUcmFuc2Zlckl0ZW0gfCBGaWxlKTogdm9pZCB7XG4gICAgaWYgKCFpdGVtKSB7XG4gICAgICByZXR1cm47XG4gICAgfVxuICAgIGlmIChpc0RhdGFUcmFuc2Zlckl0ZW0oaXRlbSkpIHtcbiAgICAgIGNvbnN0IGVudHJ5ID0gaXRlbS53ZWJraXRHZXRBc0VudHJ5KCk7XG4gICAgICBpZiAoZW50cnkpIHtcbiAgICAgICAgaWYgKGVudHJ5LmlzRmlsZSkge1xuICAgICAgICAgIGNvbnN0IHRvVXBsb2FkOiBOZ3hGaWxlRHJvcEVudHJ5ID0gbmV3IE5neEZpbGVEcm9wRW50cnkoZW50cnkubmFtZSwgZW50cnkpO1xuICAgICAgICAgIHRoaXMuYWRkVG9RdWV1ZSh0b1VwbG9hZCk7XG5cbiAgICAgICAgfSBlbHNlIGlmIChlbnRyeS5pc0RpcmVjdG9yeSkge1xuICAgICAgICAgIHRoaXMudHJhdmVyc2VGaWxlVHJlZShlbnRyeSwgZW50cnkubmFtZSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuXG4gICAgICBjb25zdCBmaWxlID0gaXRlbS5nZXRBc0ZpbGUoKTtcbiAgICAgIGlmIChmaWxlKSB7XG4gICAgICAgIHRoaXMuYWRkVG9RdWV1ZSh0aGlzLmdldEZha2VEcm9wRW50cnkoZmlsZSkpO1xuICAgICAgfVxuICAgICAgcmV0dXJuO1xuICAgIH1cbiAgICB0aGlzLmFkZFRvUXVldWUodGhpcy5nZXRGYWtlRHJvcEVudHJ5KGl0ZW0pKTtcbiAgfVxuXG4gIHByaXZhdGUgY2hlY2tGaWxlcyhpdGVtczogRmlsZUxpc3QgfCBEYXRhVHJhbnNmZXJJdGVtTGlzdCk6IHZvaWQge1xuICAgIGZvciAobGV0IGkgPSAwOyBpIDwgaXRlbXMubGVuZ3RoOyBpKyspIHtcbiAgICAgIHRoaXMuY2hlY2tGaWxlKGl0ZW1zW2ldKTtcbiAgICB9XG5cbiAgICBpZiAodGhpcy5kcm9wRXZlbnRUaW1lclN1YnNjcmlwdGlvbikge1xuICAgICAgdGhpcy5kcm9wRXZlbnRUaW1lclN1YnNjcmlwdGlvbi51bnN1YnNjcmliZSgpO1xuICAgIH1cbiAgICB0aGlzLmRyb3BFdmVudFRpbWVyU3Vic2NyaXB0aW9uID0gdGltZXIoMjAwLCAyMDApXG4gICAgICAuc3Vic2NyaWJlKCgpID0+IHtcbiAgICAgICAgaWYgKHRoaXMuZmlsZXMubGVuZ3RoID4gMCAmJiB0aGlzLm51bU9mQWN0aXZlUmVhZEVudHJpZXMgPT09IDApIHtcbiAgICAgICAgICBjb25zdCBmaWxlcyA9IHRoaXMuZmlsZXM7XG4gICAgICAgICAgdGhpcy5maWxlcyA9IFtdO1xuICAgICAgICAgIHRoaXMub25GaWxlRHJvcC5lbWl0KGZpbGVzKTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gIH1cblxuICBwcml2YXRlIHRyYXZlcnNlRmlsZVRyZWUoaXRlbTogRmlsZVN5c3RlbUVudHJ5LCBwYXRoOiBzdHJpbmcpOiB2b2lkIHtcbiAgICBpZiAoaXRlbS5pc0ZpbGUpIHtcbiAgICAgIGNvbnN0IHRvVXBsb2FkOiBOZ3hGaWxlRHJvcEVudHJ5ID0gbmV3IE5neEZpbGVEcm9wRW50cnkocGF0aCwgaXRlbSk7XG4gICAgICB0aGlzLmZpbGVzLnB1c2godG9VcGxvYWQpO1xuXG4gICAgfSBlbHNlIHtcbiAgICAgIHBhdGggPSBwYXRoICsgJy8nO1xuICAgICAgY29uc3QgZGlyUmVhZGVyID0gKGl0ZW0gYXMgRmlsZVN5c3RlbURpcmVjdG9yeUVudHJ5KS5jcmVhdGVSZWFkZXIoKTtcbiAgICAgIGxldCBlbnRyaWVzOiBGaWxlU3lzdGVtRW50cnlbXSA9IFtdO1xuXG4gICAgICBjb25zdCByZWFkRW50cmllcyA9ICgpID0+IHtcbiAgICAgICAgdGhpcy5udW1PZkFjdGl2ZVJlYWRFbnRyaWVzKys7XG4gICAgICAgIGRpclJlYWRlci5yZWFkRW50cmllcygocmVzdWx0KSA9PiB7XG4gICAgICAgICAgaWYgKCFyZXN1bHQubGVuZ3RoKSB7XG4gICAgICAgICAgICAvLyBhZGQgZW1wdHkgZm9sZGVyc1xuICAgICAgICAgICAgaWYgKGVudHJpZXMubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgICAgIGNvbnN0IHRvVXBsb2FkOiBOZ3hGaWxlRHJvcEVudHJ5ID0gbmV3IE5neEZpbGVEcm9wRW50cnkocGF0aCwgaXRlbSk7XG4gICAgICAgICAgICAgIHRoaXMuem9uZS5ydW4oKCkgPT4ge1xuICAgICAgICAgICAgICAgIHRoaXMuYWRkVG9RdWV1ZSh0b1VwbG9hZCk7XG4gICAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgICBmb3IgKGxldCBpID0gMDsgaSA8IGVudHJpZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgICAgICAgICB0aGlzLnpvbmUucnVuKCgpID0+IHtcbiAgICAgICAgICAgICAgICAgIHRoaXMudHJhdmVyc2VGaWxlVHJlZShlbnRyaWVzW2ldLCBwYXRoICsgZW50cmllc1tpXS5uYW1lKTtcbiAgICAgICAgICAgICAgICB9KTtcbiAgICAgICAgICAgICAgfVxuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIC8vIGNvbnRpbnVlIHdpdGggdGhlIHJlYWRpbmdcbiAgICAgICAgICAgIGVudHJpZXMgPSBlbnRyaWVzLmNvbmNhdChyZXN1bHQpO1xuICAgICAgICAgICAgcmVhZEVudHJpZXMoKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICB0aGlzLm51bU9mQWN0aXZlUmVhZEVudHJpZXMtLTtcbiAgICAgICAgfSk7XG4gICAgICB9O1xuXG4gICAgICByZWFkRW50cmllcygpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBDbGVhcnMgYW55IGFkZGVkIGZpbGVzIGZyb20gdGhlIGZpbGUgaW5wdXQgZWxlbWVudCBzbyB0aGUgc2FtZSBmaWxlIGNhbiBzdWJzZXF1ZW50bHkgYmUgYWRkZWQgbXVsdGlwbGUgdGltZXMuXG4gICAqL1xuICBwcml2YXRlIHJlc2V0RmlsZUlucHV0KCk6IHZvaWQge1xuICAgIGlmICh0aGlzLmZpbGVTZWxlY3RvciAmJiB0aGlzLmZpbGVTZWxlY3Rvci5uYXRpdmVFbGVtZW50KSB7XG4gICAgICBjb25zdCBmaWxlSW5wdXRFbCA9IHRoaXMuZmlsZVNlbGVjdG9yLm5hdGl2ZUVsZW1lbnQgYXMgSFRNTElucHV0RWxlbWVudDtcbiAgICAgIGNvbnN0IGZpbGVJbnB1dENvbnRhaW5lckVsID0gZmlsZUlucHV0RWwucGFyZW50RWxlbWVudDtcbiAgICAgIGNvbnN0IGhlbHBlckZvcm1FbCA9IHRoaXMuZ2V0SGVscGVyRm9ybUVsZW1lbnQoKTtcbiAgICAgIGNvbnN0IGZpbGVJbnB1dFBsYWNlaG9sZGVyRWwgPSB0aGlzLmdldEZpbGVJbnB1dFBsYWNlaG9sZGVyRWxlbWVudCgpO1xuXG4gICAgICAvLyBKdXN0IGEgcXVpY2sgY2hlY2sgc28gd2UgZG8gbm90IG1lc3MgdXAgdGhlIERPTSAod2lsbCBuZXZlciBoYXBwZW4gdGhvdWdoKS5cbiAgICAgIGlmIChmaWxlSW5wdXRDb250YWluZXJFbCAhPT0gaGVscGVyRm9ybUVsKSB7XG4gICAgICAgIC8vIEluc2VydCB0aGUgZm9ybSBpbnB1dCBwbGFjZWhvbGRlciBpbiB0aGUgRE9NIGJlZm9yZSB0aGUgZm9ybSBpbnB1dCBlbGVtZW50LlxuICAgICAgICB0aGlzLnJlbmRlcmVyLmluc2VydEJlZm9yZShmaWxlSW5wdXRDb250YWluZXJFbCwgZmlsZUlucHV0UGxhY2Vob2xkZXJFbCwgZmlsZUlucHV0RWwpO1xuICAgICAgICAvLyBBZGQgdGhlIGZvcm0gaW5wdXQgYXMgY2hpbGQgb2YgdGhlIHRlbXBvcmFyeSBmb3JtIGVsZW1lbnQsIHJlbW92aW5nIHRoZSBmb3JtIGlucHV0IGZyb20gdGhlIERPTS5cbiAgICAgICAgdGhpcy5yZW5kZXJlci5hcHBlbmRDaGlsZChoZWxwZXJGb3JtRWwsIGZpbGVJbnB1dEVsKTtcbiAgICAgICAgLy8gUmVzZXQgdGhlIGZvcm0sIHRodXMgY2xlYXJpbmcgdGhlIGlucHV0IGVsZW1lbnQgb2YgYW55IGZpbGVzLlxuICAgICAgICBoZWxwZXJGb3JtRWwucmVzZXQoKTtcbiAgICAgICAgLy8gQWRkIHRoZSBmaWxlIGlucHV0IGJhY2sgdG8gdGhlIERPTSBpbiBwbGFjZSBvZiB0aGUgZmlsZSBpbnB1dCBwbGFjZWhvbGRlciBlbGVtZW50LlxuICAgICAgICB0aGlzLnJlbmRlcmVyLmluc2VydEJlZm9yZShmaWxlSW5wdXRDb250YWluZXJFbCwgZmlsZUlucHV0RWwsIGZpbGVJbnB1dFBsYWNlaG9sZGVyRWwpO1xuICAgICAgICAvLyBSZW1vdmUgdGhlIGlucHV0IHBsYWNlaG9sZGVyIGZyb20gdGhlIERPTVxuICAgICAgICB0aGlzLnJlbmRlcmVyLnJlbW92ZUNoaWxkKGZpbGVJbnB1dENvbnRhaW5lckVsLCBmaWxlSW5wdXRQbGFjZWhvbGRlckVsKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogR2V0IGEgY2FjaGVkIEhUTUwgZm9ybSBlbGVtZW50IGFzIGEgaGVscGVyIGVsZW1lbnQgdG8gY2xlYXIgdGhlIGZpbGUgaW5wdXQgZWxlbWVudC5cbiAgICovXG4gIHByaXZhdGUgZ2V0SGVscGVyRm9ybUVsZW1lbnQoKTogSFRNTEZvcm1FbGVtZW50IHtcbiAgICBpZiAoIXRoaXMuaGVscGVyRm9ybUVsKSB7XG4gICAgICB0aGlzLmhlbHBlckZvcm1FbCA9IHRoaXMucmVuZGVyZXIuY3JlYXRlRWxlbWVudCgnZm9ybScpIGFzIEhUTUxGb3JtRWxlbWVudDtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcy5oZWxwZXJGb3JtRWw7XG4gIH1cblxuICAvKipcbiAgICogR2V0IGEgY2FjaGVkIEhUTUwgZGl2IGVsZW1lbnQgdG8gYmUgdXNlZCBhcyBwbGFjZWhvbGRlciBmb3IgdGhlIGZpbGUgaW5wdXQgZWxlbWVudCB3aGVuIGNsZWFyaW5nIHNhaWQgZWxlbWVudC5cbiAgICovXG4gIHByaXZhdGUgZ2V0RmlsZUlucHV0UGxhY2Vob2xkZXJFbGVtZW50KCk6IEhUTUxEaXZFbGVtZW50IHtcbiAgICBpZiAoIXRoaXMuZmlsZUlucHV0UGxhY2Vob2xkZXJFbCkge1xuICAgICAgdGhpcy5maWxlSW5wdXRQbGFjZWhvbGRlckVsID0gdGhpcy5yZW5kZXJlci5jcmVhdGVFbGVtZW50KCdkaXYnKSBhcyBIVE1MRGl2RWxlbWVudDtcbiAgICB9XG5cbiAgICByZXR1cm4gdGhpcy5maWxlSW5wdXRQbGFjZWhvbGRlckVsO1xuICB9XG5cbiAgcHJpdmF0ZSBpc0Ryb3B6b25lRGlzYWJsZWQoKTogYm9vbGVhbiB7XG4gICAgcmV0dXJuICh0aGlzLmdsb2JhbERyYWdnaW5nSW5Qcm9ncmVzcyB8fCB0aGlzLmRpc2FibGVkKTtcbiAgfVxuXG4gIHByaXZhdGUgYWRkVG9RdWV1ZShpdGVtOiBOZ3hGaWxlRHJvcEVudHJ5KTogdm9pZCB7XG4gICAgdGhpcy5maWxlcy5wdXNoKGl0ZW0pO1xuICB9XG5cbiAgcHJpdmF0ZSBwcmV2ZW50QW5kU3RvcChldmVudDogRXZlbnQpOiB2b2lkIHtcbiAgICBldmVudC5zdG9wUHJvcGFnYXRpb24oKTtcbiAgICBldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuICB9XG59XG4iLCI8ZGl2IFtjbGFzc05hbWVdPVwiZHJvcFpvbmVDbGFzc05hbWVcIlxuICAgICBbY2xhc3Mubmd4LWZpbGUtZHJvcF9fZHJvcC16b25lLS1vdmVyXT1cImlzRHJhZ2dpbmdPdmVyRHJvcFpvbmVcIlxuICAgICAoZHJvcCk9XCJkcm9wRmlsZXMoJGV2ZW50KVwiXG4gICAgIChkcmFnb3Zlcik9XCJvbkRyYWdPdmVyKCRldmVudClcIlxuICAgICAoZHJhZ2VudGVyKT1cIm9uRHJhZ0VudGVyKCRldmVudClcIlxuICAgICAoZHJhZ2xlYXZlKT1cIm9uRHJhZ0xlYXZlKCRldmVudClcIj5cbiAgPGRpdiBbY2xhc3NOYW1lXT1cImNvbnRlbnRDbGFzc05hbWVcIj5cbiAgICA8aW5wdXQgXG4gICAgICB0eXBlPVwiZmlsZVwiIFxuICAgICAgI2ZpbGVTZWxlY3RvciBcbiAgICAgIFthY2NlcHRdPVwiYWNjZXB0XCIgXG4gICAgICBbYXR0ci5kaXJlY3RvcnldPVwiZGlyZWN0b3J5IHx8IHVuZGVmaW5lZFwiIFxuICAgICAgW2F0dHIud2Via2l0ZGlyZWN0b3J5XT1cImRpcmVjdG9yeSB8fCB1bmRlZmluZWRcIlxuICAgICAgW2F0dHIubW96ZGlyZWN0b3J5XT1cImRpcmVjdG9yeSB8fCB1bmRlZmluZWRcIlxuICAgICAgW2F0dHIubXNkaXJlY3RvcnldPVwiZGlyZWN0b3J5IHx8IHVuZGVmaW5lZFwiXG4gICAgICBbYXR0ci5vZGlyZWN0b3J5XT1cImRpcmVjdG9yeSB8fCB1bmRlZmluZWRcIlxuICAgICAgW211bHRpcGxlXT1cIm11bHRpcGxlXCJcbiAgICAgIChjaGFuZ2UpPVwidXBsb2FkRmlsZXMoJGV2ZW50KVwiIFxuICAgICAgY2xhc3M9XCJuZ3gtZmlsZS1kcm9wX19maWxlLWlucHV0XCIgXG4gICAgLz5cblxuICAgIDxuZy10ZW1wbGF0ZSAjZGVmYXVsdENvbnRlbnRUZW1wbGF0ZT5cbiAgICAgIDxkaXYgKm5nSWY9XCJkcm9wWm9uZUxhYmVsXCIgY2xhc3M9XCJuZ3gtZmlsZS1kcm9wX19kcm9wLXpvbmUtbGFiZWxcIj57e2Ryb3Bab25lTGFiZWx9fTwvZGl2PlxuICAgICAgPGRpdiAqbmdJZj1cInNob3dCcm93c2VCdG5cIj5cbiAgICAgICAgPGlucHV0IHR5cGU9XCJidXR0b25cIiBbY2xhc3NOYW1lXT1cImJyb3dzZUJ0bkNsYXNzTmFtZVwiIHZhbHVlPVwie3ticm93c2VCdG5MYWJlbH19XCIgKGNsaWNrKT1cIm9wZW5GaWxlU2VsZWN0b3IoJGV2ZW50KVwiIC8+XG4gICAgICA8L2Rpdj5cbiAgICA8L25nLXRlbXBsYXRlPlxuXG4gICAgPG5nLXRlbXBsYXRlXG4gICAgICBbbmdUZW1wbGF0ZU91dGxldF09XCJjb250ZW50VGVtcGxhdGUgfHwgZGVmYXVsdENvbnRlbnRUZW1wbGF0ZVwiXG4gICAgICBbbmdUZW1wbGF0ZU91dGxldENvbnRleHRdPVwieyBvcGVuRmlsZVNlbGVjdG9yOiBvcGVuRmlsZVNlbGVjdG9yIH1cIj5cbiAgICA8L25nLXRlbXBsYXRlPlxuICA8L2Rpdj5cbjwvZGl2PlxuIl19
+\ No newline at end of file
+diff --git a/node_modules/ngx-file-drop/esm2022/lib/ngx-file-drop.module.mjs b/node_modules/ngx-file-drop/esm2022/lib/ngx-file-drop.module.mjs
+index 20a8d2d..7023af4 100644
+--- a/node_modules/ngx-file-drop/esm2022/lib/ngx-file-drop.module.mjs
++++ b/node_modules/ngx-file-drop/esm2022/lib/ngx-file-drop.module.mjs
+@@ -30,4 +30,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImpor
+                     ],
+                 }]
+         }] });
+-//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LWZpbGUtZHJvcC5tb2R1bGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtZmlsZS1kcm9wL3NyYy9saWIvbmd4LWZpbGUtZHJvcC5tb2R1bGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDL0MsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDakUsT0FBTyxFQUFFLG1DQUFtQyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7O0FBbUJoRixNQUFNLE9BQU8saUJBQWlCOzhHQUFqQixpQkFBaUI7K0dBQWpCLGlCQUFpQixjQUgxQixvQkFBb0Isa0JBWnBCLG9CQUFvQjtZQUNwQixtQ0FBbUMsYUFHbkMsWUFBWSxhQUdaLG9CQUFvQjtZQUNwQixtQ0FBbUM7K0dBTzFCLGlCQUFpQixZQVgxQixZQUFZOzsyRkFXSCxpQkFBaUI7a0JBakI3QixRQUFRO21CQUFDO29CQUNSLFlBQVksRUFBRTt3QkFDWixvQkFBb0I7d0JBQ3BCLG1DQUFtQztxQkFDcEM7b0JBQ0QsT0FBTyxFQUFFO3dCQUNQLFlBQVk7cUJBQ2I7b0JBQ0QsT0FBTyxFQUFFO3dCQUNQLG9CQUFvQjt3QkFDcEIsbUNBQW1DO3FCQUNwQztvQkFDRCxTQUFTLEVBQUUsRUFBRTtvQkFDYixTQUFTLEVBQUU7d0JBQ1Qsb0JBQW9CO3FCQUNyQjtpQkFDRiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IE5nTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XHJcbmltcG9ydCB7IENvbW1vbk1vZHVsZSB9IGZyb20gJ0Bhbmd1bGFyL2NvbW1vbic7XHJcbmltcG9ydCB7IE5neEZpbGVEcm9wQ29tcG9uZW50IH0gZnJvbSAnLi9uZ3gtZmlsZS1kcm9wLmNvbXBvbmVudCc7XHJcbmltcG9ydCB7IE5neEZpbGVEcm9wQ29udGVudFRlbXBsYXRlRGlyZWN0aXZlIH0gZnJvbSAnLi9uZ3gtdGVtcGxhdGVzLmRpcmVjdGl2ZSc7XHJcblxyXG5ATmdNb2R1bGUoe1xyXG4gIGRlY2xhcmF0aW9uczogW1xyXG4gICAgTmd4RmlsZURyb3BDb21wb25lbnQsXHJcbiAgICBOZ3hGaWxlRHJvcENvbnRlbnRUZW1wbGF0ZURpcmVjdGl2ZSxcclxuICBdLFxyXG4gIGltcG9ydHM6IFtcclxuICAgIENvbW1vbk1vZHVsZVxyXG4gIF0sXHJcbiAgZXhwb3J0czogW1xyXG4gICAgTmd4RmlsZURyb3BDb21wb25lbnQsXHJcbiAgICBOZ3hGaWxlRHJvcENvbnRlbnRUZW1wbGF0ZURpcmVjdGl2ZSxcclxuICBdLFxyXG4gIHByb3ZpZGVyczogW10sXHJcbiAgYm9vdHN0cmFwOiBbXHJcbiAgICBOZ3hGaWxlRHJvcENvbXBvbmVudFxyXG4gIF0sXHJcbn0pXHJcbmV4cG9ydCBjbGFzcyBOZ3hGaWxlRHJvcE1vZHVsZSB7fVxyXG4iXX0=
+\ No newline at end of file
++//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LWZpbGUtZHJvcC5tb2R1bGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtZmlsZS1kcm9wL3NyYy9saWIvbmd4LWZpbGUtZHJvcC5tb2R1bGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFFBQVEsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUN6QyxPQUFPLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFDL0MsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFDakUsT0FBTyxFQUFFLG1DQUFtQyxFQUFFLE1BQU0sMkJBQTJCLENBQUM7O0FBbUJoRixNQUFNLE9BQU8saUJBQWlCOzhHQUFqQixpQkFBaUI7K0dBQWpCLGlCQUFpQixjQUgxQixvQkFBb0Isa0JBWnBCLG9CQUFvQjtZQUNwQixtQ0FBbUMsYUFHbkMsWUFBWSxhQUdaLG9CQUFvQjtZQUNwQixtQ0FBbUM7K0dBTzFCLGlCQUFpQixZQVgxQixZQUFZOzsyRkFXSCxpQkFBaUI7a0JBakI3QixRQUFRO21CQUFDO29CQUNSLFlBQVksRUFBRTt3QkFDWixvQkFBb0I7d0JBQ3BCLG1DQUFtQztxQkFDcEM7b0JBQ0QsT0FBTyxFQUFFO3dCQUNQLFlBQVk7cUJBQ2I7b0JBQ0QsT0FBTyxFQUFFO3dCQUNQLG9CQUFvQjt3QkFDcEIsbUNBQW1DO3FCQUNwQztvQkFDRCxTQUFTLEVBQUUsRUFBRTtvQkFDYixTQUFTLEVBQUU7d0JBQ1Qsb0JBQW9CO3FCQUNyQjtpQkFDRiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IE5nTW9kdWxlIH0gZnJvbSAnQGFuZ3VsYXIvY29yZSc7XG5pbXBvcnQgeyBDb21tb25Nb2R1bGUgfSBmcm9tICdAYW5ndWxhci9jb21tb24nO1xuaW1wb3J0IHsgTmd4RmlsZURyb3BDb21wb25lbnQgfSBmcm9tICcuL25neC1maWxlLWRyb3AuY29tcG9uZW50JztcbmltcG9ydCB7IE5neEZpbGVEcm9wQ29udGVudFRlbXBsYXRlRGlyZWN0aXZlIH0gZnJvbSAnLi9uZ3gtdGVtcGxhdGVzLmRpcmVjdGl2ZSc7XG5cbkBOZ01vZHVsZSh7XG4gIGRlY2xhcmF0aW9uczogW1xuICAgIE5neEZpbGVEcm9wQ29tcG9uZW50LFxuICAgIE5neEZpbGVEcm9wQ29udGVudFRlbXBsYXRlRGlyZWN0aXZlLFxuICBdLFxuICBpbXBvcnRzOiBbXG4gICAgQ29tbW9uTW9kdWxlXG4gIF0sXG4gIGV4cG9ydHM6IFtcbiAgICBOZ3hGaWxlRHJvcENvbXBvbmVudCxcbiAgICBOZ3hGaWxlRHJvcENvbnRlbnRUZW1wbGF0ZURpcmVjdGl2ZSxcbiAgXSxcbiAgcHJvdmlkZXJzOiBbXSxcbiAgYm9vdHN0cmFwOiBbXG4gICAgTmd4RmlsZURyb3BDb21wb25lbnRcbiAgXSxcbn0pXG5leHBvcnQgY2xhc3MgTmd4RmlsZURyb3BNb2R1bGUge31cbiJdfQ==
+\ No newline at end of file
+diff --git a/node_modules/ngx-file-drop/esm2022/lib/ngx-templates.directive.mjs b/node_modules/ngx-file-drop/esm2022/lib/ngx-templates.directive.mjs
+index 6690f3c..efc36be 100644
+--- a/node_modules/ngx-file-drop/esm2022/lib/ngx-templates.directive.mjs
++++ b/node_modules/ngx-file-drop/esm2022/lib/ngx-templates.directive.mjs
+@@ -11,4 +11,4 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImpor
+             type: Directive,
+             args: [{ selector: '[ngx-file-drop-content-tmp]' }]
+         }], ctorParameters: function () { return [{ type: i0.TemplateRef }]; } });
+-//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LXRlbXBsYXRlcy5kaXJlY3RpdmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtZmlsZS1kcm9wL3NyYy9saWIvbmd4LXRlbXBsYXRlcy5kaXJlY3RpdmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBZSxNQUFNLGVBQWUsQ0FBQzs7QUFHdkQsTUFBTSxPQUFPLG1DQUFtQztJQUM5QyxZQUFtQixRQUEwQjtRQUExQixhQUFRLEdBQVIsUUFBUSxDQUFrQjtJQUFJLENBQUM7OEdBRHZDLG1DQUFtQztrR0FBbkMsbUNBQW1DOzsyRkFBbkMsbUNBQW1DO2tCQUQvQyxTQUFTO21CQUFDLEVBQUUsUUFBUSxFQUFFLDZCQUE2QixFQUFFIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRGlyZWN0aXZlLCBUZW1wbGF0ZVJlZiB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xyXG5cclxuQERpcmVjdGl2ZSh7IHNlbGVjdG9yOiAnW25neC1maWxlLWRyb3AtY29udGVudC10bXBdJyB9KVxyXG5leHBvcnQgY2xhc3MgTmd4RmlsZURyb3BDb250ZW50VGVtcGxhdGVEaXJlY3RpdmUge1xyXG4gIGNvbnN0cnVjdG9yKHB1YmxpYyB0ZW1wbGF0ZTogVGVtcGxhdGVSZWY8YW55PikgeyB9XHJcbn1cclxuIl19
+\ No newline at end of file
++//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibmd4LXRlbXBsYXRlcy5kaXJlY3RpdmUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9wcm9qZWN0cy9uZ3gtZmlsZS1kcm9wL3NyYy9saWIvbmd4LXRlbXBsYXRlcy5kaXJlY3RpdmUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBZSxNQUFNLGVBQWUsQ0FBQzs7QUFHdkQsTUFBTSxPQUFPLG1DQUFtQztJQUM5QyxZQUFtQixRQUEwQjtRQUExQixhQUFRLEdBQVIsUUFBUSxDQUFrQjtJQUFJLENBQUM7OEdBRHZDLG1DQUFtQztrR0FBbkMsbUNBQW1DOzsyRkFBbkMsbUNBQW1DO2tCQUQvQyxTQUFTO21CQUFDLEVBQUUsUUFBUSxFQUFFLDZCQUE2QixFQUFFIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgRGlyZWN0aXZlLCBUZW1wbGF0ZVJlZiB9IGZyb20gJ0Bhbmd1bGFyL2NvcmUnO1xuXG5ARGlyZWN0aXZlKHsgc2VsZWN0b3I6ICdbbmd4LWZpbGUtZHJvcC1jb250ZW50LXRtcF0nIH0pXG5leHBvcnQgY2xhc3MgTmd4RmlsZURyb3BDb250ZW50VGVtcGxhdGVEaXJlY3RpdmUge1xuICBjb25zdHJ1Y3RvcihwdWJsaWMgdGVtcGxhdGU6IFRlbXBsYXRlUmVmPGFueT4pIHsgfVxufVxuIl19
+\ No newline at end of file
+diff --git a/node_modules/ngx-file-drop/esm2022/public-api.mjs b/node_modules/ngx-file-drop/esm2022/public-api.mjs
+index dff97b4..0c806ee 100644
+--- a/node_modules/ngx-file-drop/esm2022/public-api.mjs
++++ b/node_modules/ngx-file-drop/esm2022/public-api.mjs
+@@ -2,4 +2,4 @@ export { NgxFileDropComponent } from './lib/ngx-file-drop.component';
+ export { NgxFileDropModule } from './lib/ngx-file-drop.module';
+ export { NgxFileDropEntry } from './lib/ngx-file-drop-entry';
+ export { NgxFileDropContentTemplateDirective } from './lib/ngx-templates.directive';
+-//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Byb2plY3RzL25neC1maWxlLWRyb3Avc3JjL3B1YmxpYy1hcGkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDckUsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDL0QsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFN0QsT0FBTyxFQUFFLG1DQUFtQyxFQUFFLE1BQU0sK0JBQStCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgeyBOZ3hGaWxlRHJvcENvbXBvbmVudCB9IGZyb20gJy4vbGliL25neC1maWxlLWRyb3AuY29tcG9uZW50JztcclxuZXhwb3J0IHsgTmd4RmlsZURyb3BNb2R1bGUgfSBmcm9tICcuL2xpYi9uZ3gtZmlsZS1kcm9wLm1vZHVsZSc7XHJcbmV4cG9ydCB7IE5neEZpbGVEcm9wRW50cnkgfSBmcm9tICcuL2xpYi9uZ3gtZmlsZS1kcm9wLWVudHJ5JztcclxuZXhwb3J0IHsgRmlsZVN5c3RlbUVudHJ5LCBGaWxlU3lzdGVtRGlyZWN0b3J5RW50cnksIEZpbGVTeXN0ZW1GaWxlRW50cnkgfSBmcm9tICcuL2xpYi9kb20udHlwZXMnO1xyXG5leHBvcnQgeyBOZ3hGaWxlRHJvcENvbnRlbnRUZW1wbGF0ZURpcmVjdGl2ZSB9IGZyb20gJy4vbGliL25neC10ZW1wbGF0ZXMuZGlyZWN0aXZlJztcclxuIl19
+\ No newline at end of file
++//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHVibGljLWFwaS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3Byb2plY3RzL25neC1maWxlLWRyb3Avc3JjL3B1YmxpYy1hcGkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLG9CQUFvQixFQUFFLE1BQU0sK0JBQStCLENBQUM7QUFDckUsT0FBTyxFQUFFLGlCQUFpQixFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDL0QsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0sMkJBQTJCLENBQUM7QUFFN0QsT0FBTyxFQUFFLG1DQUFtQyxFQUFFLE1BQU0sK0JBQStCLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgeyBOZ3hGaWxlRHJvcENvbXBvbmVudCB9IGZyb20gJy4vbGliL25neC1maWxlLWRyb3AuY29tcG9uZW50JztcbmV4cG9ydCB7IE5neEZpbGVEcm9wTW9kdWxlIH0gZnJvbSAnLi9saWIvbmd4LWZpbGUtZHJvcC5tb2R1bGUnO1xuZXhwb3J0IHsgTmd4RmlsZURyb3BFbnRyeSB9IGZyb20gJy4vbGliL25neC1maWxlLWRyb3AtZW50cnknO1xuZXhwb3J0IHsgRmlsZVN5c3RlbUVudHJ5LCBGaWxlU3lzdGVtRGlyZWN0b3J5RW50cnksIEZpbGVTeXN0ZW1GaWxlRW50cnkgfSBmcm9tICcuL2xpYi9kb20udHlwZXMnO1xuZXhwb3J0IHsgTmd4RmlsZURyb3BDb250ZW50VGVtcGxhdGVEaXJlY3RpdmUgfSBmcm9tICcuL2xpYi9uZ3gtdGVtcGxhdGVzLmRpcmVjdGl2ZSc7XG4iXX0=
+\ No newline at end of file
+diff --git a/node_modules/ngx-file-drop/fesm2022/ngx-file-drop.mjs b/node_modules/ngx-file-drop/fesm2022/ngx-file-drop.mjs
+index 0cdc4a2..54fd9d8 100644
+--- a/node_modules/ngx-file-drop/fesm2022/ngx-file-drop.mjs
++++ b/node_modules/ngx-file-drop/fesm2022/ngx-file-drop.mjs
+@@ -16,6 +16,10 @@ class NgxFileDropEntry {
+     }
+ }
+
++function isDataTransferItem(item) {
++    return "webkitGetAsEntry" in item && "getAsFile" in item;
++}
++
+ class NgxFileDropContentTemplateDirective {
+     constructor(template) {
+         this.template = template;
+@@ -158,17 +162,8 @@ class NgxFileDropComponent {
+         if (!item) {
+             return;
+         }
+-        // if ("getAsFile" in item) {
+-        //   const file = item.getAsFile();
+-        //   if (file) {
+-        //     this.addToQueue(
+-        //       this.getFakeDropEntry(file)
+-        //     );
+-        //     return;
+-        //   }
+-        // }
+-        if ("webkitGetAsEntry" in item) {
+-            let entry = item.webkitGetAsEntry();
++        if (isDataTransferItem(item)) {
++            const entry = item.webkitGetAsEntry();
+             if (entry) {
+                 if (entry.isFile) {
+                     const toUpload = new NgxFileDropEntry(entry.name, entry);
+@@ -179,6 +174,11 @@ class NgxFileDropComponent {
+                 }
+                 return;
+             }
++            const file = item.getAsFile();
++            if (file) {
++                this.addToQueue(this.getFakeDropEntry(file));
++            }
++            return;
+         }
+         this.addToQueue(this.getFakeDropEntry(item));
+     }
+@@ -290,11 +290,11 @@ class NgxFileDropComponent {
+         event.preventDefault();
+     }
+     static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgxFileDropComponent, deps: [{ token: i0.NgZone }, { token: i0.Renderer2 }], target: i0.ɵɵFactoryTarget.Component }); }
+-    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.1", type: NgxFileDropComponent, selector: "ngx-file-drop", inputs: { accept: "accept", directory: "directory", multiple: "multiple", dropZoneLabel: "dropZoneLabel", dropZoneClassName: "dropZoneClassName", useDragEnter: "useDragEnter", contentClassName: "contentClassName", showBrowseBtn: "showBrowseBtn", browseBtnClassName: "browseBtnClassName", browseBtnLabel: "browseBtnLabel", disabled: "disabled" }, outputs: { onFileDrop: "onFileDrop", onFileOver: "onFileOver", onFileLeave: "onFileLeave" }, queries: [{ propertyName: "contentTemplate", first: true, predicate: NgxFileDropContentTemplateDirective, descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "fileSelector", first: true, predicate: ["fileSelector"], descendants: true, static: true }], ngImport: i0, template: "<div [className]=\"dropZoneClassName\"\r\n     [class.ngx-file-drop__drop-zone--over]=\"isDraggingOverDropZone\"\r\n     (drop)=\"dropFiles($event)\"\r\n     (dragover)=\"onDragOver($event)\"\r\n     (dragenter)=\"onDragEnter($event)\"\r\n     (dragleave)=\"onDragLeave($event)\">\r\n  <div [className]=\"contentClassName\">\r\n    <input \r\n      type=\"file\" \r\n      #fileSelector \r\n      [accept]=\"accept\" \r\n      [attr.directory]=\"directory || undefined\" \r\n      [attr.webkitdirectory]=\"directory || undefined\"\r\n      [attr.mozdirectory]=\"directory || undefined\"\r\n      [attr.msdirectory]=\"directory || undefined\"\r\n      [attr.odirectory]=\"directory || undefined\"\r\n      [multiple]=\"multiple\"\r\n      (change)=\"uploadFiles($event)\" \r\n      class=\"ngx-file-drop__file-input\" \r\n    />\r\n\r\n    <ng-template #defaultContentTemplate>\r\n      <div *ngIf=\"dropZoneLabel\" class=\"ngx-file-drop__drop-zone-label\">{{dropZoneLabel}}</div>\r\n      <div *ngIf=\"showBrowseBtn\">\r\n        <input type=\"button\" [className]=\"browseBtnClassName\" value=\"{{browseBtnLabel}}\" (click)=\"openFileSelector($event)\" />\r\n      </div>\r\n    </ng-template>\r\n\r\n    <ng-template\r\n      [ngTemplateOutlet]=\"contentTemplate || defaultContentTemplate\"\r\n      [ngTemplateOutletContext]=\"{ openFileSelector: openFileSelector }\">\r\n    </ng-template>\r\n  </div>\r\n</div>\r\n", styles: [".ngx-file-drop__drop-zone{height:100px;margin:auto;border:2px dotted #0782d0;border-radius:30px}.ngx-file-drop__drop-zone--over{background-color:#93939380}.ngx-file-drop__content{display:flex;align-items:center;justify-content:center;height:100px;color:#0782d0}.ngx-file-drop__drop-zone-label{text-align:center}.ngx-file-drop__file-input{display:none}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); }
++    static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "16.1.1", type: NgxFileDropComponent, selector: "ngx-file-drop", inputs: { accept: "accept", directory: "directory", multiple: "multiple", dropZoneLabel: "dropZoneLabel", dropZoneClassName: "dropZoneClassName", useDragEnter: "useDragEnter", contentClassName: "contentClassName", showBrowseBtn: "showBrowseBtn", browseBtnClassName: "browseBtnClassName", browseBtnLabel: "browseBtnLabel", disabled: "disabled" }, outputs: { onFileDrop: "onFileDrop", onFileOver: "onFileOver", onFileLeave: "onFileLeave" }, queries: [{ propertyName: "contentTemplate", first: true, predicate: NgxFileDropContentTemplateDirective, descendants: true, read: TemplateRef }], viewQueries: [{ propertyName: "fileSelector", first: true, predicate: ["fileSelector"], descendants: true, static: true }], ngImport: i0, template: "<div [className]=\"dropZoneClassName\"\n     [class.ngx-file-drop__drop-zone--over]=\"isDraggingOverDropZone\"\n     (drop)=\"dropFiles($event)\"\n     (dragover)=\"onDragOver($event)\"\n     (dragenter)=\"onDragEnter($event)\"\n     (dragleave)=\"onDragLeave($event)\">\n  <div [className]=\"contentClassName\">\n    <input \n      type=\"file\" \n      #fileSelector \n      [accept]=\"accept\" \n      [attr.directory]=\"directory || undefined\" \n      [attr.webkitdirectory]=\"directory || undefined\"\n      [attr.mozdirectory]=\"directory || undefined\"\n      [attr.msdirectory]=\"directory || undefined\"\n      [attr.odirectory]=\"directory || undefined\"\n      [multiple]=\"multiple\"\n      (change)=\"uploadFiles($event)\" \n      class=\"ngx-file-drop__file-input\" \n    />\n\n    <ng-template #defaultContentTemplate>\n      <div *ngIf=\"dropZoneLabel\" class=\"ngx-file-drop__drop-zone-label\">{{dropZoneLabel}}</div>\n      <div *ngIf=\"showBrowseBtn\">\n        <input type=\"button\" [className]=\"browseBtnClassName\" value=\"{{browseBtnLabel}}\" (click)=\"openFileSelector($event)\" />\n      </div>\n    </ng-template>\n\n    <ng-template\n      [ngTemplateOutlet]=\"contentTemplate || defaultContentTemplate\"\n      [ngTemplateOutletContext]=\"{ openFileSelector: openFileSelector }\">\n    </ng-template>\n  </div>\n</div>\n", styles: [".ngx-file-drop__drop-zone{height:100px;margin:auto;border:2px dotted #0782d0;border-radius:30px}.ngx-file-drop__drop-zone--over{background-color:#93939380}.ngx-file-drop__content{display:flex;align-items:center;justify-content:center;height:100px;color:#0782d0}.ngx-file-drop__drop-zone-label{text-align:center}.ngx-file-drop__file-input{display:none}\n"], dependencies: [{ kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }] }); }
+ }
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.1.1", ngImport: i0, type: NgxFileDropComponent, decorators: [{
+             type: Component,
+-            args: [{ selector: 'ngx-file-drop', template: "<div [className]=\"dropZoneClassName\"\r\n     [class.ngx-file-drop__drop-zone--over]=\"isDraggingOverDropZone\"\r\n     (drop)=\"dropFiles($event)\"\r\n     (dragover)=\"onDragOver($event)\"\r\n     (dragenter)=\"onDragEnter($event)\"\r\n     (dragleave)=\"onDragLeave($event)\">\r\n  <div [className]=\"contentClassName\">\r\n    <input \r\n      type=\"file\" \r\n      #fileSelector \r\n      [accept]=\"accept\" \r\n      [attr.directory]=\"directory || undefined\" \r\n      [attr.webkitdirectory]=\"directory || undefined\"\r\n      [attr.mozdirectory]=\"directory || undefined\"\r\n      [attr.msdirectory]=\"directory || undefined\"\r\n      [attr.odirectory]=\"directory || undefined\"\r\n      [multiple]=\"multiple\"\r\n      (change)=\"uploadFiles($event)\" \r\n      class=\"ngx-file-drop__file-input\" \r\n    />\r\n\r\n    <ng-template #defaultContentTemplate>\r\n      <div *ngIf=\"dropZoneLabel\" class=\"ngx-file-drop__drop-zone-label\">{{dropZoneLabel}}</div>\r\n      <div *ngIf=\"showBrowseBtn\">\r\n        <input type=\"button\" [className]=\"browseBtnClassName\" value=\"{{browseBtnLabel}}\" (click)=\"openFileSelector($event)\" />\r\n      </div>\r\n    </ng-template>\r\n\r\n    <ng-template\r\n      [ngTemplateOutlet]=\"contentTemplate || defaultContentTemplate\"\r\n      [ngTemplateOutletContext]=\"{ openFileSelector: openFileSelector }\">\r\n    </ng-template>\r\n  </div>\r\n</div>\r\n", styles: [".ngx-file-drop__drop-zone{height:100px;margin:auto;border:2px dotted #0782d0;border-radius:30px}.ngx-file-drop__drop-zone--over{background-color:#93939380}.ngx-file-drop__content{display:flex;align-items:center;justify-content:center;height:100px;color:#0782d0}.ngx-file-drop__drop-zone-label{text-align:center}.ngx-file-drop__file-input{display:none}\n"] }]
++            args: [{ selector: 'ngx-file-drop', template: "<div [className]=\"dropZoneClassName\"\n     [class.ngx-file-drop__drop-zone--over]=\"isDraggingOverDropZone\"\n     (drop)=\"dropFiles($event)\"\n     (dragover)=\"onDragOver($event)\"\n     (dragenter)=\"onDragEnter($event)\"\n     (dragleave)=\"onDragLeave($event)\">\n  <div [className]=\"contentClassName\">\n    <input \n      type=\"file\" \n      #fileSelector \n      [accept]=\"accept\" \n      [attr.directory]=\"directory || undefined\" \n      [attr.webkitdirectory]=\"directory || undefined\"\n      [attr.mozdirectory]=\"directory || undefined\"\n      [attr.msdirectory]=\"directory || undefined\"\n      [attr.odirectory]=\"directory || undefined\"\n      [multiple]=\"multiple\"\n      (change)=\"uploadFiles($event)\" \n      class=\"ngx-file-drop__file-input\" \n    />\n\n    <ng-template #defaultContentTemplate>\n      <div *ngIf=\"dropZoneLabel\" class=\"ngx-file-drop__drop-zone-label\">{{dropZoneLabel}}</div>\n      <div *ngIf=\"showBrowseBtn\">\n        <input type=\"button\" [className]=\"browseBtnClassName\" value=\"{{browseBtnLabel}}\" (click)=\"openFileSelector($event)\" />\n      </div>\n    </ng-template>\n\n    <ng-template\n      [ngTemplateOutlet]=\"contentTemplate || defaultContentTemplate\"\n      [ngTemplateOutletContext]=\"{ openFileSelector: openFileSelector }\">\n    </ng-template>\n  </div>\n</div>\n", styles: [".ngx-file-drop__drop-zone{height:100px;margin:auto;border:2px dotted #0782d0;border-radius:30px}.ngx-file-drop__drop-zone--over{background-color:#93939380}.ngx-file-drop__content{display:flex;align-items:center;justify-content:center;height:100px;color:#0782d0}.ngx-file-drop__drop-zone-label{text-align:center}.ngx-file-drop__file-input{display:none}\n"] }]
+         }], ctorParameters: function () { return [{ type: i0.NgZone }, { type: i0.Renderer2 }]; }, propDecorators: { accept: [{
+                 type: Input
+             }], directory: [{
+diff --git a/node_modules/ngx-file-drop/fesm2022/ngx-file-drop.mjs.map b/node_modules/ngx-file-drop/fesm2022/ngx-file-drop.mjs.map
+index ab987c7..e9e2202 100644
+--- a/node_modules/ngx-file-drop/fesm2022/ngx-file-drop.mjs.map
++++ b/node_modules/ngx-file-drop/fesm2022/ngx-file-drop.mjs.map
+@@ -1 +1 @@
+-{"version":3,"file":"ngx-file-drop.mjs","sources":["../../../projects/ngx-file-drop/src/lib/ngx-file-drop-entry.ts","../../../projects/ngx-file-drop/src/lib/ngx-templates.directive.ts","../../../projects/ngx-file-drop/src/lib/ngx-file-drop.component.ts","../../../projects/ngx-file-drop/src/lib/ngx-file-drop.component.html","../../../projects/ngx-file-drop/src/lib/ngx-file-drop.module.ts","../../../projects/ngx-file-drop/src/ngx-file-drop.ts"],"sourcesContent":["import { FileSystemEntry, FileSystemFileEntry, FileSystemDirectoryEntry } from './dom.types';\r\n\r\n/**\r\n * fileEntry is an instance of {@link FileSystemFileEntry} or {@link FileSystemDirectoryEntry}.\r\n * Which one is it can be checked using {@link FileSystemEntry.isFile} or {@link FileSystemEntry.isDirectory}\r\n * properties of the given {@link FileSystemEntry}.\r\n */\r\nexport class NgxFileDropEntry {\r\n    constructor(\r\n        public relativePath: string,\r\n        public fileEntry: FileSystemEntry\r\n    ) {\r\n    }\r\n}\r\n","import { Directive, TemplateRef } from '@angular/core';\r\n\r\n@Directive({ selector: '[ngx-file-drop-content-tmp]' })\r\nexport class NgxFileDropContentTemplateDirective {\r\n  constructor(public template: TemplateRef<any>) { }\r\n}\r\n","import {\r\n  Component,\r\n  ContentChild,\r\n  ElementRef,\r\n  EventEmitter,\r\n  Input,\r\n  NgZone,\r\n  OnDestroy,\r\n  Output,\r\n  Renderer2,\r\n  TemplateRef,\r\n  ViewChild\r\n} from '@angular/core';\r\nimport { Subscription, timer } from 'rxjs';\r\n\r\nimport { NgxFileDropEntry } from './ngx-file-drop-entry';\r\nimport { FileSystemDirectoryEntry, FileSystemEntry, FileSystemFileEntry } from './dom.types';\r\nimport { NgxFileDropContentTemplateDirective } from './ngx-templates.directive';\r\n\r\n@Component({\r\n  selector: 'ngx-file-drop',\r\n  templateUrl: './ngx-file-drop.component.html',\r\n  styleUrls: ['./ngx-file-drop.component.scss'],\r\n})\r\nexport class NgxFileDropComponent implements OnDestroy {\r\n\r\n  @Input()\r\n  public accept: string = '*';\r\n\r\n  @Input()\r\n  public directory: boolean = false;\r\n\r\n  @Input()\r\n  public multiple: boolean = true;\r\n\r\n  @Input()\r\n  public dropZoneLabel: string = '';\r\n\r\n  @Input()\r\n  public dropZoneClassName: string = 'ngx-file-drop__drop-zone';\r\n\r\n  @Input()\r\n  public useDragEnter: boolean = false;\r\n\r\n  @Input()\r\n  public contentClassName: string = 'ngx-file-drop__content';\r\n\r\n  @Input()\r\n  public showBrowseBtn: boolean = false;\r\n\r\n  @Input()\r\n  public browseBtnClassName: string = 'btn btn-primary btn-xs ngx-file-drop__browse-btn';\r\n\r\n  @Input()\r\n  public browseBtnLabel: string = 'Browse files';\r\n\r\n  @Output()\r\n  public onFileDrop: EventEmitter<NgxFileDropEntry[]> = new EventEmitter();\r\n\r\n  @Output()\r\n  public onFileOver: EventEmitter<any> = new EventEmitter();\r\n\r\n  @Output()\r\n  public onFileLeave: EventEmitter<any> = new EventEmitter();\r\n\r\n  // custom templates\r\n  @ContentChild(NgxFileDropContentTemplateDirective, { read: TemplateRef }) contentTemplate?: TemplateRef<any>;\r\n\r\n  @ViewChild('fileSelector', { static: true })\r\n  public fileSelector?: ElementRef;\r\n\r\n  public isDraggingOverDropZone: boolean = false;\r\n\r\n  private globalDraggingInProgress: boolean = false;\r\n  private readonly globalDragStartListener: () => void;\r\n  private readonly globalDragEndListener: () => void;\r\n\r\n  private files: NgxFileDropEntry[] = [];\r\n  private numOfActiveReadEntries: number = 0;\r\n\r\n  private helperFormEl: HTMLFormElement | null = null;\r\n  private fileInputPlaceholderEl: HTMLDivElement | null = null;\r\n\r\n  private dropEventTimerSubscription: Subscription | null = null;\r\n\r\n  private _disabled: boolean = false;\r\n\r\n  public get disabled(): boolean { return this._disabled; }\r\n\r\n  @Input()\r\n  public set disabled(value: boolean) {\r\n    this._disabled = (value != null && `${value}` !== 'false');\r\n  }\r\n\r\n  constructor(\r\n    private zone: NgZone,\r\n    private renderer: Renderer2\r\n  ) {\r\n    this.globalDragStartListener = this.renderer.listen('document', 'dragstart', (evt: Event) => {\r\n      this.globalDraggingInProgress = true;\r\n    });\r\n    this.globalDragEndListener = this.renderer.listen('document', 'dragend', (evt: Event) => {\r\n      this.globalDraggingInProgress = false;\r\n    });\r\n  }\r\n\r\n  public ngOnDestroy(): void {\r\n    if (this.dropEventTimerSubscription) {\r\n      this.dropEventTimerSubscription.unsubscribe();\r\n      this.dropEventTimerSubscription = null;\r\n    }\r\n    this.globalDragStartListener();\r\n    this.globalDragEndListener();\r\n    this.files = [];\r\n    this.helperFormEl = null;\r\n    this.fileInputPlaceholderEl = null;\r\n  }\r\n\r\n  public onDragOver(event: DragEvent): void {\r\n    if (this.useDragEnter) {\r\n      this.preventAndStop(event);\r\n      if (event.dataTransfer) {\r\n        event.dataTransfer.dropEffect = 'copy';\r\n      }\r\n    } else if (!this.isDropzoneDisabled() && !this.useDragEnter && event.dataTransfer) {\r\n      if (!this.isDraggingOverDropZone) {\r\n        this.isDraggingOverDropZone = true;\r\n        this.onFileOver.emit(event);\r\n      }\r\n      this.preventAndStop(event);\r\n      event.dataTransfer.dropEffect = 'copy';\r\n    }\r\n  }\r\n\r\n  public onDragEnter(event: Event): void {\r\n    if (!this.isDropzoneDisabled() && this.useDragEnter) {\r\n      if (!this.isDraggingOverDropZone) {\r\n        this.isDraggingOverDropZone = true;\r\n        this.onFileOver.emit(event);\r\n      }\r\n      this.preventAndStop(event);\r\n    }\r\n  }\r\n\r\n  public onDragLeave(event: Event): void {\r\n    if (!this.isDropzoneDisabled()) {\r\n      if (this.isDraggingOverDropZone) {\r\n        this.isDraggingOverDropZone = false;\r\n        this.onFileLeave.emit(event);\r\n      }\r\n      this.preventAndStop(event);\r\n    }\r\n  }\r\n\r\n  public dropFiles(event: DragEvent): void {\r\n    if (this.isDropzoneDisabled()) {\r\n      return;\r\n    }\r\n    this.isDraggingOverDropZone = false;\r\n    if (event.dataTransfer) {\r\n      let items: FileList | DataTransferItemList;\r\n      if (event.dataTransfer.items) {\r\n        items = event.dataTransfer.items;\r\n      } else {\r\n        items = event.dataTransfer.files;\r\n      }\r\n      this.preventAndStop(event);\r\n      this.checkFiles(items);\r\n    }\r\n  }\r\n\r\n  public openFileSelector = (event?: MouseEvent): void => {\r\n    if (this.fileSelector && this.fileSelector.nativeElement) {\r\n      (this.fileSelector.nativeElement as HTMLInputElement).click();\r\n    }\r\n  };\r\n\r\n  /**\r\n   * Processes the change event of the file input and adds the given files.\r\n   * @param Event event\r\n   */\r\n  public uploadFiles(event: Event): void {\r\n    if (this.isDropzoneDisabled()) {\r\n      return;\r\n    }\r\n    if (event.target) {\r\n      const items = (event.target as HTMLInputElement).files || ([] as any);\r\n      this.checkFiles(items);\r\n      this.resetFileInput();\r\n    }\r\n  }\r\n\r\n  private getFakeDropEntry(file: File): NgxFileDropEntry {\r\n    const fakeFileEntry: FileSystemFileEntry = {\r\n      name: file.name,\r\n      isDirectory: false,\r\n      isFile: true,\r\n      file: <T>(callback: (filea: File) => T) => callback(file),\r\n    };\r\n    return new NgxFileDropEntry(fakeFileEntry.name, fakeFileEntry);\r\n  }\r\n\r\n  private checkFile(item: DataTransferItem | File): void {\r\n    if (!item) {\r\n      return;\r\n    }\r\n    // if (\"getAsFile\" in item) {\r\n    //   const file = item.getAsFile();\r\n    //   if (file) {\r\n    //     this.addToQueue(\r\n    //       this.getFakeDropEntry(file)\r\n    //     );\r\n    //     return;\r\n    //   }\r\n    // }\r\n    if (\"webkitGetAsEntry\" in item) {\r\n      let entry = item.webkitGetAsEntry();\r\n      if (entry) {\r\n        if (entry.isFile) {\r\n          const toUpload: NgxFileDropEntry = new NgxFileDropEntry(entry.name, entry);\r\n          this.addToQueue(toUpload);\r\n\r\n        } else if (entry.isDirectory) {\r\n          this.traverseFileTree(entry, entry.name);\r\n        }\r\n        return;\r\n      }\r\n    }\r\n    this.addToQueue(this.getFakeDropEntry((item as File)));\r\n  }\r\n\r\n  private checkFiles(items: FileList | DataTransferItemList): void {\r\n    for (let i = 0; i < items.length; i++) {\r\n      this.checkFile(items[i]);\r\n    }\r\n\r\n    if (this.dropEventTimerSubscription) {\r\n      this.dropEventTimerSubscription.unsubscribe();\r\n    }\r\n    this.dropEventTimerSubscription = timer(200, 200)\r\n      .subscribe(() => {\r\n        if (this.files.length > 0 && this.numOfActiveReadEntries === 0) {\r\n          const files = this.files;\r\n          this.files = [];\r\n          this.onFileDrop.emit(files);\r\n        }\r\n      });\r\n  }\r\n\r\n  private traverseFileTree(item: FileSystemEntry, path: string): void {\r\n    if (item.isFile) {\r\n      const toUpload: NgxFileDropEntry = new NgxFileDropEntry(path, item);\r\n      this.files.push(toUpload);\r\n\r\n    } else {\r\n      path = path + '/';\r\n      const dirReader = (item as FileSystemDirectoryEntry).createReader();\r\n      let entries: FileSystemEntry[] = [];\r\n\r\n      const readEntries = () => {\r\n        this.numOfActiveReadEntries++;\r\n        dirReader.readEntries((result) => {\r\n          if (!result.length) {\r\n            // add empty folders\r\n            if (entries.length === 0) {\r\n              const toUpload: NgxFileDropEntry = new NgxFileDropEntry(path, item);\r\n              this.zone.run(() => {\r\n                this.addToQueue(toUpload);\r\n              });\r\n\r\n            } else {\r\n              for (let i = 0; i < entries.length; i++) {\r\n                this.zone.run(() => {\r\n                  this.traverseFileTree(entries[i], path + entries[i].name);\r\n                });\r\n              }\r\n            }\r\n\r\n          } else {\r\n            // continue with the reading\r\n            entries = entries.concat(result);\r\n            readEntries();\r\n          }\r\n\r\n          this.numOfActiveReadEntries--;\r\n        });\r\n      };\r\n\r\n      readEntries();\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Clears any added files from the file input element so the same file can subsequently be added multiple times.\r\n   */\r\n  private resetFileInput(): void {\r\n    if (this.fileSelector && this.fileSelector.nativeElement) {\r\n      const fileInputEl = this.fileSelector.nativeElement as HTMLInputElement;\r\n      const fileInputContainerEl = fileInputEl.parentElement;\r\n      const helperFormEl = this.getHelperFormElement();\r\n      const fileInputPlaceholderEl = this.getFileInputPlaceholderElement();\r\n\r\n      // Just a quick check so we do not mess up the DOM (will never happen though).\r\n      if (fileInputContainerEl !== helperFormEl) {\r\n        // Insert the form input placeholder in the DOM before the form input element.\r\n        this.renderer.insertBefore(fileInputContainerEl, fileInputPlaceholderEl, fileInputEl);\r\n        // Add the form input as child of the temporary form element, removing the form input from the DOM.\r\n        this.renderer.appendChild(helperFormEl, fileInputEl);\r\n        // Reset the form, thus clearing the input element of any files.\r\n        helperFormEl.reset();\r\n        // Add the file input back to the DOM in place of the file input placeholder element.\r\n        this.renderer.insertBefore(fileInputContainerEl, fileInputEl, fileInputPlaceholderEl);\r\n        // Remove the input placeholder from the DOM\r\n        this.renderer.removeChild(fileInputContainerEl, fileInputPlaceholderEl);\r\n      }\r\n    }\r\n  }\r\n\r\n  /**\r\n   * Get a cached HTML form element as a helper element to clear the file input element.\r\n   */\r\n  private getHelperFormElement(): HTMLFormElement {\r\n    if (!this.helperFormEl) {\r\n      this.helperFormEl = this.renderer.createElement('form') as HTMLFormElement;\r\n    }\r\n\r\n    return this.helperFormEl;\r\n  }\r\n\r\n  /**\r\n   * Get a cached HTML div element to be used as placeholder for the file input element when clearing said element.\r\n   */\r\n  private getFileInputPlaceholderElement(): HTMLDivElement {\r\n    if (!this.fileInputPlaceholderEl) {\r\n      this.fileInputPlaceholderEl = this.renderer.createElement('div') as HTMLDivElement;\r\n    }\r\n\r\n    return this.fileInputPlaceholderEl;\r\n  }\r\n\r\n  private isDropzoneDisabled(): boolean {\r\n    return (this.globalDraggingInProgress || this.disabled);\r\n  }\r\n\r\n  private addToQueue(item: NgxFileDropEntry): void {\r\n    this.files.push(item);\r\n  }\r\n\r\n  private preventAndStop(event: Event): void {\r\n    event.stopPropagation();\r\n    event.preventDefault();\r\n  }\r\n}\r\n","<div [className]=\"dropZoneClassName\"\r\n     [class.ngx-file-drop__drop-zone--over]=\"isDraggingOverDropZone\"\r\n     (drop)=\"dropFiles($event)\"\r\n     (dragover)=\"onDragOver($event)\"\r\n     (dragenter)=\"onDragEnter($event)\"\r\n     (dragleave)=\"onDragLeave($event)\">\r\n  <div [className]=\"contentClassName\">\r\n    <input \r\n      type=\"file\" \r\n      #fileSelector \r\n      [accept]=\"accept\" \r\n      [attr.directory]=\"directory || undefined\" \r\n      [attr.webkitdirectory]=\"directory || undefined\"\r\n      [attr.mozdirectory]=\"directory || undefined\"\r\n      [attr.msdirectory]=\"directory || undefined\"\r\n      [attr.odirectory]=\"directory || undefined\"\r\n      [multiple]=\"multiple\"\r\n      (change)=\"uploadFiles($event)\" \r\n      class=\"ngx-file-drop__file-input\" \r\n    />\r\n\r\n    <ng-template #defaultContentTemplate>\r\n      <div *ngIf=\"dropZoneLabel\" class=\"ngx-file-drop__drop-zone-label\">{{dropZoneLabel}}</div>\r\n      <div *ngIf=\"showBrowseBtn\">\r\n        <input type=\"button\" [className]=\"browseBtnClassName\" value=\"{{browseBtnLabel}}\" (click)=\"openFileSelector($event)\" />\r\n      </div>\r\n    </ng-template>\r\n\r\n    <ng-template\r\n      [ngTemplateOutlet]=\"contentTemplate || defaultContentTemplate\"\r\n      [ngTemplateOutletContext]=\"{ openFileSelector: openFileSelector }\">\r\n    </ng-template>\r\n  </div>\r\n</div>\r\n","import { NgModule } from '@angular/core';\r\nimport { CommonModule } from '@angular/common';\r\nimport { NgxFileDropComponent } from './ngx-file-drop.component';\r\nimport { NgxFileDropContentTemplateDirective } from './ngx-templates.directive';\r\n\r\n@NgModule({\r\n  declarations: [\r\n    NgxFileDropComponent,\r\n    NgxFileDropContentTemplateDirective,\r\n  ],\r\n  imports: [\r\n    CommonModule\r\n  ],\r\n  exports: [\r\n    NgxFileDropComponent,\r\n    NgxFileDropContentTemplateDirective,\r\n  ],\r\n  providers: [],\r\n  bootstrap: [\r\n    NgxFileDropComponent\r\n  ],\r\n})\r\nexport class NgxFileDropModule {}\r\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
+\ No newline at end of file
++{"version":3,"file":"ngx-file-drop.mjs","sources":["../../../projects/ngx-file-drop/src/lib/ngx-file-drop-entry.ts","../../../projects/ngx-file-drop/src/lib/dom.types.ts","../../../projects/ngx-file-drop/src/lib/ngx-templates.directive.ts","../../../projects/ngx-file-drop/src/lib/ngx-file-drop.component.ts","../../../projects/ngx-file-drop/src/lib/ngx-file-drop.component.html","../../../projects/ngx-file-drop/src/lib/ngx-file-drop.module.ts","../../../projects/ngx-file-drop/src/ngx-file-drop.ts"],"sourcesContent":["import { FileSystemEntry, FileSystemFileEntry, FileSystemDirectoryEntry } from './dom.types';\n\n/**\n * fileEntry is an instance of {@link FileSystemFileEntry} or {@link FileSystemDirectoryEntry}.\n * Which one is it can be checked using {@link FileSystemEntry.isFile} or {@link FileSystemEntry.isDirectory}\n * properties of the given {@link FileSystemEntry}.\n */\nexport class NgxFileDropEntry {\n    constructor(\n        public relativePath: string,\n        public fileEntry: FileSystemEntry\n    ) {\n    }\n}\n","\nexport interface FileSystemEntry {\n  name: string,\n  isDirectory: boolean\n  isFile: boolean\n}\n\nexport interface FileSystemEntryMetadata {\n  modificationTime?: Date,\n  size?: number\n}\n\nexport interface FileSystemDirectoryReader {\n  readEntries(\n    successCallback: (result: FileSystemEntry[]) => void,\n  ): void\n}\n\nexport interface FileSystemFlags {\n  create?: boolean\n  exclusive?: boolean\n}\n\nexport interface FileSystemDirectoryEntry extends FileSystemEntry {\n  isDirectory: true\n  isFile: false\n  createReader(): FileSystemDirectoryReader\n}\n\nexport interface FileSystemFileEntry extends FileSystemEntry {\n  isDirectory: false\n  isFile: true\n  file<T>(callback: (file: File) => T): T\n}\n\nexport function isDataTransferItem(item: DataTransferItem | File): item is DataTransferItem {\n  return \"webkitGetAsEntry\" in item && \"getAsFile\" in item;\n}\n","import { Directive, TemplateRef } from '@angular/core';\n\n@Directive({ selector: '[ngx-file-drop-content-tmp]' })\nexport class NgxFileDropContentTemplateDirective {\n  constructor(public template: TemplateRef<any>) { }\n}\n","import {\n  Component,\n  ContentChild,\n  ElementRef,\n  EventEmitter,\n  Input,\n  NgZone,\n  OnDestroy,\n  Output,\n  Renderer2,\n  TemplateRef,\n  ViewChild\n} from '@angular/core';\nimport { Subscription, timer } from 'rxjs';\n\nimport { NgxFileDropEntry } from './ngx-file-drop-entry';\nimport { FileSystemDirectoryEntry, FileSystemEntry, FileSystemFileEntry, isDataTransferItem } from './dom.types';\nimport { NgxFileDropContentTemplateDirective } from './ngx-templates.directive';\n\n@Component({\n  selector: 'ngx-file-drop',\n  templateUrl: './ngx-file-drop.component.html',\n  styleUrls: ['./ngx-file-drop.component.scss'],\n})\nexport class NgxFileDropComponent implements OnDestroy {\n\n  @Input()\n  public accept: string = '*';\n\n  @Input()\n  public directory: boolean = false;\n\n  @Input()\n  public multiple: boolean = true;\n\n  @Input()\n  public dropZoneLabel: string = '';\n\n  @Input()\n  public dropZoneClassName: string = 'ngx-file-drop__drop-zone';\n\n  @Input()\n  public useDragEnter: boolean = false;\n\n  @Input()\n  public contentClassName: string = 'ngx-file-drop__content';\n\n  @Input()\n  public showBrowseBtn: boolean = false;\n\n  @Input()\n  public browseBtnClassName: string = 'btn btn-primary btn-xs ngx-file-drop__browse-btn';\n\n  @Input()\n  public browseBtnLabel: string = 'Browse files';\n\n  @Output()\n  public onFileDrop: EventEmitter<NgxFileDropEntry[]> = new EventEmitter();\n\n  @Output()\n  public onFileOver: EventEmitter<any> = new EventEmitter();\n\n  @Output()\n  public onFileLeave: EventEmitter<any> = new EventEmitter();\n\n  // custom templates\n  @ContentChild(NgxFileDropContentTemplateDirective, { read: TemplateRef }) contentTemplate?: TemplateRef<any>;\n\n  @ViewChild('fileSelector', { static: true })\n  public fileSelector?: ElementRef;\n\n  public isDraggingOverDropZone: boolean = false;\n\n  private globalDraggingInProgress: boolean = false;\n  private readonly globalDragStartListener: () => void;\n  private readonly globalDragEndListener: () => void;\n\n  private files: NgxFileDropEntry[] = [];\n  private numOfActiveReadEntries: number = 0;\n\n  private helperFormEl: HTMLFormElement | null = null;\n  private fileInputPlaceholderEl: HTMLDivElement | null = null;\n\n  private dropEventTimerSubscription: Subscription | null = null;\n\n  private _disabled: boolean = false;\n\n  public get disabled(): boolean { return this._disabled; }\n\n  @Input()\n  public set disabled(value: boolean) {\n    this._disabled = (value != null && `${value}` !== 'false');\n  }\n\n  constructor(\n    private zone: NgZone,\n    private renderer: Renderer2\n  ) {\n    this.globalDragStartListener = this.renderer.listen('document', 'dragstart', (evt: Event) => {\n      this.globalDraggingInProgress = true;\n    });\n    this.globalDragEndListener = this.renderer.listen('document', 'dragend', (evt: Event) => {\n      this.globalDraggingInProgress = false;\n    });\n  }\n\n  public ngOnDestroy(): void {\n    if (this.dropEventTimerSubscription) {\n      this.dropEventTimerSubscription.unsubscribe();\n      this.dropEventTimerSubscription = null;\n    }\n    this.globalDragStartListener();\n    this.globalDragEndListener();\n    this.files = [];\n    this.helperFormEl = null;\n    this.fileInputPlaceholderEl = null;\n  }\n\n  public onDragOver(event: DragEvent): void {\n    if (this.useDragEnter) {\n      this.preventAndStop(event);\n      if (event.dataTransfer) {\n        event.dataTransfer.dropEffect = 'copy';\n      }\n    } else if (!this.isDropzoneDisabled() && !this.useDragEnter && event.dataTransfer) {\n      if (!this.isDraggingOverDropZone) {\n        this.isDraggingOverDropZone = true;\n        this.onFileOver.emit(event);\n      }\n      this.preventAndStop(event);\n      event.dataTransfer.dropEffect = 'copy';\n    }\n  }\n\n  public onDragEnter(event: Event): void {\n    if (!this.isDropzoneDisabled() && this.useDragEnter) {\n      if (!this.isDraggingOverDropZone) {\n        this.isDraggingOverDropZone = true;\n        this.onFileOver.emit(event);\n      }\n      this.preventAndStop(event);\n    }\n  }\n\n  public onDragLeave(event: Event): void {\n    if (!this.isDropzoneDisabled()) {\n      if (this.isDraggingOverDropZone) {\n        this.isDraggingOverDropZone = false;\n        this.onFileLeave.emit(event);\n      }\n      this.preventAndStop(event);\n    }\n  }\n\n  public dropFiles(event: DragEvent): void {\n    if (this.isDropzoneDisabled()) {\n      return;\n    }\n    this.isDraggingOverDropZone = false;\n    if (event.dataTransfer) {\n      let items: FileList | DataTransferItemList;\n      if (event.dataTransfer.items) {\n        items = event.dataTransfer.items;\n      } else {\n        items = event.dataTransfer.files;\n      }\n      this.preventAndStop(event);\n      this.checkFiles(items);\n    }\n  }\n\n  public openFileSelector = (event?: MouseEvent): void => {\n    if (this.fileSelector && this.fileSelector.nativeElement) {\n      (this.fileSelector.nativeElement as HTMLInputElement).click();\n    }\n  };\n\n  /**\n   * Processes the change event of the file input and adds the given files.\n   * @param Event event\n   */\n  public uploadFiles(event: Event): void {\n    if (this.isDropzoneDisabled()) {\n      return;\n    }\n    if (event.target) {\n      const items = (event.target as HTMLInputElement).files || ([] as any);\n      this.checkFiles(items);\n      this.resetFileInput();\n    }\n  }\n\n  private getFakeDropEntry(file: File): NgxFileDropEntry {\n    const fakeFileEntry: FileSystemFileEntry = {\n      name: file.name,\n      isDirectory: false,\n      isFile: true,\n      file: <T>(callback: (filea: File) => T) => callback(file),\n    };\n    return new NgxFileDropEntry(fakeFileEntry.name, fakeFileEntry);\n  }\n\n  private checkFile(item: DataTransferItem | File): void {\n    if (!item) {\n      return;\n    }\n    if (isDataTransferItem(item)) {\n      const entry = item.webkitGetAsEntry();\n      if (entry) {\n        if (entry.isFile) {\n          const toUpload: NgxFileDropEntry = new NgxFileDropEntry(entry.name, entry);\n          this.addToQueue(toUpload);\n\n        } else if (entry.isDirectory) {\n          this.traverseFileTree(entry, entry.name);\n        }\n        return;\n      }\n\n      const file = item.getAsFile();\n      if (file) {\n        this.addToQueue(this.getFakeDropEntry(file));\n      }\n      return;\n    }\n    this.addToQueue(this.getFakeDropEntry(item));\n  }\n\n  private checkFiles(items: FileList | DataTransferItemList): void {\n    for (let i = 0; i < items.length; i++) {\n      this.checkFile(items[i]);\n    }\n\n    if (this.dropEventTimerSubscription) {\n      this.dropEventTimerSubscription.unsubscribe();\n    }\n    this.dropEventTimerSubscription = timer(200, 200)\n      .subscribe(() => {\n        if (this.files.length > 0 && this.numOfActiveReadEntries === 0) {\n          const files = this.files;\n          this.files = [];\n          this.onFileDrop.emit(files);\n        }\n      });\n  }\n\n  private traverseFileTree(item: FileSystemEntry, path: string): void {\n    if (item.isFile) {\n      const toUpload: NgxFileDropEntry = new NgxFileDropEntry(path, item);\n      this.files.push(toUpload);\n\n    } else {\n      path = path + '/';\n      const dirReader = (item as FileSystemDirectoryEntry).createReader();\n      let entries: FileSystemEntry[] = [];\n\n      const readEntries = () => {\n        this.numOfActiveReadEntries++;\n        dirReader.readEntries((result) => {\n          if (!result.length) {\n            // add empty folders\n            if (entries.length === 0) {\n              const toUpload: NgxFileDropEntry = new NgxFileDropEntry(path, item);\n              this.zone.run(() => {\n                this.addToQueue(toUpload);\n              });\n\n            } else {\n              for (let i = 0; i < entries.length; i++) {\n                this.zone.run(() => {\n                  this.traverseFileTree(entries[i], path + entries[i].name);\n                });\n              }\n            }\n\n          } else {\n            // continue with the reading\n            entries = entries.concat(result);\n            readEntries();\n          }\n\n          this.numOfActiveReadEntries--;\n        });\n      };\n\n      readEntries();\n    }\n  }\n\n  /**\n   * Clears any added files from the file input element so the same file can subsequently be added multiple times.\n   */\n  private resetFileInput(): void {\n    if (this.fileSelector && this.fileSelector.nativeElement) {\n      const fileInputEl = this.fileSelector.nativeElement as HTMLInputElement;\n      const fileInputContainerEl = fileInputEl.parentElement;\n      const helperFormEl = this.getHelperFormElement();\n      const fileInputPlaceholderEl = this.getFileInputPlaceholderElement();\n\n      // Just a quick check so we do not mess up the DOM (will never happen though).\n      if (fileInputContainerEl !== helperFormEl) {\n        // Insert the form input placeholder in the DOM before the form input element.\n        this.renderer.insertBefore(fileInputContainerEl, fileInputPlaceholderEl, fileInputEl);\n        // Add the form input as child of the temporary form element, removing the form input from the DOM.\n        this.renderer.appendChild(helperFormEl, fileInputEl);\n        // Reset the form, thus clearing the input element of any files.\n        helperFormEl.reset();\n        // Add the file input back to the DOM in place of the file input placeholder element.\n        this.renderer.insertBefore(fileInputContainerEl, fileInputEl, fileInputPlaceholderEl);\n        // Remove the input placeholder from the DOM\n        this.renderer.removeChild(fileInputContainerEl, fileInputPlaceholderEl);\n      }\n    }\n  }\n\n  /**\n   * Get a cached HTML form element as a helper element to clear the file input element.\n   */\n  private getHelperFormElement(): HTMLFormElement {\n    if (!this.helperFormEl) {\n      this.helperFormEl = this.renderer.createElement('form') as HTMLFormElement;\n    }\n\n    return this.helperFormEl;\n  }\n\n  /**\n   * Get a cached HTML div element to be used as placeholder for the file input element when clearing said element.\n   */\n  private getFileInputPlaceholderElement(): HTMLDivElement {\n    if (!this.fileInputPlaceholderEl) {\n      this.fileInputPlaceholderEl = this.renderer.createElement('div') as HTMLDivElement;\n    }\n\n    return this.fileInputPlaceholderEl;\n  }\n\n  private isDropzoneDisabled(): boolean {\n    return (this.globalDraggingInProgress || this.disabled);\n  }\n\n  private addToQueue(item: NgxFileDropEntry): void {\n    this.files.push(item);\n  }\n\n  private preventAndStop(event: Event): void {\n    event.stopPropagation();\n    event.preventDefault();\n  }\n}\n","<div [className]=\"dropZoneClassName\"\n     [class.ngx-file-drop__drop-zone--over]=\"isDraggingOverDropZone\"\n     (drop)=\"dropFiles($event)\"\n     (dragover)=\"onDragOver($event)\"\n     (dragenter)=\"onDragEnter($event)\"\n     (dragleave)=\"onDragLeave($event)\">\n  <div [className]=\"contentClassName\">\n    <input \n      type=\"file\" \n      #fileSelector \n      [accept]=\"accept\" \n      [attr.directory]=\"directory || undefined\" \n      [attr.webkitdirectory]=\"directory || undefined\"\n      [attr.mozdirectory]=\"directory || undefined\"\n      [attr.msdirectory]=\"directory || undefined\"\n      [attr.odirectory]=\"directory || undefined\"\n      [multiple]=\"multiple\"\n      (change)=\"uploadFiles($event)\" \n      class=\"ngx-file-drop__file-input\" \n    />\n\n    <ng-template #defaultContentTemplate>\n      <div *ngIf=\"dropZoneLabel\" class=\"ngx-file-drop__drop-zone-label\">{{dropZoneLabel}}</div>\n      <div *ngIf=\"showBrowseBtn\">\n        <input type=\"button\" [className]=\"browseBtnClassName\" value=\"{{browseBtnLabel}}\" (click)=\"openFileSelector($event)\" />\n      </div>\n    </ng-template>\n\n    <ng-template\n      [ngTemplateOutlet]=\"contentTemplate || defaultContentTemplate\"\n      [ngTemplateOutletContext]=\"{ openFileSelector: openFileSelector }\">\n    </ng-template>\n  </div>\n</div>\n","import { NgModule } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { NgxFileDropComponent } from './ngx-file-drop.component';\nimport { NgxFileDropContentTemplateDirective } from './ngx-templates.directive';\n\n@NgModule({\n  declarations: [\n    NgxFileDropComponent,\n    NgxFileDropContentTemplateDirective,\n  ],\n  imports: [\n    CommonModule\n  ],\n  exports: [\n    NgxFileDropComponent,\n    NgxFileDropContentTemplateDirective,\n  ],\n  providers: [],\n  bootstrap: [\n    NgxFileDropComponent\n  ],\n})\nexport class NgxFileDropModule {}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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"}
+\ No newline at end of file
+diff --git a/node_modules/ngx-file-drop/lib/dom.types.d.ts b/node_modules/ngx-file-drop/lib/dom.types.d.ts
+index 2539d26..0a2315c 100644
+--- a/node_modules/ngx-file-drop/lib/dom.types.d.ts
++++ b/node_modules/ngx-file-drop/lib/dom.types.d.ts
+@@ -24,3 +24,4 @@ export interface FileSystemFileEntry extends FileSystemEntry {
+     isFile: true;
+     file<T>(callback: (file: File) => T): T;
+ }
++export declare function isDataTransferItem(item: DataTransferItem | File): item is DataTransferItem;
index 7fbb8dc4a8b72b8efda6eed0b832dd930eb50feb..ef2addf2c2d62f04d1d03186a05f87f5935a5981 100644 (file)
@@ -1,16 +1,10 @@
 <pngx-toasts></pngx-toasts>
 
-<ngx-file-drop dropZoneClassName="main-dropzone" contentClassName="main-content" [disabled]="!dragDropEnabled"
-(onFileDrop)="dropped($event)" (onFileOver)="fileOver()" (onFileLeave)="fileLeave()">
-    <ng-template ngx-file-drop-content-tmp>
-        <div class="global-dropzone-overlay fade" [class.show]="fileIsOver" [class.hide]="hidden">
-            <h2 i18n>Drop files to begin upload</h2>
-        </div>
-        <div [class.inert]="fileIsOver">
-            <router-outlet></router-outlet>
-        </div>
-    </ng-template>
-</ngx-file-drop>
+<pngx-file-drop>
+    <ng-container content>
+        <router-outlet></router-outlet>
+    </ng-container>
+</pngx-file-drop>
 
 <tour-step-template>
     <ng-template #tourStep let-step="step">
index 57c0e1afb0a521c71fdde53fb815ac57e18dffee..80fbdfa5ff6d33eba38b5e3064140872acb34b36 100644 (file)
@@ -2,14 +2,11 @@ import { HttpClientTestingModule } from '@angular/common/http/testing'
 import {
   ComponentFixture,
   TestBed,
-  discardPeriodicTasks,
   fakeAsync,
   tick,
 } from '@angular/core/testing'
-import { By } from '@angular/platform-browser'
 import { Router } from '@angular/router'
 import { RouterTestingModule } from '@angular/router/testing'
-import { NgxFileDropModule } from 'ngx-file-drop'
 import { TourService, TourNgBootstrapModule } from 'ngx-ui-tour-ng-bootstrap'
 import { Subject } from 'rxjs'
 import { routes } from './app-routing.module'
@@ -21,8 +18,9 @@ import {
 } from './services/consumer-status.service'
 import { PermissionsService } from './services/permissions.service'
 import { ToastService, Toast } from './services/toast.service'
-import { UploadDocumentsService } from './services/upload-documents.service'
 import { SettingsService } from './services/settings.service'
+import { FileDropComponent } from './components/file-drop/file-drop.component'
+import { NgxFileDropModule } from 'ngx-file-drop'
 
 describe('AppComponent', () => {
   let component: AppComponent
@@ -33,11 +31,10 @@ describe('AppComponent', () => {
   let toastService: ToastService
   let router: Router
   let settingsService: SettingsService
-  let uploadDocumentsService: UploadDocumentsService
 
   beforeEach(async () => {
     TestBed.configureTestingModule({
-      declarations: [AppComponent, ToastsComponent],
+      declarations: [AppComponent, ToastsComponent, FileDropComponent],
       providers: [],
       imports: [
         HttpClientTestingModule,
@@ -53,7 +50,6 @@ describe('AppComponent', () => {
     settingsService = TestBed.inject(SettingsService)
     toastService = TestBed.inject(ToastService)
     router = TestBed.inject(Router)
-    uploadDocumentsService = TestBed.inject(UploadDocumentsService)
     fixture = TestBed.createComponent(AppComponent)
     component = fixture.componentInstance
   })
@@ -72,6 +68,7 @@ describe('AppComponent', () => {
   }))
 
   it('should display toast on document consumed with link if user has access', () => {
+    const navigateSpy = jest.spyOn(router, 'navigate')
     jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
     let toast: Toast
     toastService.getToasts().subscribe((toasts) => (toast = toasts[0]))
@@ -81,9 +78,13 @@ describe('AppComponent', () => {
       .spyOn(consumerStatusService, 'onDocumentConsumptionFinished')
       .mockReturnValue(fileStatusSubject)
     component.ngOnInit()
-    fileStatusSubject.next(new FileStatus())
+    const status = new FileStatus()
+    status.documentId = 1
+    fileStatusSubject.next(status)
     expect(toastSpy).toHaveBeenCalled()
     expect(toast.action).not.toBeUndefined()
+    toast.action()
+    expect(navigateSpy).toHaveBeenCalledWith(['documents', status.documentId])
   })
 
   it('should display toast on document consumed without link if user does not have access', () => {
@@ -138,45 +139,4 @@ describe('AppComponent', () => {
     fileStatusSubject.next(new FileStatus())
     expect(toastSpy).toHaveBeenCalled()
   })
-
-  it('should disable drag-drop if on dashboard', () => {
-    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
-    jest.spyOn(router, 'url', 'get').mockReturnValueOnce('/dashboard')
-    expect(component.dragDropEnabled).toBeFalsy()
-    jest.spyOn(router, 'url', 'get').mockReturnValueOnce('/documents')
-    expect(component.dragDropEnabled).toBeTruthy()
-  })
-
-  it('should enable drag-drop if user has permissions', () => {
-    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
-    expect(component.dragDropEnabled).toBeTruthy()
-  })
-
-  it('should disable drag-drop if user does not have permissions', () => {
-    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(false)
-    expect(component.dragDropEnabled).toBeFalsy()
-  })
-
-  it('should support drag drop', fakeAsync(() => {
-    expect(component.fileIsOver).toBeFalsy()
-    component.fileOver()
-    tick(1)
-    fixture.detectChanges()
-    expect(component.fileIsOver).toBeTruthy()
-    const dropzone = fixture.debugElement.query(
-      By.css('.global-dropzone-overlay')
-    )
-    expect(dropzone).not.toBeNull()
-    component.fileLeave()
-    tick(700)
-    fixture.detectChanges()
-    expect(dropzone.classes['hide']).toBeTruthy()
-    // drop
-    const toastSpy = jest.spyOn(toastService, 'show')
-    const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFiles')
-    component.dropped([])
-    tick(3000)
-    expect(toastSpy).toHaveBeenCalled()
-    expect(uploadSpy).toHaveBeenCalled()
-  }))
 })
index 9ca963337b580f92199a8fa604f35b91b6c87d8d..fb54665f095f4e2a1bec153b06d8a7a834862b52 100644 (file)
@@ -5,8 +5,6 @@ import { Router } from '@angular/router'
 import { Subscription, first } from 'rxjs'
 import { ConsumerStatusService } from './services/consumer-status.service'
 import { ToastService } from './services/toast.service'
-import { NgxFileDropEntry } from 'ngx-file-drop'
-import { UploadDocumentsService } from './services/upload-documents.service'
 import { TasksService } from './services/tasks.service'
 import { TourService } from 'ngx-ui-tour-ng-bootstrap'
 import {
@@ -25,16 +23,11 @@ export class AppComponent implements OnInit, OnDestroy {
   successSubscription: Subscription
   failedSubscription: Subscription
 
-  private fileLeaveTimeoutID: any
-  fileIsOver: boolean = false
-  hidden: boolean = true
-
   constructor(
     private settings: SettingsService,
     private consumerStatusService: ConsumerStatusService,
     private toastService: ToastService,
     private router: Router,
-    private uploadDocumentsService: UploadDocumentsService,
     private tasksService: TasksService,
     public tourService: TourService,
     private renderer: Renderer2,
@@ -250,42 +243,4 @@ export class AppComponent implements OnInit, OnDestroy {
       })
     })
   }
-
-  public get dragDropEnabled(): boolean {
-    return (
-      !this.router.url.includes('dashboard') &&
-      this.permissionsService.currentUserCan(
-        PermissionAction.Add,
-        PermissionType.Document
-      )
-    )
-  }
-
-  public fileOver() {
-    // allows transition
-    setTimeout(() => {
-      this.fileIsOver = true
-    }, 1)
-    this.hidden = false
-    // stop fileLeave timeout
-    clearTimeout(this.fileLeaveTimeoutID)
-  }
-
-  public fileLeave(immediate: boolean = false) {
-    const ms = immediate ? 0 : 500
-
-    this.fileLeaveTimeoutID = setTimeout(() => {
-      this.fileIsOver = false
-      // await transition completed
-      setTimeout(() => {
-        this.hidden = true
-      }, 150)
-    }, ms)
-  }
-
-  public dropped(files: NgxFileDropEntry[]) {
-    this.fileLeave(true)
-    this.uploadDocumentsService.uploadFiles(files)
-    this.toastService.showInfo($localize`Initiating upload...`, 3000)
-  }
 }
index 9d93074920d2e1ea67081a1268d58260eb92b4e3..42a226015ec8fc8f1aff1e82b162e02630597e69 100644 (file)
@@ -99,6 +99,8 @@ import { ConsumptionTemplatesComponent } from './components/manage/consumption-t
 import { ConsumptionTemplateEditDialogComponent } from './components/common/edit-dialog/consumption-template-edit-dialog/consumption-template-edit-dialog.component'
 import { MailComponent } from './components/manage/mail/mail.component'
 import { UsersAndGroupsComponent } from './components/admin/users-groups/users-groups.component'
+import { DndModule } from 'ngx-drag-drop'
+import { FileDropComponent } from './components/file-drop/file-drop.component'
 
 import localeAf from '@angular/common/locales/af'
 import localeAr from '@angular/common/locales/ar'
@@ -241,6 +243,7 @@ function initializeApp(settings: SettingsService) {
     ConsumptionTemplateEditDialogComponent,
     MailComponent,
     UsersAndGroupsComponent,
+    FileDropComponent,
   ],
   imports: [
     BrowserModule,
@@ -254,6 +257,7 @@ function initializeApp(settings: SettingsService) {
     NgSelectModule,
     ColorSliderModule,
     TourNgBootstrapModule,
+    DndModule,
   ],
   providers: [
     {
index cb4223e3313de0c73fec08e26ca7bb2f33a99ee2..edb710c3677e4e7e128c098c7f8a7f26dc32e251 100644 (file)
@@ -1,4 +1,4 @@
-<nav class="navbar navbar-dark sticky-top bg-primary flex-md-nowrap p-0 shadow">
+<nav class="navbar navbar-dark sticky-top bg-primary flex-md-nowrap p-0 shadow-sm">
   <button class="navbar-toggler d-md-none collapsed border-0" type="button" data-toggle="collapse"
     data-target="#sidebarMenu" aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation"
     (click)="isMenuCollapsed = !isMenuCollapsed">
index 93468207d26cd9c84821a38933e416d68293af05..89f69bb5e029e24aad0190bab8ef70850b4f6f61 100644 (file)
@@ -99,10 +99,6 @@ main {
     }
   }
 
-  .col-slim {
-    padding-left: calc(50px + $grid-gutter-width) !important;
-  }
-
   .sidebar-slim-toggler {
     display: block;
     position: absolute;
index 1174ee04421f262758573faba15bac325bdcb0e3..b77b6e44509a70428d15f8e9718b76ceae6e463a 100644 (file)
@@ -1,7 +1,9 @@
-<div class="row pt-3 pb-3 pb-md-1 mb-3 border-bottom align-items-center">
+<div class="row pt-3 pb-3 pb-md-2 align-items-center">
   <div class="col-md text-truncate">
-    <p class="h2 text-truncate" style="line-height: 1.4">{{title}}</p>
-    <p *ngIf="subTitle" class="h5 text-truncate" style="line-height: 1.4">{{subTitle}}</p>
+    <h3 class="text-truncate" style="line-height: 1.4">
+      {{title}}
+      <span *ngIf="subTitle" class="h6 mb-0 d-block d-md-inline fw-normal ms-md-3 text-truncate" style="line-height: 1.4">{{subTitle}}</span>
+    </h3>
   </div>
   <div class="btn-toolbar col col-md-auto">
     <ng-content></ng-content>
index cff375429540dff3a2f430c80c6df97288a16f0d..dbecb150fc05f3b7fb2be159b0073c5eb832e2c3 100644 (file)
@@ -25,7 +25,7 @@ describe('PageHeaderComponent', () => {
     component.title = 'Foo'
     component.subTitle = 'Bar'
     fixture.detectChanges()
-    expect(fixture.nativeElement.textContent).toContain('FooBar')
+    expect(fixture.nativeElement.textContent).toContain('Foo Bar')
   })
 
   it('should set html title', () => {
index 538f6a910c14cff066120ab94594d1a744a79d72..a503e143d6f54fc2030fa33fe4657a3daa0b39ff 100644 (file)
@@ -1,29 +1,46 @@
 <pngx-page-header title="Dashboard" [subTitle]="subtitle" i18n-title>
-  <pngx-logo extra_classes="d-none d-md-block"></pngx-logo>
+  <pngx-logo extra_classes="d-none d-md-block mt-n2 me-1" height="3.5rem"></pngx-logo>
 </pngx-page-header>
 
 <div class="row">
-  <div class="col-lg-8">
-    <div tourAnchor="tour.dashboard">
-      <ng-container *ngIf="savedViewService.loading">
+  <div class="col-auto col-lg-8 col-xl-9 mb-4">
+    <div class="row row-cols-1 g-4" tourAnchor="tour.dashboard"
+      dndDropzone
+      [dndDisableIf]="settingsService.globalDropzoneActive"
+      dndEffectAllowed="move"
+      (dndDrop)="onDrop($event)"
+    >
+      <div *ngIf="savedViewService.loading" class="col">
         <div class="spinner-border spinner-border-sm me-2" role="status"></div>
         <ng-container i18n>Loading...</ng-container>
-      </ng-container>
-
-      <pngx-welcome-widget *ngIf="settingsService.offerTour()" (dismiss)="completeTour()"></pngx-welcome-widget>
+      </div>
 
-      <div *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
-        <ng-container *ngFor="let v of savedViewService.dashboardViews; first as isFirst">
-          <pngx-saved-view-widget [savedView]="v"></pngx-saved-view-widget>
-        </ng-container>
+      <div *ngIf="settingsService.offerTour()" class="col">
+        <pngx-welcome-widget (dismiss)="completeTour()"></pngx-welcome-widget>
       </div>
+
+      <ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.SavedView }">
+        <div *ngFor="let v of dashboardViews" class="col">
+          <pngx-saved-view-widget
+            [savedView]="v"
+            (dndStart)="onDragStart($event)"
+            (dndMoved)="onDragged(v)"
+            (dndEnd)="onDragEnd($event)"
+            >
+          </pngx-saved-view-widget>
+        </div>
+      </ng-container>
+      <div class="p-1" dndPlaceholderRef></div>
     </div>
   </div>
-  <div class="col-lg-4">
-
-    <pngx-statistics-widget></pngx-statistics-widget>
-
-    <pngx-upload-file-widget></pngx-upload-file-widget>
-
+  <div class="col-auto col-lg-4 col-xl-3">
+    <div class="row row-cols-1 g-4">
+      <div class="col">
+        <pngx-statistics-widget></pngx-statistics-widget>
+      </div>
+      <div class="col">
+        <pngx-upload-file-widget></pngx-upload-file-widget>
+      </div>
+    </div>
   </div>
 </div>
index 6d100510d282530ca77b4192c6e4c8888e99c071..45d397b98558a6ebdc7c0676b3dbdd9f6f12ac95 100644 (file)
@@ -13,16 +13,59 @@ import { PermissionsService } from 'src/app/services/permissions.service'
 import { By } from '@angular/platform-browser'
 import { SavedViewWidgetComponent } from './widgets/saved-view-widget/saved-view-widget.component'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
-import { NgxFileDropModule } from 'ngx-file-drop'
 import { RouterTestingModule } from '@angular/router/testing'
 import { TourNgBootstrapModule, TourService } from 'ngx-ui-tour-ng-bootstrap'
 import { LogoComponent } from '../common/logo/logo.component'
+import { of, throwError } from 'rxjs'
+import { DndDropEvent, DndModule } from 'ngx-drag-drop'
+import { ToastService } from 'src/app/services/toast.service'
+import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
+
+const saved_views = [
+  {
+    name: 'Saved View 0',
+    id: 0,
+    show_on_dashboard: true,
+    show_in_sidebar: true,
+    sort_field: 'name',
+    sort_reverse: true,
+    filter_rules: [],
+  },
+  {
+    name: 'Saved View 1',
+    id: 1,
+    show_on_dashboard: false,
+    show_in_sidebar: false,
+    sort_field: 'name',
+    sort_reverse: true,
+    filter_rules: [],
+  },
+  {
+    name: 'Saved View 2',
+    id: 2,
+    show_on_dashboard: true,
+    show_in_sidebar: false,
+    sort_field: 'name',
+    sort_reverse: true,
+    filter_rules: [],
+  },
+  {
+    name: 'Saved View 3',
+    id: 3,
+    show_on_dashboard: true,
+    show_in_sidebar: false,
+    sort_field: 'name',
+    sort_reverse: true,
+    filter_rules: [],
+  },
+]
 
 describe('DashboardComponent', () => {
   let component: DashboardComponent
   let fixture: ComponentFixture<DashboardComponent>
   let settingsService: SettingsService
   let tourService: TourService
+  let toastService: ToastService
 
   beforeEach(async () => {
     TestBed.configureTestingModule({
@@ -47,33 +90,22 @@ describe('DashboardComponent', () => {
         {
           provide: SavedViewService,
           useValue: {
-            dashboardViews: [
-              {
-                id: 1,
-                name: 'saved view 1',
-                show_on_dashboard: true,
-                sort_field: 'added',
-                sort_reverse: true,
-                filter_rules: [],
-              },
-              {
-                id: 2,
-                name: 'saved view 2',
-                show_on_dashboard: true,
-                sort_field: 'created',
-                sort_reverse: true,
-                filter_rules: [],
-              },
-            ],
+            listAll: () =>
+              of({
+                all: [saved_views.map((v) => v.id)],
+                count: saved_views.length,
+                results: saved_views,
+              }),
+            dashboardViews: saved_views.filter((v) => v.show_on_dashboard),
           },
         },
       ],
       imports: [
         NgbAlertModule,
         HttpClientTestingModule,
-        NgxFileDropModule,
         RouterTestingModule,
         TourNgBootstrapModule,
+        DndModule,
       ],
     }).compileComponents()
 
@@ -82,7 +114,11 @@ describe('DashboardComponent', () => {
       first_name: 'Foo',
       last_name: 'Bar',
     }
+    jest.spyOn(settingsService, 'get').mockImplementation((key) => {
+      if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER) return [0, 2, 3]
+    })
     tourService = TestBed.inject(TourService)
+    toastService = TestBed.inject(ToastService)
     fixture = TestBed.createComponent(DashboardComponent)
     component = fixture.componentInstance
 
@@ -100,7 +136,7 @@ describe('DashboardComponent', () => {
   it('should show dashboard widgets', () => {
     expect(
       fixture.debugElement.queryAll(By.directive(SavedViewWidgetComponent))
-    ).toHaveLength(2)
+    ).toHaveLength(saved_views.filter((v) => v.show_on_dashboard).length)
   })
 
   it('should end tour service if still running and welcome widget dismissed', () => {
@@ -116,4 +152,44 @@ describe('DashboardComponent', () => {
     component.completeTour()
     expect(settingsCompleteTourSpy).toHaveBeenCalled()
   })
+
+  it('should disable global dropzone on start drag + drop, re-enable after', () => {
+    expect(settingsService.globalDropzoneEnabled).toBeTruthy()
+    component.onDragStart(null)
+    expect(settingsService.globalDropzoneEnabled).toBeFalsy()
+    component.onDragEnd(null)
+    expect(settingsService.globalDropzoneEnabled).toBeTruthy()
+  })
+
+  it('should update saved view sorting on drag + drop, show info', () => {
+    const settingsSpy = jest.spyOn(settingsService, 'updateDashboardViewsSort')
+    const toastSpy = jest.spyOn(toastService, 'showInfo')
+    jest.spyOn(settingsService, 'storeSettings').mockReturnValue(of(true))
+    component.onDrop({ index: 2, data: saved_views[0] } as DndDropEvent)
+    component.onDragged(saved_views[0])
+    expect(settingsSpy).toHaveBeenCalledWith([
+      saved_views[2],
+      saved_views[0],
+      saved_views[3],
+    ])
+    expect(toastSpy).toHaveBeenCalled()
+    component.onDrop({ data: saved_views[3] } as DndDropEvent)
+  })
+
+  it('should update saved view sorting on drag + drop, show info2', () => {
+    jest.spyOn(settingsService, 'get').mockImplementation((key) => {
+      if (key === SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER) return []
+    })
+    fixture.destroy()
+    fixture = TestBed.createComponent(DashboardComponent)
+    component = fixture.componentInstance
+    fixture.detectChanges()
+    const toastSpy = jest.spyOn(toastService, 'showError')
+    jest
+      .spyOn(settingsService, 'storeSettings')
+      .mockReturnValue(throwError(() => new Error('unable to save')))
+    component.onDrop({ index: 2, data: saved_views[0] } as DndDropEvent)
+    component.onDragged(saved_views[0])
+    expect(toastSpy).toHaveBeenCalled()
+  })
 })
index 8d47495a83c0ced3a9a3a204fb47dee156d078aa..45ab053a4e092c59503d0b5cee1f167fc693625d 100644 (file)
@@ -3,6 +3,10 @@ import { SavedViewService } from 'src/app/services/rest/saved-view.service'
 import { SettingsService } from 'src/app/services/settings.service'
 import { ComponentWithPermissions } from '../with-permissions/with-permissions.component'
 import { TourService } from 'ngx-ui-tour-ng-bootstrap'
+import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
+import { DndDropEvent } from 'ngx-drag-drop'
+import { ToastService } from 'src/app/services/toast.service'
+import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
 
 @Component({
   selector: 'pngx-dashboard',
@@ -10,12 +14,33 @@ import { TourService } from 'ngx-ui-tour-ng-bootstrap'
   styleUrls: ['./dashboard.component.scss'],
 })
 export class DashboardComponent extends ComponentWithPermissions {
+  public dashboardViews: PaperlessSavedView[] = []
   constructor(
     public settingsService: SettingsService,
     public savedViewService: SavedViewService,
-    private tourService: TourService
+    private tourService: TourService,
+    private toastService: ToastService
   ) {
     super()
+
+    this.savedViewService.listAll().subscribe(() => {
+      const sorted: number[] = this.settingsService.get(
+        SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER
+      )
+      this.dashboardViews =
+        sorted?.length > 0
+          ? sorted
+              .map((id) =>
+                this.savedViewService.dashboardViews.find((v) => v.id === id)
+              )
+              .concat(
+                this.savedViewService.dashboardViews.filter(
+                  (v) => !sorted.includes(v.id)
+                )
+              )
+              .filter((v) => v)
+          : [...this.savedViewService.dashboardViews]
+    })
   }
 
   get subtitle() {
@@ -33,4 +58,35 @@ export class DashboardComponent extends ComponentWithPermissions {
       this.settingsService.completeTour()
     }
   }
+
+  onDragStart(event: DragEvent) {
+    this.settingsService.globalDropzoneEnabled = false
+  }
+
+  onDragged(v: PaperlessSavedView) {
+    const index = this.dashboardViews.indexOf(v)
+    this.dashboardViews.splice(index, 1)
+    this.settingsService
+      .updateDashboardViewsSort(this.dashboardViews)
+      .subscribe({
+        next: () => {
+          this.toastService.showInfo($localize`Dashboard updated`)
+        },
+        error: (e) => {
+          this.toastService.showError($localize`Error updating dashboard`, e)
+        },
+      })
+  }
+
+  onDragEnd(event: DragEvent) {
+    this.settingsService.globalDropzoneEnabled = true
+  }
+
+  onDrop(event: DndDropEvent) {
+    if (typeof event.index === 'undefined') {
+      event.index = this.dashboardViews.length
+    }
+
+    this.dashboardViews.splice(event.index, 0, event.data)
+  }
 }
index 9082d0cd86ccc7646241e6578fd828f0ccaef496..fdaa7e9e238f93efdd61de2ae858e29dc3960340 100644 (file)
@@ -1,22 +1,38 @@
-<pngx-widget-frame [title]="savedView.name" [loading]="loading">
+<pngx-widget-frame
+  *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }"
+  [title]="savedView.name"
+  [loading]="loading"
+  [draggable]="savedView"
+  (dndStart)="dndStart.emit($event)"
+  (dndMoved)="dndMoved.emit($event)"
+  (dndCanceled)="dndCanceled.emit($event)"
+  (dndEnd)="dndEnd.emit($event)"
+>
 
-  <a class="btn-link" header-buttons [routerLink]="[]" (click)="showAll()" *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }" i18n>Show all</a>
+  <a *ngIf="documents.length" class="btn-link" header-buttons [routerLink]="[]" (click)="showAll()" i18n>Show all</a>
 
-
-  <table content class="table table-sm table-hover table-borderless mb-0">
+  <table *ngIf="documents.length; else empty" content class="table table-hover mb-0 align-middle">
     <thead>
       <tr>
         <th scope="col" i18n>Created</th>
         <th scope="col" i18n>Title</th>
+        <th scope="col" class="d-none d-md-table-cell" i18n>Tags</th>
+        <th scope="col" class="d-none d-md-table-cell" i18n>Correspondent</th>
       </tr>
     </thead>
-    <tbody *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
+    <tbody>
       <tr *ngFor="let doc of documents" (mouseleave)="mouseLeaveCard()">
-        <td><a routerLink="/documents/{{doc.id}}" class="d-block text-dark text-decoration-none">{{doc.created_date | customDate}}</a></td>
-        <td class="position-relative">
-          <a routerLink="/documents/{{doc.id}}" title="Edit" i18n-title class="d-block text-dark text-decoration-none">{{doc.title | documentTitle}}<pngx-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ms-1" (click)="clickTag(t, $event)"></pngx-tag></a>
+        <td class="py-2 py-md-3"><a routerLink="/documents/{{doc.id}}" class="btn-link text-dark text-decoration-none">{{doc.created_date | customDate}}</a></td>
+        <td class="py-2 py-md-3">
+          <a routerLink="/documents/{{doc.id}}" title="Edit" i18n-title class="btn-link text-dark text-decoration-none">{{doc.title | documentTitle}}</a>
+        </td>
+        <td class="py-2 py-md-3 d-none d-md-table-cell">
+          <pngx-tag [tag]="t" *ngFor="let t of doc.tags$ | async" class="ms-1" (click)="clickTag(t, $event)"></pngx-tag>
+        </td>
+        <td class="position-relative py-2 py-md-3 d-none d-md-table-cell">
+          <a *ngIf="doc.correspondent !== null" class="btn-link" routerLink="/documents" [queryParams]="getCorrespondentQueryParams(doc.correspondent)">{{(doc.correspondent$ | async)?.name}}</a>
           <div class="btn-group position-absolute top-50 end-0 translate-middle-y">
-            <a [href]="getPreviewUrl(doc)" title="View Preview" i18n-title target="_blank" class="btn btn-sm px-4 py-0 btn-dark border-dark-subtle"
+            <a [href]="getPreviewUrl(doc)" title="View Preview" i18n-title target="_blank" class="btn px-4 btn-dark border-dark-subtle"
             [ngbPopover]="previewContent" [popoverTitle]="doc.title | documentTitle"
             autoClose="true" popoverClass="shadow popover-preview" container="body" (mouseenter)="mouseEnterPreview(doc)" (mouseleave)="mouseLeavePreview()" #popover="ngbPopover">
               <svg class="buttonicon-xs" fill="currentColor">
@@ -26,7 +42,7 @@
             <ng-template #previewContent>
               <object [data]="getPreviewUrl(doc) | safeUrl" class="preview" width="100%"></object>
             </ng-template>
-            <a [href]="getDownloadUrl(doc)" class="btn btn-sm px-4 py-0 btn-dark border-dark-subtle" title="Download" i18n-title (click)="$event.stopPropagation()">
+            <a [href]="getDownloadUrl(doc)" class="btn px-4 btn-dark border-dark-subtle" title="Download" i18n-title (click)="$event.stopPropagation()">
               <svg class="buttonicon-xs" fill="currentColor">
                 <use xlink:href="assets/bootstrap-icons.svg#download"/>
               </svg>
@@ -37,4 +53,8 @@
     </tbody>
   </table>
 
+  <ng-template #empty>
+    <p i18n class="text-center text-muted mb-0 fst-italic">No documents</p>
+  </ng-template>
+
 </pngx-widget-frame>
index 941d474d48ff1f4b4a79db99b6935a5b19c4b736..bf1894b48bdb1a2939e403c3062a7229d4c7b182 100644 (file)
@@ -5,9 +5,12 @@ table {
 
 th:first-child {
   width: 25%;
+  @media (min-width: 768px) {
+    width: 15%;
+  }
 }
 
-tbody app-tag {
+tbody pngx-tag {
   cursor: pointer;
 }
 
@@ -22,3 +25,8 @@ tr:hover .btn-group {
   opacity: 1;
   pointer-events: all;
 }
+
+td.py-3 {
+  padding-top: 0.75em !important;
+  padding-bottom: 0.75em !important;
+}
index 7e1a77ae8ee49cd5fb2ad46ad6042b372d5dd22f..c355bdf6113441903e118e8e848d50eba15eefc4 100644 (file)
@@ -28,6 +28,7 @@ import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
 import { SavedViewWidgetComponent } from './saved-view-widget.component'
 import { By } from '@angular/platform-browser'
 import { SafeUrlPipe } from 'src/app/pipes/safeurl.pipe'
+import { DndModule } from 'ngx-drag-drop'
 
 const savedView: PaperlessSavedView = {
   id: 1,
@@ -52,6 +53,7 @@ const documentResults = [
   {
     id: 3,
     title: 'doc3',
+    correspondent: 0,
   },
 ]
 
@@ -89,6 +91,7 @@ describe('SavedViewWidgetComponent', () => {
         HttpClientTestingModule,
         NgbModule,
         RouterTestingModule.withRoutes(routes),
+        DndModule,
       ],
     }).compileComponents()
 
index efc288378ed3016ec540a56a37f5e1b19a61c73b..f6a5d8c49efe716be0d2769400ecab31e3d9c92b 100644 (file)
@@ -1,23 +1,29 @@
 import {
   Component,
+  EventEmitter,
   Input,
   OnDestroy,
   OnInit,
+  Output,
   QueryList,
   ViewChildren,
 } from '@angular/core'
-import { Router } from '@angular/router'
+import { Params, Router } from '@angular/router'
 import { Subject, takeUntil } from 'rxjs'
 import { PaperlessDocument } from 'src/app/data/paperless-document'
 import { PaperlessSavedView } from 'src/app/data/paperless-saved-view'
 import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
 import { DocumentService } from 'src/app/services/rest/document.service'
 import { PaperlessTag } from 'src/app/data/paperless-tag'
-import { FILTER_HAS_TAGS_ALL } from 'src/app/data/filter-rule-type'
+import {
+  FILTER_CORRESPONDENT,
+  FILTER_HAS_TAGS_ALL,
+} from 'src/app/data/filter-rule-type'
 import { OpenDocumentsService } from 'src/app/services/open-documents.service'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
 import { NgbPopover } from '@ng-bootstrap/ng-bootstrap'
+import { queryParamsFromFilterRules } from 'src/app/utils/query-params'
 
 @Component({
   selector: 'pngx-saved-view-widget',
@@ -38,7 +44,8 @@ export class SavedViewWidgetComponent
     private router: Router,
     private list: DocumentListViewService,
     private consumerStatusService: ConsumerStatusService,
-    public openDocumentsService: OpenDocumentsService
+    public openDocumentsService: OpenDocumentsService,
+    public documentListViewService: DocumentListViewService
   ) {
     super()
   }
@@ -46,6 +53,18 @@ export class SavedViewWidgetComponent
   @Input()
   savedView: PaperlessSavedView
 
+  @Output()
+  dndStart: EventEmitter<DragEvent> = new EventEmitter()
+
+  @Output()
+  dndMoved: EventEmitter<DragEvent> = new EventEmitter()
+
+  @Output()
+  dndCanceled: EventEmitter<DragEvent> = new EventEmitter()
+
+  @Output()
+  dndEnd: EventEmitter<DragEvent> = new EventEmitter()
+
   documents: PaperlessDocument[] = []
 
   unsubscribeNotifier: Subject<any> = new Subject()
@@ -141,4 +160,15 @@ export class SavedViewWidgetComponent
   mouseLeaveCard() {
     this.popover?.close()
   }
+
+  getCorrespondentQueryParams(correspondentId: number): Params {
+    return correspondentId !== undefined
+      ? queryParamsFromFilterRules([
+          {
+            rule_type: FILTER_CORRESPONDENT,
+            value: correspondentId.toString(),
+          },
+        ])
+      : null
+  }
 }
index 3561bbcf4917f1152f4bf36c8951c8aa8ad974ca..f42100414c177eca5c293a5a1c9ec68b2f971039 100644 (file)
         </div>
       </div>
     </div>
+
+    <div class="list-group border-light mt-3">
+      <ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Tag }">
+        <a *ngIf="statistics?.tag_count > 0" class="list-group-item d-flex justify-content-between align-items-center" routerLink="/tags/">
+          <ng-container i18n>Tags</ng-container>:
+          <span class="badge bg-secondary text-light rounded-pill">{{statistics?.tag_count | number}}</span>
+        </a>
+      </ng-container>
+      <ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Correspondent }">
+        <a *ngIf="statistics?.correspondent_count > 0" class="list-group-item d-flex justify-content-between align-items-center" routerLink="/correspondents/">
+          <ng-container i18n>Correspondents</ng-container>:
+          <span class="badge bg-secondary text-light rounded-pill">{{statistics?.correspondent_count | number}}</span>
+        </a>
+      </ng-container>
+      <ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.DocumentType }">
+        <a *ngIf="statistics?.document_type_count > 0" class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documenttypes/">
+          <ng-container i18n>Document Types</ng-container>:
+          <span class="badge bg-secondary text-light rounded-pill">{{statistics?.document_type_count | number}}</span>
+        </a>
+      </ng-container>
+      <ng-container *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.StoragePath }">
+        <a *ngIf="statistics?.storage_path_count > 0" class="list-group-item d-flex justify-content-between align-items-center" routerLink="/documenttypes/">
+          <ng-container i18n>Storage Paths</ng-container>:
+          <span class="badge bg-secondary text-light rounded-pill">{{statistics?.storage_path_count | number}}</span>
+        </a>
+      </ng-container>
+    </div>
   </ng-container>
 </pngx-widget-frame>
index da07b7186d2c579792634d26a1a304da56d8bc8a..a838e7f52a9a8d79035a3312355aed597dbcfc89 100644 (file)
@@ -11,24 +11,42 @@ import { environment } from 'src/environments/environment'
 import { RouterTestingModule } from '@angular/router/testing'
 import { routes } from 'src/app/app-routing.module'
 import { PermissionsGuard } from 'src/app/guards/permissions.guard'
+import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
+import { DndModule } from 'ngx-drag-drop'
+import {
+  ConsumerStatusService,
+  FileStatus,
+} from 'src/app/services/consumer-status.service'
+import { Subject } from 'rxjs'
 
 describe('StatisticsWidgetComponent', () => {
   let component: StatisticsWidgetComponent
   let fixture: ComponentFixture<StatisticsWidgetComponent>
   let httpTestingController: HttpTestingController
+  let consumerStatusService: ConsumerStatusService
+  const fileStatusSubject = new Subject<FileStatus>()
 
   beforeEach(async () => {
     TestBed.configureTestingModule({
-      declarations: [StatisticsWidgetComponent, WidgetFrameComponent],
+      declarations: [
+        StatisticsWidgetComponent,
+        WidgetFrameComponent,
+        IfPermissionsDirective,
+      ],
       providers: [PermissionsGuard],
       imports: [
         HttpClientTestingModule,
         NgbModule,
         RouterTestingModule.withRoutes(routes),
+        DndModule,
       ],
     }).compileComponents()
 
     fixture = TestBed.createComponent(StatisticsWidgetComponent)
+    consumerStatusService = TestBed.inject(ConsumerStatusService)
+    jest
+      .spyOn(consumerStatusService, 'onDocumentConsumptionFinished')
+      .mockReturnValue(fileStatusSubject)
     component = fixture.componentInstance
 
     httpTestingController = TestBed.inject(HttpTestingController)
@@ -43,6 +61,12 @@ describe('StatisticsWidgetComponent', () => {
     expect(req.request.method).toEqual('GET')
   })
 
+  it('should reload after doc is consumed', () => {
+    const reloadSpy = jest.spyOn(component, 'reload')
+    fileStatusSubject.next(new FileStatus())
+    expect(reloadSpy).toHaveBeenCalled()
+  })
+
   it('should display inbox link with count', () => {
     const mockStats = {
       documents_total: 200,
@@ -107,4 +131,62 @@ describe('StatisticsWidgetComponent', () => {
       'CSV(10%)'
     )
   })
+
+  it('should limit mime types to 5 max', () => {
+    const mockStats = {
+      documents_total: 222,
+      documents_inbox: 18,
+      inbox_tag: 10,
+      document_file_type_counts: [
+        {
+          mime_type: 'application/pdf',
+          mime_type_count: 160,
+        },
+        {
+          mime_type: 'text/plain',
+          mime_type_count: 20,
+        },
+        {
+          mime_type: 'text/csv',
+          mime_type_count: 20,
+        },
+        {
+          mime_type: 'application/vnd.oasis.opendocument.text',
+          mime_type_count: 11,
+        },
+        {
+          mime_type: 'application/msword',
+          mime_type_count: 9,
+        },
+        {
+          mime_type: 'image/jpeg',
+          mime_type_count: 2,
+        },
+      ],
+      character_count: 162312,
+    }
+
+    const req = httpTestingController.expectOne(
+      `${environment.apiBaseUrl}statistics/`
+    )
+
+    req.flush(mockStats)
+    fixture.detectChanges()
+
+    expect(fixture.nativeElement.textContent.replace(/\s/g, '')).toContain(
+      'PDF(72.1%)'
+    )
+    expect(fixture.nativeElement.textContent.replace(/\s/g, '')).toContain(
+      'TXT(9%)'
+    )
+    expect(fixture.nativeElement.textContent.replace(/\s/g, '')).toContain(
+      'CSV(9%)'
+    )
+    expect(fixture.nativeElement.textContent.replace(/\s/g, '')).toContain(
+      'ODT(5%)'
+    )
+    expect(fixture.nativeElement.textContent.replace(/\s/g, '')).toContain(
+      'Other(0.9%)'
+    )
+  })
 })
index be722197593f63dafeeb38f42b533f1c66bb0643..01799e9ac1c1caba80106f9e7ab0107b4d5c67c3 100644 (file)
@@ -6,6 +6,7 @@ import { ConsumerStatusService } from 'src/app/services/consumer-status.service'
 import { DocumentListViewService } from 'src/app/services/document-list-view.service'
 import { environment } from 'src/environments/environment'
 import * as mimeTypeNames from 'mime-names'
+import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
 
 export interface Statistics {
   documents_total?: number
@@ -13,6 +14,10 @@ export interface Statistics {
   inbox_tag?: number
   document_file_type_counts?: DocumentFileType[]
   character_count?: number
+  tag_count?: number
+  correspondent_count?: number
+  document_type_count?: number
+  storage_path_count?: number
 }
 
 interface DocumentFileType {
@@ -25,14 +30,19 @@ interface DocumentFileType {
   templateUrl: './statistics-widget.component.html',
   styleUrls: ['./statistics-widget.component.scss'],
 })
-export class StatisticsWidgetComponent implements OnInit, OnDestroy {
+export class StatisticsWidgetComponent
+  extends ComponentWithPermissions
+  implements OnInit, OnDestroy
+{
   loading: boolean = true
 
   constructor(
     private http: HttpClient,
     private consumerStatusService: ConsumerStatusService,
     private documentListViewService: DocumentListViewService
-  ) {}
+  ) {
+    super()
+  }
 
   statistics: Statistics = {}
 
@@ -87,7 +97,7 @@ export class StatisticsWidgetComponent implements OnInit, OnDestroy {
     this.reload()
     this.subscription = this.consumerStatusService
       .onDocumentConsumptionFinished()
-      .subscribe((status) => {
+      .subscribe(() => {
         this.reload()
       })
   }
index af1dd533b99b7d77f152f08ed7b5772ac3018b11..58b9800b828a9acaf4dcca6f84409593f187eea5 100644 (file)
@@ -1,24 +1,26 @@
 <pngx-widget-frame title="Upload new documents" i18n-title *pngxIfPermissions="{ action: PermissionAction.Add, type: PermissionType.Document }">
-  <div header-buttons>
-    <a *ngIf="getStatusSuccess().length > 0" (click)="dismissCompleted()" [routerLink]="[]" >
-      <span class="me-1" i18n="This button dismisses all status messages about processed documents on the dashboard (failed and successful)">Dismiss completed</span>
-      <svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-check2-all" viewBox="0 0 16 16">
-        <path d="M12.354 4.354a.5.5 0 0 0-.708-.708L5 10.293 1.854 7.146a.5.5 0 1 0-.708.708l3.5 3.5a.5.5 0 0 0 .708 0l7-7zm-4.208 7l-.896-.897.707-.707.543.543 6.646-6.647a.5.5 0 0 1 .708.708l-7 7a.5.5 0 0 1-.708 0z"/>
-        <path d="M5.354 7.146l.896.897-.707.707-.897-.896a.5.5 0 1 1 .708-.708z"/>
-      </svg>
-    </a>
-  </div>
   <div content tourAnchor="tour.upload-widget">
-    <form>
-      <ngx-file-drop dropZoneLabel="Drop documents here or" browseBtnLabel="Browse files" (onFileDrop)="dropped($event)"
-        (onFileOver)="fileOver($event)" (onFileLeave)="fileLeave($event)" dropZoneClassName="bg-light card"
-        multiple="true" contentClassName="justify-content-center d-flex align-items-center py-5 px-2" [showBrowseBtn]=true
-        browseBtnClassName="btn btn-sm btn-outline-primary ms-2" i18n-dropZoneLabel i18n-browseBtnLabel>
-      </ngx-file-drop>
+    <form class="justify-content-center d-flex flex-column align-items-center py-3 px-2">
+      <span class="text-muted" i18n>Drop documents anywhere or</span>
+      <button class="btn btn-sm btn-outline-primary mt-3" (click)="fileUpload.click()" i18n>Browse files</button>
+      <input type="file" class="visually-hidden" (change)="onFileSelected($event)" multiple #fileUpload>
     </form>
-    <p class="mt-3" *ngIf="getStatus().length > 0">{{getStatusSummary()}}</p>
-    <div *ngFor="let status of getStatus()">
-      <ng-container [ngTemplateOutlet]="consumerAlert" [ngTemplateOutletContext]="{ $implicit: status }"></ng-container>
+    <div class="fixed-bottom p-2 p-md-4" [ngClass]="slimSidebarEnabled ? 'col-slim' : 'offset-md-3 offset-lg-2'">
+      <div class="row d-flex justify-content-end">
+        <div class="col col-lg-4 col-xl-3 d-flex px-4 justify-content-between align-items-center">
+          <p class="m-0 small text-muted" *ngIf="getStatus().length > 0">{{getStatusSummary()}}</p>
+          <a *ngIf="getStatusCompleted().length > 0" class="btn-link" (click)="dismissCompleted()" [routerLink]="[]" >
+            <span class="me-1" i18n="This button dismisses all status messages about processed documents on the dashboard (failed and successful)">Dismiss completed</span>
+            <svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-check2-all" viewBox="0 0 16 16">
+              <path d="M12.354 4.354a.5.5 0 0 0-.708-.708L5 10.293 1.854 7.146a.5.5 0 1 0-.708.708l3.5 3.5a.5.5 0 0 0 .708 0l7-7zm-4.208 7l-.896-.897.707-.707.543.543 6.646-6.647a.5.5 0 0 1 .708.708l-7 7a.5.5 0 0 1-.708 0z"/>
+              <path d="M5.354 7.146l.896.897-.707.707-.897-.896a.5.5 0 1 1 .708-.708z"/>
+            </svg>
+          </a>
+        </div>
+      </div>
+      <div *ngFor="let status of getStatus()">
+        <ng-container [ngTemplateOutlet]="consumerAlert" [ngTemplateOutletContext]="{ $implicit: status }"></ng-container>
+      </div>
     </div>
     <div *ngIf="getStatusHidden().length" class="alerts-hidden">
       <p *ngIf="!alertsExpanded" class="mt-3 mb-0 text-center">
 </pngx-widget-frame>
 
 <ng-template #consumerAlert let-status>
-  <ngb-alert type="secondary" class="mt-2 mb-0" [dismissible]="isFinished(status)" (closed)="dismiss(status)">
-    <h6 class="alert-heading">{{status.filename}}</h6>
-    <p class="mb-0 pb-1" *ngIf="!isFinished(status) || (isFinished(status) && !status.documentId)">{{status.message}}</p>
-    <ngb-progressbar [value]="status.getProgress()" [max]="1" [type]="getStatusColor(status)"></ngb-progressbar>
-    <div *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
-      <div *ngIf="isFinished(status)">
-        <button *ngIf="status.documentId" class="btn btn-sm btn-outline-primary btn-open" routerLink="/documents/{{status.documentId}}" (click)="dismiss(status)">
-          <small i18n>Open document</small>
-          <svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-arrow-right-short" viewBox="0 0 16 16">
-            <path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z"/>
-          </svg>
-        </button>
-      </div>
+  <div class="row d-flex justify-content-end">
+    <div class="col col-lg-4 col-xl-3">
+      <ngb-alert type="secondary" class="mt-2 mb-0" [dismissible]="isFinished(status)" (closed)="dismiss(status)">
+        <h6 class="alert-heading">{{status.filename}}</h6>
+        <p class="mb-0 pb-1" *ngIf="!isFinished(status) || (isFinished(status) && !status.documentId)">{{status.message}}</p>
+        <ngb-progressbar [value]="status.getProgress()" [max]="1" [type]="getStatusColor(status)"></ngb-progressbar>
+        <div *pngxIfPermissions="{ action: PermissionAction.View, type: PermissionType.Document }">
+          <div *ngIf="isFinished(status)">
+            <button *ngIf="status.documentId" class="btn btn-sm btn-outline-primary btn-open" routerLink="/documents/{{status.documentId}}" (click)="dismiss(status)">
+              <small i18n>Open document</small>
+              <svg xmlns="http://www.w3.org/2000/svg" width="1rem" height="1rem" fill="currentColor" class="bi bi-arrow-right-short" viewBox="0 0 16 16">
+                <path fill-rule="evenodd" d="M4 8a.5.5 0 0 1 .5-.5h5.793L8.146 5.354a.5.5 0 1 1 .708-.708l3 3a.5.5 0 0 1 0 .708l-3 3a.5.5 0 0 1-.708-.708L10.293 8.5H4.5A.5.5 0 0 1 4 8z"/>
+              </svg>
+            </button>
+          </div>
+        </div>
+      </ngb-alert>
     </div>
-  </ngb-alert>
+  </div>
 </ng-template>
index 4b2e2f990e2687e2ebfe39094e229e05103fbb75..227e9e7f7dd603b309987c3a5442d4f71a5040d9 100644 (file)
@@ -1,5 +1,10 @@
 import { HttpClientTestingModule } from '@angular/common/http/testing'
-import { ComponentFixture, TestBed } from '@angular/core/testing'
+import {
+  ComponentFixture,
+  TestBed,
+  fakeAsync,
+  tick,
+} from '@angular/core/testing'
 import { By } from '@angular/platform-browser'
 import { RouterTestingModule } from '@angular/router/testing'
 import {
@@ -8,7 +13,6 @@ import {
   NgbAlert,
   NgbCollapse,
 } from '@ng-bootstrap/ng-bootstrap'
-import { NgxFileDropModule } from 'ngx-file-drop'
 import { routes } from 'src/app/app-routing.module'
 import { IfPermissionsDirective } from 'src/app/directives/if-permissions.directive'
 import { PermissionsGuard } from 'src/app/guards/permissions.guard'
@@ -21,6 +25,7 @@ import { PermissionsService } from 'src/app/services/permissions.service'
 import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
 import { WidgetFrameComponent } from '../widget-frame/widget-frame.component'
 import { UploadFileWidgetComponent } from './upload-file-widget.component'
+import { DndModule } from 'ngx-drag-drop'
 
 describe('UploadFileWidgetComponent', () => {
   let component: UploadFileWidgetComponent
@@ -48,8 +53,8 @@ describe('UploadFileWidgetComponent', () => {
         HttpClientTestingModule,
         NgbModule,
         RouterTestingModule.withRoutes(routes),
-        NgxFileDropModule,
         NgbAlertModule,
+        DndModule,
       ],
     }).compileComponents()
 
@@ -61,13 +66,21 @@ describe('UploadFileWidgetComponent', () => {
     fixture.detectChanges()
   })
 
-  it('should support drop files', () => {
+  it('should support browse files', () => {
+    const fileInput = fixture.debugElement.query(By.css('input'))
+    const clickSpy = jest.spyOn(fileInput.nativeElement, 'click')
+    fixture.debugElement
+      .query(By.css('button'))
+      .nativeElement.dispatchEvent(new Event('click'))
+    expect(clickSpy).toHaveBeenCalled()
+  })
+
+  it('should upload files', () => {
     const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFiles')
-    component.dropped([])
+    fixture.debugElement
+      .query(By.css('input'))
+      .nativeElement.dispatchEvent(new Event('change'))
     expect(uploadSpy).toHaveBeenCalled()
-    // coverage
-    component.fileLeave(null)
-    component.fileOver(null)
   })
 
   it('should generate stats summary', () => {
@@ -114,11 +127,15 @@ describe('UploadFileWidgetComponent', () => {
     expect(dismissSpy).toHaveBeenCalled()
   })
 
-  it('should allow dismissing all alerts', () => {
-    const dismissSpy = jest.spyOn(consumerStatusService, 'dismissCompleted')
+  it('should allow dismissing all alerts', fakeAsync(() => {
+    mockConsumerStatuses(consumerStatusService)
+    fixture.detectChanges()
+    const dismissSpy = jest.spyOn(consumerStatusService, 'dismiss')
     component.dismissCompleted()
-    expect(dismissSpy).toHaveBeenCalled()
-  })
+    tick(1000)
+    fixture.detectChanges()
+    expect(dismissSpy).toHaveBeenCalledTimes(6)
+  }))
 })
 
 function mockConsumerStatuses(consumerStatusService) {
index 22a3263e3695fe23f9bb4da2090b913d386d6317..8332012ca1c8f15ff05dad37ef3269eef9560dee 100644 (file)
@@ -1,11 +1,13 @@
-import { Component } from '@angular/core'
-import { NgxFileDropEntry } from 'ngx-file-drop'
+import { Component, QueryList, ViewChildren } from '@angular/core'
+import { NgbAlert } from '@ng-bootstrap/ng-bootstrap'
 import { ComponentWithPermissions } from 'src/app/components/with-permissions/with-permissions.component'
+import { SETTINGS_KEYS } from 'src/app/data/paperless-uisettings'
 import {
   ConsumerStatusService,
   FileStatus,
   FileStatusPhase,
 } from 'src/app/services/consumer-status.service'
+import { SettingsService } from 'src/app/services/settings.service'
 import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
 
 const MAX_ALERTS = 5
@@ -18,9 +20,12 @@ const MAX_ALERTS = 5
 export class UploadFileWidgetComponent extends ComponentWithPermissions {
   alertsExpanded = false
 
+  @ViewChildren(NgbAlert) alerts: QueryList<NgbAlert>
+
   constructor(
     private consumerStatusService: ConsumerStatusService,
-    private uploadDocumentsService: UploadDocumentsService
+    private uploadDocumentsService: UploadDocumentsService,
+    public settingsService: SettingsService
   ) {
     super()
   }
@@ -69,6 +74,10 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
     return this.consumerStatusService.getConsumerStatus(FileStatusPhase.SUCCESS)
   }
 
+  getStatusCompleted() {
+    return this.consumerStatusService.getConsumerStatusCompleted()
+  }
+
   getTotalUploadProgress() {
     let current = 0
     let max = 0
@@ -106,14 +115,16 @@ export class UploadFileWidgetComponent extends ComponentWithPermissions {
   }
 
   dismissCompleted() {
-    this.consumerStatusService.dismissCompleted()
+    this.alerts.forEach((a) => a.close())
   }
 
-  public fileOver(event) {}
-
-  public fileLeave(event) {}
+  public onFileSelected(event: Event) {
+    this.uploadDocumentsService.uploadFiles(
+      (event.target as HTMLInputElement).files
+    )
+  }
 
-  public dropped(files: NgxFileDropEntry[]) {
-    this.uploadDocumentsService.uploadFiles(files)
+  get slimSidebarEnabled(): boolean {
+    return this.settingsService.get(SETTINGS_KEYS.SLIM_SIDEBAR)
   }
 }
index 2fb15b090e766eafe312342e126a0e8e7e6ef5e6..1fb9980734f61a6ee06a22242dfd5ff6d451fd5d 100644 (file)
@@ -1,7 +1,21 @@
-<div class="card mb-3 shadow-sm bg-light">
+<div class="card shadow-sm bg-light"
+  [dndDraggable]="draggable"
+  dndEffectAllowed="move"
+  [dndDisableIf]="!draggable"
+  (dndStart)="dndStart.emit($event)"
+  (dndMoved)="dndMoved.emit($event)"
+  (dndCanceled)="dndCanceled.emit($event)"
+  (dndEnd)="dndEnd.emit($event)">
   <div class="card-header">
     <div class="d-flex justify-content-between align-items-center">
-      <h5 class="card-title mb-0">{{title}}</h5>
+      <div class="d-flex">
+        <div *ngIf="draggable" class="ms-n2 me-1" dndHandle>
+          <svg class="sidebaricon text-muted" fill="currentColor">
+            <use xlink:href="assets/bootstrap-icons.svg#grip-vertical"/>
+          </svg>
+        </div>
+        <h6 class="card-title mb-0">{{title}}</h6>
+      </div>
       <ng-container *ngIf="loading">
         <div class="spinner-border spinner-border-sm fw-normal ms-2 me-auto" role="status"></div>
         <div class="visually-hidden" i18n>Loading...</div>
index a0ff0a50d33a0a6a2ca077a99b2c6dd64321e9a0..74665ea013df9d1687df29dcdd46c91627361860 100644 (file)
@@ -4,6 +4,7 @@ import { By } from '@angular/platform-browser'
 import { NgbAlertModule, NgbAlert } from '@ng-bootstrap/ng-bootstrap'
 import { PermissionsGuard } from 'src/app/guards/permissions.guard'
 import { WidgetFrameComponent } from './widget-frame.component'
+import { DndModule } from 'ngx-drag-drop'
 
 @Component({
   template: `
@@ -29,7 +30,7 @@ describe('WidgetFrameComponent', () => {
     TestBed.configureTestingModule({
       declarations: [WidgetFrameComponent, WidgetFrameComponent],
       providers: [PermissionsGuard],
-      imports: [NgbAlertModule],
+      imports: [NgbAlertModule, DndModule],
     }).compileComponents()
 
     fixture = TestBed.createComponent(WidgetFrameComponent)
index 65f80ed084f4d5464f951a53c29477aa366e066b..e19154dce00c4a660e9dbfab5193b6987ddee44c 100644 (file)
@@ -1,4 +1,4 @@
-import { Component, Input } from '@angular/core'
+import { Component, EventEmitter, Input, Output } from '@angular/core'
 
 @Component({
   selector: 'pngx-widget-frame',
@@ -13,4 +13,19 @@ export class WidgetFrameComponent {
 
   @Input()
   loading: boolean = false
+
+  @Input()
+  draggable: any
+
+  @Output()
+  dndStart: EventEmitter<DragEvent> = new EventEmitter()
+
+  @Output()
+  dndMoved: EventEmitter<DragEvent> = new EventEmitter()
+
+  @Output()
+  dndCanceled: EventEmitter<DragEvent> = new EventEmitter()
+
+  @Output()
+  dndEnd: EventEmitter<DragEvent> = new EventEmitter()
 }
index 70931e5bc4cc901637e86842f0be38500ca00d32..2becf98601bbfc6b8cdf0ab22fd58fdc1a189d49 100644 (file)
@@ -81,7 +81,7 @@
 
 </pngx-page-header>
 
-<div class="row sticky-top pt-3 pt-sm-4 pb-3 pb-lg-4 bg-body">
+<div class="row sticky-top pb-3 bg-body">
   <pngx-filter-editor [hidden]="isBulkEditing" [(filterRules)]="list.filterRules" [unmodifiedFilterRules]="unmodifiedFilterRules" [selectionData]="list.selectionData" #filterEditor></pngx-filter-editor>
   <pngx-bulk-editor [hidden]="!isBulkEditing"></pngx-bulk-editor>
 </div>
diff --git a/src-ui/src/app/components/file-drop/file-drop.component.html b/src-ui/src/app/components/file-drop/file-drop.component.html
new file mode 100644 (file)
index 0000000..9e29ba6
--- /dev/null
@@ -0,0 +1,14 @@
+<div [class.pe-none]="fileIsOver">
+    <ng-content select="[content]"></ng-content>
+</div>
+
+<div class="global-dropzone-overlay position-fixed top-0 start-0 bottom-0 end-0 text-center pe-none fade" [class.show]="fileIsOver" [class.hide]="hidden">
+    <h2 class="pe-none position-absolute top-50 start-50 translate-middle" i18n>Drop files to begin upload</h2>
+</div>
+
+<ngx-file-drop
+    dropZoneClassName="visually-hidden"
+    contentClassName="visually-hidden"
+    (onFileDrop)="dropped($event)"
+    #ngxFileDrop>
+</ngx-file-drop>
diff --git a/src-ui/src/app/components/file-drop/file-drop.component.scss b/src-ui/src/app/components/file-drop/file-drop.component.scss
new file mode 100644 (file)
index 0000000..62636c4
--- /dev/null
@@ -0,0 +1,8 @@
+.global-dropzone-overlay {
+    background-color: hsla(var(--pngx-primary), var(--pngx-primary-lightness), .8);
+    z-index: 1200;
+
+    h2 {
+        color: var(--pngx-primary-text-contrast)
+    }
+}
diff --git a/src-ui/src/app/components/file-drop/file-drop.component.spec.ts b/src-ui/src/app/components/file-drop/file-drop.component.spec.ts
new file mode 100644 (file)
index 0000000..ec86ff2
--- /dev/null
@@ -0,0 +1,177 @@
+import { HttpClientTestingModule } from '@angular/common/http/testing'
+import {
+  ComponentFixture,
+  TestBed,
+  discardPeriodicTasks,
+  fakeAsync,
+  flush,
+  tick,
+} from '@angular/core/testing'
+import { By } from '@angular/platform-browser'
+import { PermissionsService } from 'src/app/services/permissions.service'
+import { SettingsService } from 'src/app/services/settings.service'
+import { ToastService } from 'src/app/services/toast.service'
+import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
+import { ToastsComponent } from '../common/toasts/toasts.component'
+import { FileDropComponent } from './file-drop.component'
+import { NgxFileDropEntry, NgxFileDropModule } from 'ngx-file-drop'
+
+describe('FileDropComponent', () => {
+  let component: FileDropComponent
+  let fixture: ComponentFixture<FileDropComponent>
+  let permissionsService: PermissionsService
+  let toastService: ToastService
+  let settingsService: SettingsService
+  let uploadDocumentsService: UploadDocumentsService
+
+  beforeEach(() => {
+    TestBed.configureTestingModule({
+      declarations: [FileDropComponent, ToastsComponent],
+      providers: [],
+      imports: [HttpClientTestingModule, NgxFileDropModule],
+    }).compileComponents()
+
+    permissionsService = TestBed.inject(PermissionsService)
+    settingsService = TestBed.inject(SettingsService)
+    toastService = TestBed.inject(ToastService)
+    uploadDocumentsService = TestBed.inject(UploadDocumentsService)
+
+    fixture = TestBed.createComponent(FileDropComponent)
+    component = fixture.componentInstance
+    fixture.detectChanges()
+  })
+
+  it('should enable drag-drop if user has permissions', () => {
+    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
+    expect(component.dragDropEnabled).toBeTruthy()
+  })
+
+  it('should disable drag-drop if user does not have permissions', () => {
+    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(false)
+    expect(component.dragDropEnabled).toBeFalsy()
+  })
+
+  it('should disable drag-drop if disabled in settings', fakeAsync(() => {
+    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
+    settingsService.globalDropzoneEnabled = false
+    expect(component.dragDropEnabled).toBeFalsy()
+
+    component.onDragOver(new Event('dragover') as DragEvent)
+    tick(1)
+    fixture.detectChanges()
+    expect(component.fileIsOver).toBeFalsy()
+    const dropzone = fixture.debugElement.query(
+      By.css('.global-dropzone-overlay')
+    )
+    expect(dropzone.classes['hide']).toBeTruthy()
+    component.onDragLeave(new Event('dragleave') as DragEvent)
+    tick(700)
+    fixture.detectChanges()
+    // drop
+    const uploadSpy = jest.spyOn(uploadDocumentsService, 'uploadFiles')
+    const dragEvent = new Event('drop')
+    dragEvent['dataTransfer'] = {
+      files: {
+        item: () => {},
+        length: 0,
+      },
+    }
+    component.onDrop(dragEvent as DragEvent)
+    tick(3000)
+    expect(uploadSpy).not.toHaveBeenCalled()
+  }))
+
+  it('should support drag drop, initiate upload', fakeAsync(() => {
+    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
+    expect(component.fileIsOver).toBeFalsy()
+    component.onDragOver(new Event('dragover') as DragEvent)
+    tick(1)
+    fixture.detectChanges()
+    expect(component.fileIsOver).toBeTruthy()
+    const dropzone = fixture.debugElement.query(
+      By.css('.global-dropzone-overlay')
+    )
+    component.onDragLeave(new Event('dragleave') as DragEvent)
+    tick(700)
+    fixture.detectChanges()
+    expect(dropzone.classes['hide']).toBeTruthy()
+    // drop
+    const toastSpy = jest.spyOn(toastService, 'show')
+    const uploadSpy = jest.spyOn(
+      UploadDocumentsService.prototype as any,
+      'uploadFile'
+    )
+    const dragEvent = new Event('drop')
+    dragEvent['dataTransfer'] = {
+      files: {
+        item: () => {
+          return new File(
+            [new Blob(['testing'], { type: 'application/pdf' })],
+            'file.pdf'
+          )
+        },
+        length: 1,
+      } as unknown as FileList,
+    }
+    component.onDrop(dragEvent as DragEvent)
+    component.dropped([
+      {
+        fileEntry: {
+          isFile: true,
+          file: (callback) => {
+            callback(
+              new File(
+                [new Blob(['testing'], { type: 'application/pdf' })],
+                'file.pdf'
+              )
+            )
+          },
+        },
+      } as unknown as NgxFileDropEntry,
+    ])
+    tick(3000)
+    expect(toastSpy).toHaveBeenCalled()
+    expect(uploadSpy).toHaveBeenCalled()
+    discardPeriodicTasks()
+  }))
+
+  it('should ignore events if disabled', fakeAsync(() => {
+    settingsService.globalDropzoneEnabled = false
+    expect(settingsService.globalDropzoneActive).toBeFalsy()
+    component.onDragOver(new Event('dragover') as DragEvent)
+    expect(settingsService.globalDropzoneActive).toBeFalsy()
+    settingsService.globalDropzoneActive = true
+    component.onDragLeave(new Event('dragleave') as DragEvent)
+    expect(settingsService.globalDropzoneActive).toBeTruthy()
+    component.onDrop(new Event('drop') as DragEvent)
+    expect(settingsService.globalDropzoneActive).toBeTruthy()
+  }))
+
+  it('should hide if app loses focus', fakeAsync(() => {
+    const leaveSpy = jest.spyOn(component, 'onDragLeave')
+    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
+    settingsService.globalDropzoneEnabled = true
+    component.onDragOver(new Event('dragover') as DragEvent)
+    tick(1)
+    expect(component.hidden).toBeFalsy()
+    expect(component.fileIsOver).toBeTruthy()
+    jest.spyOn(document, 'hidden', 'get').mockReturnValue(true)
+    component.onVisibilityChange()
+    expect(leaveSpy).toHaveBeenCalled()
+    flush()
+  }))
+
+  it('should hide on window blur', fakeAsync(() => {
+    const leaveSpy = jest.spyOn(component, 'onDragLeave')
+    jest.spyOn(permissionsService, 'currentUserCan').mockReturnValue(true)
+    settingsService.globalDropzoneEnabled = true
+    component.onDragOver(new Event('dragover') as DragEvent)
+    tick(1)
+    expect(component.hidden).toBeFalsy()
+    expect(component.fileIsOver).toBeTruthy()
+    jest.spyOn(document, 'hidden', 'get').mockReturnValue(true)
+    component.onWindowBlur()
+    expect(leaveSpy).toHaveBeenCalled()
+    flush()
+  }))
+})
diff --git a/src-ui/src/app/components/file-drop/file-drop.component.ts b/src-ui/src/app/components/file-drop/file-drop.component.ts
new file mode 100644 (file)
index 0000000..0c9637b
--- /dev/null
@@ -0,0 +1,98 @@
+import { Component, HostListener, ViewChild } from '@angular/core'
+import { NgxFileDropComponent, NgxFileDropEntry } from 'ngx-file-drop'
+import {
+  PermissionsService,
+  PermissionAction,
+  PermissionType,
+} from 'src/app/services/permissions.service'
+import { SettingsService } from 'src/app/services/settings.service'
+import { ToastService } from 'src/app/services/toast.service'
+import { UploadDocumentsService } from 'src/app/services/upload-documents.service'
+
+@Component({
+  selector: 'pngx-file-drop',
+  templateUrl: './file-drop.component.html',
+  styleUrls: ['./file-drop.component.scss'],
+})
+export class FileDropComponent {
+  private fileLeaveTimeoutID: any
+  fileIsOver: boolean = false
+  hidden: boolean = true
+
+  constructor(
+    private settings: SettingsService,
+    private toastService: ToastService,
+    private uploadDocumentsService: UploadDocumentsService,
+    private permissionsService: PermissionsService
+  ) {}
+
+  public get dragDropEnabled(): boolean {
+    return (
+      this.settings.globalDropzoneEnabled &&
+      this.permissionsService.currentUserCan(
+        PermissionAction.Add,
+        PermissionType.Document
+      )
+    )
+  }
+
+  @ViewChild('ngxFileDrop') ngxFileDrop: NgxFileDropComponent
+
+  @HostListener('dragover', ['$event ']) onDragOver(event: DragEvent) {
+    if (!this.dragDropEnabled) return
+    event.preventDefault()
+    event.stopImmediatePropagation()
+    this.settings.globalDropzoneActive = true
+    // allows transition
+    setTimeout(() => {
+      this.fileIsOver = true
+    }, 1)
+    this.hidden = false
+    // stop fileLeave timeout
+    clearTimeout(this.fileLeaveTimeoutID)
+  }
+
+  @HostListener('dragleave', ['$event']) public onDragLeave(
+    event: DragEvent,
+    immediate: boolean = false
+  ) {
+    if (!this.dragDropEnabled) return
+    event?.preventDefault()
+    event?.stopImmediatePropagation()
+    this.settings.globalDropzoneActive = false
+
+    const ms = immediate ? 0 : 500
+
+    this.fileLeaveTimeoutID = setTimeout(() => {
+      this.fileIsOver = false
+      // await transition completed
+      setTimeout(() => {
+        this.hidden = true
+      }, 150)
+    }, ms)
+  }
+
+  @HostListener('drop', ['$event']) public onDrop(event: DragEvent) {
+    if (!this.dragDropEnabled) return
+    event.preventDefault()
+    event.stopImmediatePropagation()
+    // pass event onto ngx-file-drop to handle files
+    this.ngxFileDrop.dropFiles(event)
+    this.onDragLeave(event, true)
+  }
+
+  public dropped(files: NgxFileDropEntry[]) {
+    this.uploadDocumentsService.onNgxFileDrop(files)
+    if (files.length > 0)
+      this.toastService.showInfo($localize`Initiating upload...`, 3000)
+  }
+
+  @HostListener('window:blur', ['$event']) public onWindowBlur() {
+    if (this.fileIsOver) this.onDragLeave(null)
+  }
+
+  @HostListener('document:visibilitychange', ['$event'])
+  public onVisibilityChange() {
+    if (document.hidden && this.fileIsOver) this.onDragLeave(null)
+  }
+}
index 064ebcf335f4c493727feb490f6864a9493c7d42..2f1e9e230b79866e6b91970bcd945a7c3aa45410 100644 (file)
@@ -41,6 +41,8 @@ export const SETTINGS_KEYS = {
     'general-settings:update-checking:backend-setting',
   SAVED_VIEWS_WARN_ON_UNSAVED_CHANGE:
     'general-settings:saved-views:warn-on-unsaved-change',
+  DASHBOARD_VIEWS_SORT_ORDER:
+    'general-settings:saved-views:dashboard-views-sort-order',
   TOUR_COMPLETE: 'general-settings:tour-complete',
   DEFAULT_PERMS_OWNER: 'general-settings:permissions:default-owner',
   DEFAULT_PERMS_VIEW_USERS: 'general-settings:permissions:default-view-users',
@@ -180,4 +182,9 @@ export const SETTINGS: PaperlessUiSetting[] = [
     type: 'array',
     default: [],
   },
+  {
+    key: SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER,
+    type: 'array',
+    default: [],
+  },
 ]
index 2b587fbfd9c84bd92e33ce325d6b61deab2995a5..82b081e9322c9ead91f30f77c45762c85247017a 100644 (file)
@@ -230,7 +230,10 @@ export class ConsumerStatusService {
 
   dismissCompleted() {
     this.consumerStatus = this.consumerStatus.filter(
-      (status) => status.phase != FileStatusPhase.SUCCESS
+      (status) =>
+        ![FileStatusPhase.SUCCESS, FileStatusPhase.FAILED].includes(
+          status.phase
+        )
     )
   }
 
index c0ba16d74c0e9b16a4a711c5fd54cf9afe2d2e9b..e7d13dffc3fd453f18b5ddcde9282a7f14070bf2 100644 (file)
@@ -15,6 +15,7 @@ import {
   SETTINGS_KEYS,
 } from '../data/paperless-uisettings'
 import { SettingsService } from './settings.service'
+import { PaperlessSavedView } from '../data/paperless-saved-view'
 
 describe('SettingsService', () => {
   let httpTestingController: HttpTestingController
@@ -277,4 +278,22 @@ describe('SettingsService', () => {
     )[0]
     expect(req.request.method).toEqual('POST')
   })
+
+  it('should update saved view sorting', () => {
+    httpTestingController
+      .expectOne(`${environment.apiBaseUrl}ui_settings/`)
+      .flush(ui_settings)
+    const setSpy = jest.spyOn(settingsService, 'set')
+    settingsService.updateDashboardViewsSort([
+      { id: 1 } as PaperlessSavedView,
+      { id: 4 } as PaperlessSavedView,
+    ])
+    expect(setSpy).toHaveBeenCalledWith(
+      SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER,
+      [1, 4]
+    )
+    httpTestingController
+      .expectOne(`${environment.apiBaseUrl}ui_settings/`)
+      .flush(ui_settings)
+  })
 })
index bd6404106ecf9751b76170b8d467f282ba470d83..6109135499216a639a66243cf1a7403f8604124a 100644 (file)
@@ -26,6 +26,7 @@ import { PaperlessUser } from '../data/paperless-user'
 import { PermissionsService } from './permissions.service'
 import { SavedViewService } from './rest/saved-view.service'
 import { ToastService } from './toast.service'
+import { PaperlessSavedView } from '../data/paperless-saved-view'
 
 export interface LanguageOption {
   code: string
@@ -54,6 +55,9 @@ export class SettingsService {
     return this._renderer
   }
 
+  public globalDropzoneEnabled: boolean = true
+  public globalDropzoneActive: boolean = false
+
   constructor(
     rendererFactory: RendererFactory2,
     @Inject(DOCUMENT) private document,
@@ -531,4 +535,13 @@ export class SettingsService {
         })
     }
   }
+
+  updateDashboardViewsSort(
+    dashboardViews: PaperlessSavedView[]
+  ): Observable<any> {
+    this.set(SETTINGS_KEYS.DASHBOARD_VIEWS_SORT_ORDER, [
+      ...new Set(dashboardViews.map((v) => v.id)),
+    ])
+    return this.storeSettings()
+  }
 }
index 0c3ab8033d24b68c792c46be0c574947b2153e5e..f1e1ae8b5e3fb7cd177a54255751f58e1854e51e 100644 (file)
@@ -5,12 +5,39 @@ import {
   HttpTestingController,
 } from '@angular/common/http/testing'
 import { environment } from 'src/environments/environment'
-import { HttpEventType, HttpResponse } from '@angular/common/http'
+import { HttpEventType } from '@angular/common/http'
 import {
   ConsumerStatusService,
   FileStatusPhase,
 } from './consumer-status.service'
 
+const files = [
+  {
+    lastModified: 1693349892540,
+    lastModifiedDate: new Date(),
+    name: 'file1.pdf',
+    size: 386,
+    type: 'application/pdf',
+  },
+  {
+    lastModified: 1695618533892,
+    lastModifiedDate: new Date(),
+    name: 'file2.pdf',
+    size: 358265,
+    type: 'application/pdf',
+  },
+]
+
+const fileList = {
+  item: (x) => {
+    return new File(
+      [new Blob(['testing'], { type: files[x].type })],
+      files[x].name
+    )
+  },
+  length: files.length,
+} as unknown as FileList
+
 describe('UploadDocumentsService', () => {
   let httpTestingController: HttpTestingController
   let uploadDocumentsService: UploadDocumentsService
@@ -32,66 +59,30 @@ describe('UploadDocumentsService', () => {
   })
 
   it('calls post_document api endpoint on upload', () => {
-    const fileEntry = {
-      name: 'file.pdf',
-      isDirectory: false,
-      isFile: true,
-      file: (callback) => {
-        return callback(
-          new File(
-            [new Blob(['testing'], { type: 'application/pdf' })],
-            'file.pdf'
-          )
-        )
-      },
-    }
-    uploadDocumentsService.uploadFiles([
-      {
-        relativePath: 'path/to/file.pdf',
-        fileEntry,
-      },
-    ])
-    const req = httpTestingController.expectOne(
+    uploadDocumentsService.uploadFiles(fileList)
+    const req = httpTestingController.match(
       `${environment.apiBaseUrl}documents/post_document/`
     )
-    expect(req.request.method).toEqual('POST')
+    expect(req[0].request.method).toEqual('POST')
 
-    req.flush('123-456')
+    req[0].flush('123-456')
   })
 
   it('updates progress during upload and failure', () => {
-    const fileEntry = {
-      name: 'file.pdf',
-      isDirectory: false,
-      isFile: true,
-      file: (callback) => {
-        return callback(
-          new File(
-            [new Blob(['testing'], { type: 'application/pdf' })],
-            'file.pdf'
-          )
-        )
-      },
-    }
-    uploadDocumentsService.uploadFiles([
-      {
-        relativePath: 'path/to/file.pdf',
-        fileEntry,
-      },
-    ])
+    uploadDocumentsService.uploadFiles(fileList)
 
     expect(consumerStatusService.getConsumerStatusNotCompleted()).toHaveLength(
-      1
+      2
     )
     expect(
       consumerStatusService.getConsumerStatus(FileStatusPhase.UPLOADING)
     ).toHaveLength(0)
 
-    const req = httpTestingController.expectOne(
+    const req = httpTestingController.match(
       `${environment.apiBaseUrl}documents/post_document/`
     )
 
-    req.event({
+    req[0].event({
       type: HttpEventType.UploadProgress,
       loaded: 100,
       total: 300,
@@ -103,27 +94,9 @@ describe('UploadDocumentsService', () => {
   })
 
   it('updates progress on failure', () => {
-    const fileEntry = {
-      name: 'file.pdf',
-      isDirectory: false,
-      isFile: true,
-      file: (callback) => {
-        return callback(
-          new File(
-            [new Blob(['testing'], { type: 'application/pdf' })],
-            'file.pdf'
-          )
-        )
-      },
-    }
-    uploadDocumentsService.uploadFiles([
-      {
-        relativePath: 'path/to/file.pdf',
-        fileEntry,
-      },
-    ])
+    uploadDocumentsService.uploadFiles(fileList)
 
-    let req = httpTestingController.expectOne(
+    let req = httpTestingController.match(
       `${environment.apiBaseUrl}documents/post_document/`
     )
 
@@ -131,7 +104,7 @@ describe('UploadDocumentsService', () => {
       consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED)
     ).toHaveLength(0)
 
-    req.flush(
+    req[0].flush(
       {},
       {
         status: 400,
@@ -143,18 +116,13 @@ describe('UploadDocumentsService', () => {
       consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED)
     ).toHaveLength(1)
 
-    uploadDocumentsService.uploadFiles([
-      {
-        relativePath: 'path/to/file.pdf',
-        fileEntry,
-      },
-    ])
+    uploadDocumentsService.uploadFiles(fileList)
 
-    req = httpTestingController.expectOne(
+    req = httpTestingController.match(
       `${environment.apiBaseUrl}documents/post_document/`
     )
 
-    req.flush(
+    req[0].flush(
       {},
       {
         status: 500,
@@ -166,4 +134,35 @@ describe('UploadDocumentsService', () => {
       consumerStatusService.getConsumerStatus(FileStatusPhase.FAILED)
     ).toHaveLength(2)
   })
+
+  it('accepts files via drag and drop', () => {
+    const uploadSpy = jest.spyOn(
+      UploadDocumentsService.prototype as any,
+      'uploadFile'
+    )
+    const fileEntry = {
+      name: 'file.pdf',
+      isDirectory: false,
+      isFile: true,
+      file: (callback) => {
+        return callback(
+          new File(
+            [new Blob(['testing'], { type: 'application/pdf' })],
+            'file.pdf'
+          )
+        )
+      },
+    }
+    uploadDocumentsService.onNgxFileDrop([
+      {
+        relativePath: 'path/to/file.pdf',
+        fileEntry,
+      },
+    ])
+    expect(uploadSpy).toHaveBeenCalled()
+
+    let req = httpTestingController.match(
+      `${environment.apiBaseUrl}documents/post_document/`
+    )
+  })
 })
index 5e7ef7fbe2b2fa6189bd81c54717938ea265920a..6a086cd99f7f56b0a7a3ba7d6c1b8ad01f06f55a 100644 (file)
@@ -19,56 +19,61 @@ export class UploadDocumentsService {
     private consumerStatusService: ConsumerStatusService
   ) {}
 
-  uploadFiles(files: NgxFileDropEntry[]) {
+  onNgxFileDrop(files: NgxFileDropEntry[]) {
     for (const droppedFile of files) {
       if (droppedFile.fileEntry.isFile) {
         const fileEntry = droppedFile.fileEntry as FileSystemFileEntry
-        fileEntry.file((file: File) => {
-          let formData = new FormData()
-          formData.append('document', file, file.name)
-          let status = this.consumerStatusService.newFileUpload(file.name)
-
-          status.message = $localize`Connecting...`
-
-          this.uploadSubscriptions[file.name] = this.documentService
-            .uploadDocument(formData)
-            .subscribe({
-              next: (event) => {
-                if (event.type == HttpEventType.UploadProgress) {
-                  status.updateProgress(
-                    FileStatusPhase.UPLOADING,
-                    event.loaded,
-                    event.total
-                  )
-                  status.message = $localize`Uploading...`
-                } else if (event.type == HttpEventType.Response) {
-                  status.taskId = event.body['task_id']
-                  status.message = $localize`Upload complete, waiting...`
-                  this.uploadSubscriptions[file.name]?.complete()
-                }
-              },
-              error: (error) => {
-                switch (error.status) {
-                  case 400: {
-                    this.consumerStatusService.fail(
-                      status,
-                      error.error.document
-                    )
-                    break
-                  }
-                  default: {
-                    this.consumerStatusService.fail(
-                      status,
-                      $localize`HTTP error: ${error.status} ${error.statusText}`
-                    )
-                    break
-                  }
-                }
-                this.uploadSubscriptions[file.name]?.complete()
-              },
-            })
-        })
+        fileEntry.file((file: File) => this.uploadFile(file))
       }
     }
   }
+
+  uploadFiles(files: FileList) {
+    for (let index = 0; index < files.length; index++) {
+      this.uploadFile(files.item(index))
+    }
+  }
+
+  private uploadFile(file: File) {
+    let formData = new FormData()
+    formData.append('document', file, file.name)
+    let status = this.consumerStatusService.newFileUpload(file.name)
+
+    status.message = $localize`Connecting...`
+
+    this.uploadSubscriptions[file.name] = this.documentService
+      .uploadDocument(formData)
+      .subscribe({
+        next: (event) => {
+          if (event.type == HttpEventType.UploadProgress) {
+            status.updateProgress(
+              FileStatusPhase.UPLOADING,
+              event.loaded,
+              event.total
+            )
+            status.message = $localize`Uploading...`
+          } else if (event.type == HttpEventType.Response) {
+            status.taskId = event.body['task_id']
+            status.message = $localize`Upload complete, waiting...`
+            this.uploadSubscriptions[file.name]?.complete()
+          }
+        },
+        error: (error) => {
+          switch (error.status) {
+            case 400: {
+              this.consumerStatusService.fail(status, error.error.document)
+              break
+            }
+            default: {
+              this.consumerStatusService.fail(
+                status,
+                $localize`HTTP error: ${error.status} ${error.statusText}`
+              )
+              break
+            }
+          }
+          this.uploadSubscriptions[file.name]?.complete()
+        },
+      })
+  }
 }
index 02fd51573b4851c0907e520d371f542a61ccd112..d93b6d8dbb8fc2bab9fb3482a8de35aa3a64a785 100644 (file)
@@ -16,6 +16,12 @@ body {
   transition: background-color 0.3s ease, border-color 0.3s ease;
 }
 
+@media(min-width: 768px) {
+  .col-slim {
+    padding-left: calc(50px + $grid-gutter-width) !important;
+  }
+}
+
 svg.logo {
   .leaf {
     fill: var(--bs-primary) !important;
@@ -478,50 +484,6 @@ table.table {
   color: var(--bs-body-color);
 }
 
-.main-dropzone {
-  height: 100%;
-  width: 100%;
-
-  &.ngx-file-drop__drop-zone--over {
-    background-color: transparent !important;
-  }
-}
-
-.global-dropzone-overlay {
-  position: fixed;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  background-color: hsla(var(--pngx-primary), var(--pngx-primary-lightness), .8);
-  z-index: 1055; // $zindex-modal
-  pointer-events: none !important;
-  user-select: none !important;
-  text-align: center;
-  padding-top: 25%;
-
-  h2 {
-    color: var(--pngx-primary-text-contrast)
-  }
-
-  &.show {
-    opacity: 1 !important;
-  }
-
-  &.hide {
-    display: none;
-  }
-}
-
-.ngx-file-drop__drop-zone--over .global-dropzone-overlay {
-  opacity: 0;
-}
-
-.inert {
-  pointer-events: none !important;
-  user-select: none !important;
-}
-
 .alert-primary {
   --bs-alert-color: var(--bs-primary);
   --bs-alert-bg: var(--pngx-primary-faded);
index 02817404431fec3edc05da3fc72ba71f36c6c54a..ac5654c140f5bbf72f0b555661c71dcfbb4a0915 100644 (file)
@@ -214,6 +214,10 @@ $form-check-radio-checked-bg-image-dark: url("data:image/svg+xml,<svg xmlns='htt
     }
   }
 
+  .card .table {
+    border-color: var(--bs-gray-800);
+  }
+
   .alert-secondary {
     background-color: var(--bs-light);
     border-color: var(--pngx-bg-darker);
index 242bc5702ce211a02a81127ae89d59a7546f9995..a47c78dc501e98b7dcfbf26633a4ab8fb788b849 100644 (file)
@@ -1571,6 +1571,13 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
         )
 
         tag_inbox = Tag.objects.create(name="t1", is_inbox_tag=True)
+        Tag.objects.create(name="t2")
+        Tag.objects.create(name="t3")
+        Correspondent.objects.create(name="c1")
+        Correspondent.objects.create(name="c2")
+        DocumentType.objects.create(name="dt1")
+        StoragePath.objects.create(name="sp1")
+        StoragePath.objects.create(name="sp2")
 
         doc1.tags.add(tag_inbox)
 
@@ -1588,6 +1595,10 @@ class TestDocumentApi(DirectoriesMixin, DocumentConsumeDelayMixin, APITestCase):
             1,
         )
         self.assertEqual(response.data["character_count"], 11)
+        self.assertEqual(response.data["tag_count"], 3)
+        self.assertEqual(response.data["correspondent_count"], 2)
+        self.assertEqual(response.data["document_type_count"], 1)
+        self.assertEqual(response.data["storage_path_count"], 2)
 
     def test_statistics_no_inbox_tag(self):
         Document.objects.create(title="none1", checksum="A")
index e572527f694b6a3d619596828357bfe1e232ff47..9440741e75206ff1885cb278c43b768fefea352a 100644 (file)
@@ -907,6 +907,39 @@ class StatisticsView(APIView):
             if user is None
             else get_objects_for_user_owner_aware(user, "documents.view_tag", Tag)
         )
+        correspondent_count = (
+            Correspondent.objects.count()
+            if user is None
+            else len(
+                get_objects_for_user_owner_aware(
+                    user,
+                    "documents.view_correspondent",
+                    Correspondent,
+                ),
+            )
+        )
+        document_type_count = (
+            DocumentType.objects.count()
+            if user is None
+            else len(
+                get_objects_for_user_owner_aware(
+                    user,
+                    "documents.view_documenttype",
+                    DocumentType,
+                ),
+            )
+        )
+        storage_path_count = (
+            StoragePath.objects.count()
+            if user is None
+            else len(
+                get_objects_for_user_owner_aware(
+                    user,
+                    "documents.view_storagepath",
+                    StoragePath,
+                ),
+            )
+        )
 
         documents_total = documents.count()
 
@@ -941,6 +974,10 @@ class StatisticsView(APIView):
                 "inbox_tag": inbox_tag.first().pk if inbox_tag.exists() else None,
                 "document_file_type_counts": document_file_type_counts,
                 "character_count": character_count,
+                "tag_count": len(tags),
+                "correspondent_count": correspondent_count,
+                "document_type_count": document_type_count,
+                "storage_path_count": storage_path_count,
             },
         )