thekid / dialog

Dialog photoblog
2 stars 1 forks source link

Admin interface #26

Open thekid opened 1 year ago

thekid commented 1 year ago

Create an admin interface to upload images and videos

Step 1: Research which libraries we can use for this

thekid commented 1 year ago

💡 https://www.dropzone.dev/ for file uploads 💡 https://ckeditor.com/ for WYSIWYG editor for content

thekid commented 1 year ago

Uploads via Dropzone.js

The cool thing is that Dropzone.js uploads files instantly, without a need for submitting the surrounding form (and we could start processing them directly via the functionality implemented in #46 - when the post is ready, so are the images!). However, that also we don't know what to attach these images to.

Unassigned uploads

Files from the dropzone within the create form upload their files to a global place (/image/@uploads).

Dropzone

When files are successfully uploaded, hidden input elements with the file names are created, which are then submitted along with the form. The remove button will not only delete the uploads from the server, but also remove the hidden input element.

Assign uploads

After uploading, on the server side, these uploads are then moved to the created element's storage directory.

First implementation

// Set up dropzone
const $upload = document.querySelector('#upload');
const uploads = new Dropzone($upload, {
  url            : '/api/entries/uploads',
  acceptedFiles  : 'image/jpeg,image/png,image/webp,video/mp4,video/mpeg,video/quicktime',
  addRemoveLinks : true,
  dictRemoveFile : 'Entfernen',
  accept         : function(file, done) {
    for (let i = 0; i < this.files.length - 1; i++) {
      if (this.files[i].name === file.name) return done('Existiert bereits: ' + file.name);
    }
    done();
  }
});
uploads.on('success', file => {
  const $field = document.createElement('input');
  $field.type = 'hidden';
  $field.name = 'uploads[]';
  $field.value = file.name;
  $upload.appendChild($field);
});
uploads.on('removedfile', file => {
  if ('success' === file.status || 'existing' === file.status) {
    $upload.querySelectorAll('input[type="hidden"][name="uploads[]"]').forEach($e => {
      if ($e.value === file.name) $e.remove();
    });
    fetch('/api/entries/uploads/' + encodeURIComponent(file.name), {method: 'DELETE'});
  }
});

// Populate uploads
fetch('/api/entries/uploads')
  .then(res => res.json())
  .then(files => {
    for (const file of files) {
      uploads.displayExistingFile(
        {name: file.name, size: file.size, status: 'existing'},
        '/image/' + file.path + '/' + encodeURIComponent(file.name)
      );
      uploads.files.push(file);
      uploads.emit('success', file);
    }
  })
;
thekid commented 1 year ago

Post editor

Early draft:

Editor

thekid commented 1 year ago

Authentication

Early draft for login page:

Login page

Lock icon is from https://www.svgrepo.com/svg/184189/lock