From e36cdaad0d56280aec7cd02c0c78e77dc0c4d9a1 Mon Sep 17 00:00:00 2001
From: Dan Brown <ssddanbrown@googlemail.com>
Date: Wed, 26 Apr 2023 16:41:34 +0100
Subject: [PATCH] Updated attachments to work with new dropzone

- Fixes existing broken attachment edit tabs.
- Redesigns area to move away from old tabbed interface.
- Integrates new dropzone system, for both addition and edit.
---
 lang/en/components.php                        |  1 -
 lang/en/entities.php                          |  4 +-
 lang/en/errors.php                            |  1 +
 resources/js/components/attachments.js        | 38 ++++++---
 resources/js/components/editor-toolbox.js     |  4 +
 resources/sass/_components.scss               | 11 ++-
 resources/sass/_layout.scss                   |  9 +++
 .../attachments/manager-edit-form.blade.php   | 33 ++++++--
 .../attachments/manager-link-form.blade.php   |  3 +
 resources/views/attachments/manager.blade.php | 80 +++++++------------
 resources/views/form/dropzone.blade.php       |  8 --
 .../views/form/simple-dropzone.blade.php      | 21 +++++
 .../views/pages/parts/image-manager.blade.php |  2 +-
 13 files changed, 131 insertions(+), 84 deletions(-)
 delete mode 100644 resources/views/form/dropzone.blade.php
 create mode 100644 resources/views/form/simple-dropzone.blade.php

diff --git a/lang/en/components.php b/lang/en/components.php
index 426a427d2..cd5dca251 100644
--- a/lang/en/components.php
+++ b/lang/en/components.php
@@ -25,7 +25,6 @@ return [
     'images_deleted' => 'Images Deleted',
     'image_preview' => 'Image Preview',
     'image_upload_success' => 'Image uploaded successfully',
-    'image_upload_failure' => 'Image failed to upload',
     'image_update_success' => 'Image details successfully updated',
     'image_delete_success' => 'Image successfully deleted',
 
diff --git a/lang/en/entities.php b/lang/en/entities.php
index 9b02f3111..9614f92fe 100644
--- a/lang/en/entities.php
+++ b/lang/en/entities.php
@@ -311,12 +311,12 @@ return [
     'attachments' => 'Attachments',
     'attachments_explain' => 'Upload some files or attach some links to display on your page. These are visible in the page sidebar.',
     'attachments_explain_instant_save' => 'Changes here are saved instantly.',
-    'attachments_items' => 'Attached Items',
     'attachments_upload' => 'Upload File',
     'attachments_link' => 'Attach Link',
+    'attachments_upload_drop' => 'Alternatively you can drag and drop a file here to upload it as an attachment.',
     'attachments_set_link' => 'Set Link',
     'attachments_delete' => 'Are you sure you want to delete this attachment?',
-    'attachments_dropzone' => 'Drop files or click here to attach a file',
+    'attachments_dropzone' => 'Drop files here to upload',
     'attachments_no_files' => 'No files have been uploaded',
     'attachments_explain_link' => 'You can attach a link if you\'d prefer not to upload a file. This can be a link to another page or a link to a file in the cloud.',
     'attachments_link_name' => 'Link Name',
diff --git a/lang/en/errors.php b/lang/en/errors.php
index eca39c946..6991f96e4 100644
--- a/lang/en/errors.php
+++ b/lang/en/errors.php
@@ -53,6 +53,7 @@ return [
 
     // Attachments
     'attachment_not_found' => 'Attachment not found',
+    'attachment_upload_error' => 'An error occurred uploading the attachment file',
 
     // Pages
     'page_draft_autosave_fail' => 'Failed to save draft. Ensure you have internet connection before saving this page',
diff --git a/resources/js/components/attachments.js b/resources/js/components/attachments.js
index 9555a59e8..e185b3c5c 100644
--- a/resources/js/components/attachments.js
+++ b/resources/js/components/attachments.js
@@ -1,4 +1,4 @@
-import {showLoading} from '../services/dom';
+import {onSelect, showLoading} from '../services/dom';
 import {Component} from './component';
 
 export class Attachments extends Component {
@@ -8,15 +8,16 @@ export class Attachments extends Component {
         this.pageId = this.$opts.pageId;
         this.editContainer = this.$refs.editContainer;
         this.listContainer = this.$refs.listContainer;
-        this.mainTabs = this.$refs.mainTabs;
-        this.list = this.$refs.list;
+        this.linksContainer = this.$refs.linksContainer;
+        this.listPanel = this.$refs.listPanel;
+        this.attachLinkButton = this.$refs.attachLinkButton;
 
         this.setupListeners();
     }
 
     setupListeners() {
         const reloadListBound = this.reloadList.bind(this);
-        this.container.addEventListener('dropzone-success', reloadListBound);
+        this.container.addEventListener('dropzone-upload-success', reloadListBound);
         this.container.addEventListener('ajax-form-success', reloadListBound);
 
         this.container.addEventListener('sortable-list-sort', event => {
@@ -39,16 +40,29 @@ export class Attachments extends Component {
                 markdown: contentTypes['text/plain'],
             });
         });
+
+        this.attachLinkButton.addEventListener('click', () => {
+            this.showSection('links');
+        });
+    }
+
+    showSection(section) {
+        const sectionMap = {
+            links: this.linksContainer,
+            edit: this.editContainer,
+            list: this.listContainer,
+        };
+
+        for (const [name, elem] of Object.entries(sectionMap)) {
+            elem.toggleAttribute('hidden', name !== section);
+        }
     }
 
     reloadList() {
         this.stopEdit();
-        /** @var {Tabs} */
-        const tabs = window.$components.firstOnElement(this.mainTabs, 'tabs');
-        tabs.show('attachment-panel-items');
         window.$http.get(`/attachments/get/page/${this.pageId}`).then(resp => {
-            this.list.innerHTML = resp.data;
-            window.$components.init(this.list);
+            this.listPanel.innerHTML = resp.data;
+            window.$components.init(this.listPanel);
         });
     }
 
@@ -59,8 +73,7 @@ export class Attachments extends Component {
     }
 
     async startEdit(id) {
-        this.editContainer.classList.remove('hidden');
-        this.listContainer.classList.add('hidden');
+        this.showSection('edit');
 
         showLoading(this.editContainer);
         const resp = await window.$http.get(`/attachments/edit/${id}`);
@@ -69,8 +82,7 @@ export class Attachments extends Component {
     }
 
     stopEdit() {
-        this.editContainer.classList.add('hidden');
-        this.listContainer.classList.remove('hidden');
+        this.showSection('list');
     }
 
 }
diff --git a/resources/js/components/editor-toolbox.js b/resources/js/components/editor-toolbox.js
index 4d3c0ae75..1f1fce9dc 100644
--- a/resources/js/components/editor-toolbox.js
+++ b/resources/js/components/editor-toolbox.js
@@ -13,6 +13,10 @@ export class EditorToolbox extends Component {
 
         // Set the first tab as active on load
         this.setActiveTab(this.contentElements[0].dataset.tabContent);
+
+        setTimeout(() => {
+            this.setActiveTab('files', true);
+        }, 500);
     }
 
     setupListeners() {
diff --git a/resources/sass/_components.scss b/resources/sass/_components.scss
index d1a11a964..e28ec2836 100644
--- a/resources/sass/_components.scss
+++ b/resources/sass/_components.scss
@@ -228,6 +228,15 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
   animation: dzAnimIn 240ms ease-in-out;
 }
 
+.dropzone-landing-area {
+  background-color: var(--color-primary-light);
+  padding: $-m $-l;
+  width: 100%;
+  border: 1px dashed var(--color-primary);
+  color: var(--color-primary);
+  border-radius: 4px;
+}
+
 @keyframes dzAnimIn {
   0% {
     opacity: 0;
@@ -319,7 +328,7 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
 .dropzone-file-item-label,
 .dropzone-file-item-status {
   align-items: center;
-  font-size: .9rem;
+  font-size: .8rem;
   font-weight: 700;
 }
 .dropzone-file-item-status[data-status] {
diff --git a/resources/sass/_layout.scss b/resources/sass/_layout.scss
index 19333faf7..541978a65 100644
--- a/resources/sass/_layout.scss
+++ b/resources/sass/_layout.scss
@@ -253,6 +253,15 @@ body.flexbox {
   position: relative;
 }
 
+.fixed {
+  position: fixed;
+  z-index: 20;
+  &.top-right {
+    top: 0;
+    right: 0;
+  }
+}
+
 .hidden {
   display: none !important;
 }
diff --git a/resources/views/attachments/manager-edit-form.blade.php b/resources/views/attachments/manager-edit-form.blade.php
index 15837448a..9422a0752 100644
--- a/resources/views/attachments/manager-edit-form.blade.php
+++ b/resources/views/attachments/manager-edit-form.blade.php
@@ -17,18 +17,35 @@
     </div>
 
     <div component="tabs" class="tab-container">
-        <div class="nav-tabs">
-            <button refs="tabs@toggleFile" type="button" class="tab-item {{ $attachment->external ? '' : 'selected' }}">{{ trans('entities.attachments_upload') }}</button>
-            <button refs="tabs@toggleLink" type="button" class="tab-item {{ $attachment->external ? 'selected' : '' }}">{{ trans('entities.attachments_set_link') }}</button>
+        <div class="nav-tabs" role="tablist">
+            <button id="attachment-edit-file-tab"
+                    type="button"
+                    aria-controls="attachment-edit-file-panel"
+                    aria-selected="{{ $attachment->external ? 'false' : 'true' }}"
+                    role="tab">{{ trans('entities.attachments_upload') }}</button>
+            <button id="attachment-edit-link-tab"
+                    type="button"
+                    aria-controls="attachment-edit-link-panel"
+                    aria-selected="{{ $attachment->external ? 'true' : 'false' }}"
+                    role="tab">{{ trans('entities.attachments_set_link') }}</button>
         </div>
-        <div refs="tabs@contentFile" class="mb-m {{ $attachment->external ? 'hidden' : '' }}">
-            @include('form.dropzone', [
+        <div id="attachment-edit-file-panel"
+             @if($attachment->external) hidden @endif
+             tabindex="0"
+             role="tabpanel"
+             aria-labelledby="attachment-edit-file-tab"
+             class="mb-m">
+            @include('form.simple-dropzone', [
                 'placeholder' => trans('entities.attachments_edit_drop_upload'),
                 'url' =>  url('/attachments/upload/' . $attachment->id),
                 'successMessage' => trans('entities.attachments_file_updated'),
             ])
         </div>
-        <div refs="tabs@contentLink" class="{{ $attachment->external ? '' : 'hidden' }}">
+        <div id="attachment-edit-link-panel"
+             @if(!$attachment->external) hidden @endif
+             tabindex="0"
+             role="tabpanel"
+             aria-labelledby="attachment-edit-link-tab">
             <div class="form-group">
                 <label for="attachment_edit_url">{{ trans('entities.attachments_link_url') }}</label>
                 <input type="text" id="attachment_edit_url"
@@ -43,6 +60,8 @@
     </div>
 
     <button component="event-emit-select"
-            option:event-emit-select:name="edit-back" type="button" class="button outline">{{ trans('common.back') }}</button>
+            option:event-emit-select:name="edit-back"
+            type="button"
+            class="button outline">{{ trans('common.back') }}</button>
     <button refs="ajax-form@submit" type="button" class="button">{{ trans('common.save') }}</button>
 </div>
\ No newline at end of file
diff --git a/resources/views/attachments/manager-link-form.blade.php b/resources/views/attachments/manager-link-form.blade.php
index b51daa40e..e9ec2d6dc 100644
--- a/resources/views/attachments/manager-link-form.blade.php
+++ b/resources/views/attachments/manager-link-form.blade.php
@@ -22,6 +22,9 @@
             <div class="text-neg text-small">{{ $errors->first('attachment_link_url') }}</div>
         @endif
     </div>
+    <button component="event-emit-select"
+            option:event-emit-select:name="edit-back"
+            type="button" class="button outline">{{ trans('common.cancel') }}</button>
     <button refs="ajax-form@submit"
             type="button"
             class="button">{{ trans('entities.attach') }}</button>
diff --git a/resources/views/attachments/manager.blade.php b/resources/views/attachments/manager.blade.php
index 7d14d00e7..4eeee24bb 100644
--- a/resources/views/attachments/manager.blade.php
+++ b/resources/views/attachments/manager.blade.php
@@ -6,66 +6,44 @@
      class="toolbox-tab-content">
 
     <h4>{{ trans('entities.attachments') }}</h4>
-    <div class="px-l files">
+    <div component="dropzone"
+         option:dropzone:url="{{ url('/attachments/upload?uploaded_to=' . $page->id) }}"
+         option:dropzone:success-message="{{ trans('entities.attachments_file_uploaded') }}"
+         option:dropzone:error-message="{{ trans('errors.attachment_upload_error') }}"
+         option:dropzone:upload-limit="{{ config('app.upload_limit') }}"
+         option:dropzone:upload-limit-message="{{ trans('errors.server_upload_limit') }}"
+         option:dropzone:zone-text="{{ trans('entities.attachments_dropzone') }}"
+         option:dropzone:file-accept="*"
+         class="px-l files">
 
-        <div refs="attachments@listContainer">
+        <div refs="attachments@list-container dropzone@drop-target" class="relative">
             <p class="text-muted small">{{ trans('entities.attachments_explain') }} <span
                         class="text-warn">{{ trans('entities.attachments_explain_instant_save') }}</span></p>
 
-            <div component="tabs" refs="attachments@mainTabs" class="tab-container">
-                <div role="tablist">
-                    <button id="attachment-tab-items"
-                            role="tab"
-                            aria-selected="true"
-                            aria-controls="attachment-panel-items"
-                            type="button"
-                            class="tab-item">{{ trans('entities.attachments_items') }}</button>
-                    <button id="attachment-tab-upload"
-                            role="tab"
-                            aria-selected="false"
-                            aria-controls="attachment-panel-upload"
-                            type="button"
-                            class="tab-item">{{ trans('entities.attachments_upload') }}</button>
-                    <button id="attachment-tab-links"
-                            role="tab"
-                            aria-selected="false"
-                            aria-controls="attachment-panel-links"
-                            type="button"
-                            class="tab-item">{{ trans('entities.attachments_link') }}</button>
-                </div>
-                <div id="attachment-panel-items"
-                     tabindex="0"
-                     role="tabpanel"
-                     aria-labelledby="attachment-tab-items"
-                     refs="attachments@list">
-                    @include('attachments.manager-list', ['attachments' => $page->attachments->all()])
-                </div>
-                <div id="attachment-panel-upload"
-                     tabindex="0"
-                     role="tabpanel"
-                     hidden
-                     aria-labelledby="attachment-tab-upload">
-                    @include('form.dropzone', [
-                        'placeholder' => trans('entities.attachments_dropzone'),
-                        'url' =>  url('/attachments/upload?uploaded_to=' . $page->id),
-                        'successMessage' => trans('entities.attachments_file_uploaded'),
-                    ])
-                </div>
-                <div id="attachment-panel-links"
-                     tabindex="0"
-                     role="tabpanel"
-                     hidden
-                     aria-labelledby="attachment-tab-links"
-                     class="link-form-container">
-                    @include('attachments.manager-link-form', ['pageId' => $page->id])
-                </div>
+            <hr class="mb-s">
+
+            <div class="flex-container-row">
+                <button refs="dropzone@select-button" type="button" class="button outline small">{{ trans('entities.attachments_upload') }}</button>
+                <button refs="attachments@attach-link-button" type="button" class="button outline small">{{ trans('entities.attachments_link') }}</button>
+            </div>
+            <div>
+                <p class="text-muted text-small">{{ trans('entities.attachments_upload_drop') }}</p>
+            </div>
+            <div refs="dropzone@status-area" class="fixed top-right px-m py-m"></div>
+
+            <hr>
+
+            <div refs="attachments@list-panel">
+                @include('attachments.manager-list', ['attachments' => $page->attachments->all()])
             </div>
 
         </div>
 
-        <div refs="attachments@editContainer" class="hidden attachment-edit-container">
-
+        <div refs="attachments@links-container" hidden class="link-form-container">
+            @include('attachments.manager-link-form', ['pageId' => $page->id])
         </div>
 
+        <div refs="attachments@edit-container" hidden class="attachment-edit-container"></div>
+
     </div>
 </div>
\ No newline at end of file
diff --git a/resources/views/form/dropzone.blade.php b/resources/views/form/dropzone.blade.php
deleted file mode 100644
index 2eeec7505..000000000
--- a/resources/views/form/dropzone.blade.php
+++ /dev/null
@@ -1,8 +0,0 @@
-{{--
-@url - URL to upload to.
-@placeholder - Placeholder text
-@successMessage
---}}
-<div refs="" class="dropzone-container text-center">
-    <button type="button" class="dz-message">{{ $placeholder }}</button>
-</div>
\ No newline at end of file
diff --git a/resources/views/form/simple-dropzone.blade.php b/resources/views/form/simple-dropzone.blade.php
new file mode 100644
index 000000000..e274949ea
--- /dev/null
+++ b/resources/views/form/simple-dropzone.blade.php
@@ -0,0 +1,21 @@
+{{--
+@url - URL to upload to.
+@placeholder - Placeholder text
+@successMessage
+--}}
+<div component="dropzone"
+     option:dropzone:url="{{ $url }}"
+     option:dropzone:success-message="{{ $successMessage }}"
+     option:dropzone:error-message="{{ trans('errors.attachment_upload_error') }}"
+     option:dropzone:upload-limit="{{ config('app.upload_limit') }}"
+     option:dropzone:upload-limit-message="{{ trans('errors.server_upload_limit') }}"
+     option:dropzone:zone-text="{{ trans('entities.attachments_dropzone') }}"
+     option:dropzone:file-accept="*"
+     class="relative">
+    <div refs="dropzone@status-area" class="fixed top-right px-m py-m"></div>
+    <button type="button"
+            refs="dropzone@select-button dropzone@drop-target"
+            class="dropzone-landing-area text-center">
+        {{ $placeholder }}
+    </button>
+</div>
\ No newline at end of file
diff --git a/resources/views/pages/parts/image-manager.blade.php b/resources/views/pages/parts/image-manager.blade.php
index cab5daa64..0594c67e8 100644
--- a/resources/views/pages/parts/image-manager.blade.php
+++ b/resources/views/pages/parts/image-manager.blade.php
@@ -1,7 +1,7 @@
 <div components="image-manager dropzone"
      option:dropzone:url="{{ url('/images/gallery?' . http_build_query(['uploaded_to' => $uploaded_to ?? 0])) }}"
      option:dropzone:success-message="{{ trans('components.image_upload_success') }}"
-     option:dropzone:error-message="{{ trans('components.image_upload_failure') }}"
+     option:dropzone:error-message="{{ trans('errors.image_upload_error') }}"
      option:dropzone:upload-limit="{{ config('app.upload_limit') }}"
      option:dropzone:upload-limit-message="{{ trans('errors.server_upload_limit') }}"
      option:dropzone:zone-text="{{ trans('components.image_dropzone_drop') }}"