update filemanager with ajax features

This commit is contained in:
Simon Vieille 2021-06-21 21:34:01 +02:00
parent 41190f43b2
commit 3d597296c2
14 changed files with 316 additions and 138 deletions

View File

@ -272,7 +272,7 @@ th {
.toast-container {
display: flex;
position: relative;
z-index: 1060;
z-index: 4000;
.toast-wrapper {
position: fixed;

View File

@ -3,7 +3,9 @@ import '../../css/admin.scss'
require('../../../node_modules/bootstrap/dist/js/bootstrap.min.js')
require('./modules/table-fixed.js')()
require('./modules/form-confirm.js')()
require('./modules/form.js')()
require('./modules/form-file.js')()
require('./modules/form-error.js')()
require('./modules/form-ajax.js')()
require('./modules/dbclick.js')()
require('./modules/toast.js')()
require('./modules/modal.js')()

View File

@ -10,7 +10,7 @@
</li>
</ol>
<ol v-if="['crud'].indexOf(context) > -1" class="breadcrumb mb-0 float-right file-manager-actions">
<ol class="breadcrumb mb-0 float-right file-manager-actions">
<li class="breadcrumb-item">
<span class="fa fa-upload text-primary" v-bind:data-modal="generateUploadLink(directory)"></span>
<span class="fa fa-folder-plus text-primary" v-bind:data-modal="generateNewDirectoryLink(directory)"></span>
@ -60,7 +60,7 @@
</div>
</div>
</div>
<div v-for="item in files" class="card mt-3 ml-3 mb-3 border-0" v-bind:data-modal="generateInfoLink(item, null, context)">
<div v-for="item in files" class="card mt-3 ml-3 mb-3 border-0" v-on:click="modalUrl = generateInfoLink(item, null, context)" v-bind:data-modal="generateInfoLink(item, null, context)">
<div class="card-body p-2">
<div class="card-text">
<div class="text-center">
@ -112,7 +112,7 @@
<td width="10">
<FileIcon v-bind:mime="item.mime" />
</td>
<td v-bind:data-modal="generateInfoLink(item, null, context)">
<td v-on:click="modalUrl = generateInfoLink(item, null, context)" v-bind:data-modal="generateInfoLink(item, null, context)">
<div v-if="item.locked" class="float-right">
<span class="btn btn-sm btn-light">
<span class="fa fa-lock"></span>
@ -166,6 +166,7 @@ import Routing from '../../../../../vendor/friendsofsymfony/jsrouting-bundle/Res
import FileIcon from './FileIcon'
const axios = require('axios').default
const $ = require('jquery')
const routes = require('../../../../../public/js/fos_js_routes.json')
Routing.setRoutingData(routes)
@ -188,11 +189,16 @@ export default {
directories: [],
breadcrumb: [],
files: [],
parent: null
parent: null,
modalUrl: null,
ajax: 0,
}
},
methods: {
setDirectory (directory) {
if (!directory) {
directory = '/'
}
this.directory = directory
},
setView (view) {
@ -204,23 +210,27 @@ export default {
if (directory) {
return Routing.generate('admin_file_manager_info', {
file: item.path,
context: context
context: context,
ajax: this.ajax
})
} else {
return Routing.generate('admin_file_manager_info', {
file: item.path + '/' + item.basename,
context: context
context: context,
ajax: this.ajax
})
}
},
generateUploadLink (directory) {
return Routing.generate('admin_file_manager_upload', {
file: directory
file: directory,
ajax: this.ajax
})
},
generateNewDirectoryLink (directory) {
return Routing.generate('admin_file_manager_directory_new', {
file: directory
file: directory,
ajax: this.ajax
})
},
buildBreadcrum (elements) {
@ -244,6 +254,33 @@ export default {
})
}
}
},
refresh () {
const that = this
axios.get(Routing.generate('admin_file_manager_api_directory', {
directory: that.directory,
context: that.context,
ajax: this.ajax,
}))
.then((response) => {
that.buildBreadcrum(response.data.breadcrumb)
that.parent = response.data.parent
that.directories = response.data.directories
that.files = response.data.files
const query = new URLSearchParams(window.location.search)
query.set('path', that.directory)
history.pushState(
null,
'',
window.location.pathname + '?' + query.toString()
)
})
.catch((e) => {
alert('An error occured')
})
}
},
mounted () {
@ -260,31 +297,27 @@ export default {
} else {
this.setDirectory('/')
}
this.ajax = (['crud'].indexOf(this.context) === -1 ? 1 : 0)
const body = $('body')
const events = ['file_manager.file.new', 'file_manager.directory.new', 'file_manager.directory.rename']
const that = this
$(events).each((k, event) => {
body.on(event + '.success', () => {
$('#modal-container').modal('hide')
that.refresh()
})
})
body.on('file_manager.info.update.success', () => {
$('*[data-modal="' + that.modalUrl + '"]').click()
})
},
watch: {
directory (directory) {
axios.get(Routing.generate('admin_file_manager_api_directory', {
directory: this.directory,
context: this.context
}))
.then((response) => {
this.buildBreadcrum(response.data.breadcrumb)
this.parent = response.data.parent
this.directories = response.data.directories
this.files = response.data.files
const query = new URLSearchParams(window.location.search)
query.set('path', directory)
history.pushState(
null,
'',
window.location.pathname + '?' + query.toString()
)
})
.catch(() => {
alert('An error occured')
})
this.refresh()
}
}
}

View File

@ -9,7 +9,7 @@ module.exports = () => {
new Vue({
el: '#file-manager',
template: '<FileManager />',
template: '<FileManager context="crud" />',
components: {
FileManager
}

View File

@ -0,0 +1,80 @@
const $ = require('jquery')
module.exports = function () {
$('body').on('submit', '*[data-form-ajax]', function (e) {
e.preventDefault()
const target = e.target
const form = $(target)
const data = new FormData(target)
const type = form.attr('enctype')
const method = form.attr('method')
const files = form.find('input[type=file]')
files.each((i, v) => {
data.append(v.name, v)
})
const options = {
url: form.attr('action'),
data: data,
processData: false,
contentType: false,
type: method || 'GET',
success: function (data) {
if (data.hasOwnProperty('_dispatch')) {
$('body').trigger(data._dispatch)
}
if (data.hasOwnProperty('_message') && data.hasOwnProperty('_level')) {
const message = data._message
const level = data._level
const titles = {
notice: 'Information',
info: 'Information',
success: 'Success',
warning: 'Warning',
danger: 'Danger',
error: 'Error'
}
const borders = {
notice: '',
info: 'border border-primary',
success: 'border border-success',
warning: 'border border-warning',
danger: 'border border-danger',
error: 'border border-danger'
}
const colors = {
info: 'text-body',
notice: 'text-body',
success: 'text-success font-weight-bold',
warning: 'text-warning font-weight-bold',
danger: 'text-danger font-weight-bold',
error: 'text-danger font-weight-bold'
}
$('#toast-wrapper-main').append(
`
<div class="toast ${borders[level]}" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="mr-auto">${titles[level]}</strong>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body text-${colors[level]}">
${message}
</div>
</div>
`
)
$('.toast').last().toast('show')
}
}
}
$.ajax(options)
})
}

View File

@ -1,14 +1,6 @@
const $ = require('jquery')
module.exports = function () {
$('body').on('change', '.custom-file-input', function (event) {
const inputFile = event.currentTarget
$(inputFile).parent()
.find('.custom-file-label')
.html(inputFile.files[0].name)
})
$('.nav a').each(function () {
const link = $(this)
const href = link.attr('href')

View File

@ -0,0 +1,11 @@
const $ = require('jquery')
module.exports = function () {
$('body').on('change', '.custom-file-input', function (event) {
const inputFile = event.currentTarget
$(inputFile).parent()
.find('.custom-file-label')
.html(inputFile.files[0].name)
})
}

View File

@ -12,6 +12,7 @@ use App\Core\Manager\EntityManager;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Contracts\Translation\TranslatorInterface;
/**
* @Route("/admin/file_manager")
@ -37,14 +38,16 @@ class FileManagerAdminController extends AdminController
}
/**
* @Route("/info/{tab}/{context}", name="admin_file_manager_info", options={"expose"=true})
* @Route("/info/{tab}/{context}/{ajax}", name="admin_file_manager_info", options={"expose"=true})
*/
public function info(
FsFileManager $manager,
Request $request,
EntityManager $entityManager,
TranslatorInterface $translator,
string $context = 'crud',
string $tab = 'information'
string $tab = 'information',
bool $ajax = false
): Response {
$splInfo = $manager->getSplInfo($request->query->get('file'));
@ -63,9 +66,27 @@ class FileManagerAdminController extends AdminController
if ($form->isValid()) {
$entityManager->update($fileInfo);
$this->addFlash('success', 'The data has been saved.');
if (!$request->isXmlHttpRequest()) {
$this->addFlash('success', 'The data has been saved.');
} else {
return $this->json([
'_error' => 0,
'_message' => $translator->trans('The data has been saved.'),
'_level' => 'success',
'_dispatch' => 'file_manager.info.update.success',
]);
}
} else {
$this->addFlash('warning', 'The form is not valid.');
if (!$request->isXmlHttpRequest()) {
$this->addFlash('warning', 'The form is not valid.');
} else {
return $this->json([
'_error' => 1,
'_message' => $translator->trans('The form is not valid.'),
'_level' => 'warning',
'_dispatch' => 'file_manager.info.update.error',
]);
}
}
return $this->redirectToRoute('admin_file_manager_index', [
@ -84,13 +105,14 @@ class FileManagerAdminController extends AdminController
'tab' => $tab,
'form' => $form->createView(),
'context' => $context,
'ajax' => $ajax,
]);
}
/**
* @Route("/directory/new", name="admin_file_manager_directory_new", options={"expose"=true}, methods={"GET", "POST"})
* @Route("/directory/new/{ajax}", name="admin_file_manager_directory_new", options={"expose"=true}, methods={"GET", "POST"})
*/
public function directoryNew(FsFileManager $manager, Request $request): Response
public function directoryNew(FsFileManager $manager, Request $request, TranslatorInterface $translator, bool $ajax = false): Response
{
$splInfo = $manager->getSplInfo($request->query->get('file'));
@ -116,9 +138,27 @@ class FileManagerAdminController extends AdminController
$status = $manager->createDirectory($form->get('name')->getData(), $request->query->get('file'));
if (true === $status) {
$this->addFlash('success', 'Directory created.');
if (!$request->isXmlHttpRequest()) {
$this->addFlash('success', 'Directory created.');
} else {
return $this->json([
'_error' => 0,
'_message' => $translator->trans('Directory created.'),
'_level' => 'success',
'_dispatch' => 'file_manager.directory.new.success',
]);
}
} else {
$this->addFlash('warning', 'Directory not created.');
if (!$request->isXmlHttpRequest()) {
$this->addFlash('warning', 'Directory not created.');
} else {
return $this->json([
'_error' => 1,
'_message' => $translator->trans('Directory not created.'),
'_level' => 'warning',
'_dispatch' => 'file_manager.directory.new.error',
]);
}
}
} else {
$this->addFlash('warning', 'Unauthorized char(s).');
@ -132,14 +172,15 @@ class FileManagerAdminController extends AdminController
return $this->render('@Core/file_manager/directory_new.html.twig', [
'form' => $form->createView(),
'file' => $request->query->get('file'),
'ajax' => $ajax,
'locked' => false,
]);
}
/**
* @Route("/directory/rename", name="admin_file_manager_directory_rename", methods={"GET", "POST"})
* @Route("/directory/rename/{ajax}", name="admin_file_manager_directory_rename", methods={"GET", "POST"})
*/
public function directoryRename(FsFileManager $manager, Request $request): Response
public function directoryRename(FsFileManager $manager, Request $request, TranslatorInterface $translator, bool $ajax = false): Response
{
$splInfo = $manager->getSplInfo($request->query->get('file'));
@ -166,9 +207,27 @@ class FileManagerAdminController extends AdminController
$status = $manager->renameDirectory($form->get('name')->getData(), $request->query->get('file'));
if (true === $status) {
$this->addFlash('success', 'Directory renamed.');
if (!$request->isXmlHttpRequest()) {
$this->addFlash('success', 'Directory renamed.');
} else {
return $this->json([
'_error' => 0,
'_message' => $translator->trans('Directory renamed.'),
'_level' => 'success',
'_dispatch' => 'file_manager.directory.rename.success',
]);
}
} else {
$this->addFlash('warning', 'Directory not renamed.');
if (!$request->isXmlHttpRequest()) {
$this->addFlash('warning', 'Directory not renamed.');
} else {
return $this->json([
'_error' => 1,
'_message' => $translator->trans('Directory not renamed.'),
'_level' => 'warning',
'_dispatch' => 'file_manager.directory.rename.error',
]);
}
}
} else {
$this->addFlash('warning', 'Unauthorized char(s).');
@ -183,13 +242,14 @@ class FileManagerAdminController extends AdminController
'form' => $form->createView(),
'file' => $request->query->get('file'),
'locked' => false,
'ajax' => $ajax,
]);
}
/**
* @Route("/upload", name="admin_file_manager_upload", options={"expose"=true}, methods={"GET", "POST"})
* @Route("/upload/{ajax}", name="admin_file_manager_upload", options={"expose"=true}, methods={"GET", "POST"})
*/
public function upload(FsFileManager $manager, Request $request): Response
public function upload(FsFileManager $manager, Request $request, TranslatorInterface $translator, bool $ajax = false): Response
{
$splInfo = $manager->getSplInfo($request->query->get('file'));
@ -216,9 +276,27 @@ class FileManagerAdminController extends AdminController
if ($form->isValid()) {
$manager->upload($form->get('files')->getData(), $request->query->get('file'));
$this->addFlash('success', 'Files uploaded.');
if (!$request->isXmlHttpRequest()) {
$this->addFlash('success', 'Files uploaded.');
} else {
return $this->json([
'_error' => 0,
'_message' => $translator->trans('Files uploaded.'),
'_level' => 'success',
'_dispatch' => 'file_manager.file.new.success',
]);
}
} else {
$this->addFlash('warning', 'Unauthorized file type(s).');
if (!$request->isXmlHttpRequest()) {
$this->addFlash('warning', 'Unauthorized file type(s).');
} else {
return $this->json([
'_error' => 1,
'_message' => $translator->trans('Unauthorized file type(s).'),
'_level' => 'warning',
'_dispatch' => 'file_manager.file.new.error',
]);
}
}
return $this->redirectToRoute('admin_file_manager_index', [
@ -230,6 +308,7 @@ class FileManagerAdminController extends AdminController
'form' => $form->createView(),
'file' => $request->query->get('file'),
'locked' => false,
'ajax' => $ajax,
]);
}

View File

@ -215,6 +215,8 @@ class FsFileManager
if ($file) {
$filesystem = new Filesystem();
$filesystem->remove($file);
return true;
}
return false;

View File

@ -1,51 +1,49 @@
{% set flashes = app.flashes %}
{% if flashes|length %}
{% set colors = {
'info': 'text-body',
'notice': 'text-body',
'success': 'text-success font-weight-bold',
'warning': 'text-warning font-weight-bold',
'danger': 'text-danger font-weight-bold',
'error': 'text-danger font-weight-bold',
} %}
{% set colors = {
'info': 'text-body',
'notice': 'text-body',
'success': 'text-success font-weight-bold',
'warning': 'text-warning font-weight-bold',
'danger': 'text-danger font-weight-bold',
'error': 'text-danger font-weight-bold',
} %}
{% set titles = {
'notice': 'Information',
'info': 'Information',
'success': 'Success',
'warning': 'Warning',
'danger': 'Danger',
'error': 'Error',
} %}
{% set titles = {
'notice': 'Information',
'info': 'Information',
'success': 'Success',
'warning': 'Warning',
'danger': 'Danger',
'error': 'Error',
} %}
{% set borders = {
'notice': '',
'info': 'border border-primary',
'success': 'border border-success',
'warning': 'border border-warning',
'danger': 'border border-danger',
'error': 'border border-danger',
} %}
{% set borders = {
'notice': '',
'info': 'border border-primary',
'success': 'border border-success',
'warning': 'border border-warning',
'danger': 'border border-danger',
'error': 'border border-danger',
} %}
<div aria-live="polite" aria-atomic="true" class="toast-container">
<div class="toast-wrapper">
{% for label, messages in flashes %}
{% for message in messages %}
<div class="toast {{ borders[label] }}" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="mr-auto">{{ titles[label]|trans }}</strong>
<small>{{ 'now'|date('H:i') }}</small>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="toast-body text-{{ colors[label] }}">
{{ message|trans|nl2br }}
</div>
<div aria-live="polite" aria-atomic="true" class="toast-container">
<div class="toast-wrapper" id="toast-wrapper-main">
{% for label, messages in flashes %}
{% for message in messages %}
<div class="toast {{ borders[label] }}" role="alert" aria-live="assertive" aria-atomic="true">
<div class="toast-header">
<strong class="mr-auto">{{ titles[label]|trans }}</strong>
<small>{{ 'now'|date('H:i') }}</small>
<button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
{% endfor %}
<div class="toast-body text-{{ colors[label] }}">
{{ message|trans|nl2br }}
</div>
</div>
{% endfor %}
</div>
{% endfor %}
</div>
{% endif %}
</div>

View File

@ -18,7 +18,7 @@
{{ 'Directory locked.'|trans }}
</p>
{% else %}
<form action="{{ path('admin_file_manager_directory_new', {file: file}) }}" id="form-file-manager-directory" method="POST" enctype="multipart/form-data">
<form action="{{ path('admin_file_manager_directory_new', {file: file}) }}" {% if ajax %}data-form-ajax{% endif %} id="form-file-manager-directory" method="POST" enctype="multipart/form-data">
{{ include('@Core/file_manager/_form.html.twig') }}
</form>
{% endif %}

View File

@ -18,7 +18,7 @@
{{ 'Directory locked.'|trans }}
</p>
{% else %}
<form action="{{ path('admin_file_manager_directory_rename', {file: file}) }}" id="form-file-manager-directory" method="POST" enctype="multipart/form-data">
<form action="{{ path('admin_file_manager_directory_rename', {file: file}) }}" {% if ajax %}data-form-ajax{% endif %} id="form-file-manager-directory" method="POST" enctype="multipart/form-data">
{{ include('@Core/file_manager/_form.html.twig') }}
</form>
{% endif %}

View File

@ -18,11 +18,13 @@
</a>
</li>
<li class="nav-item" role="presentation">
<a class="nav-link {% if tab == 'attributes' %}active{% endif %}" data-toggle="tab" href="#tab-fm-attributes">
{{ 'Attributes'|trans }}
</a>
</li>
{% if splInfo.isFile %}
<li class="nav-item" role="presentation">
<a class="nav-link {% if tab == 'attributes' %}active{% endif %}" data-toggle="tab" href="#tab-fm-attributes">
{{ 'Attributes'|trans }}
</a>
</li>
{% endif %}
{% if splInfo.extension in ['jpeg', 'jpg', 'gif', 'png', 'svg'] %}
<li class="nav-item" role="presentation">
@ -69,33 +71,9 @@
</ul>
</div>
<div class="tab-pane {% if tab == 'attributes' %}show active{% endif %}" id="tab-fm-attributes">
{% if context == 'tinymce' %}
<div class="accordion mb-3" id="fm-attributes">
{% for item in form.attributes %}
<div class="card">
<div class="card-header p-0">
<span class="btn btn-link btn-block text-left" data-toggle="collapse" data-target="#fm-attribute-{{ loop.index }}">
{{ item.vars.data.label }}
</span>
</div>
<div class="collapse" data-parent="#fm-attributes" id="fm-attribute-{{ loop.index }}">
<div class="card-body">
<div>{{ 'Value'|trans }}</div>
<code>{{ item.vars.data.value }}</code>
{% set code = 'fattr://' ~ form.vars.data.id ~ '/' ~ item.vars.data.label %}
{% set code = '{{' ~ code ~ '}}' %}
<div class="mt-1">{{ 'Tag to insert in content'|trans }}</div>
<code>{{ code }}</code>
</div>
</div>
</div>
{% endfor %}
</div>
{% else %}
<form method="post" action="{{ path('admin_file_manager_info', {tab: 'attributes', file: splInfo.relativePathname}) }}" id="form-fm-attributes">
{% if splInfo.isFile %}
<div class="tab-pane {% if tab == 'attributes' %}show active{% endif %}" id="tab-fm-attributes">
<form method="post" action="{{ path('admin_file_manager_info', {tab: 'attributes', file: splInfo.relativePathname}) }}" id="form-fm-attributes" {% if ajax %}data-form-ajax{% endif %}>
<div class="accordion mb-3" data-collection="collection-fm-attributes" id="form-fm-attributes-collection">
{% for item in form.attributes %}
<div class="card" data-collection-item="{{ loop.index }}">
@ -137,8 +115,8 @@
{{ form_row(form._token) }}
</form>
{% endif %}
</div>
</div>
{% endif %}
{% if splInfo.extension in ['jpeg', 'jpg', 'gif', 'png', 'svg'] %}
<div class="tab-pane {% if tab == 'preview' %}show active{% endif %}" id="tab-fm-preview">
@ -157,9 +135,12 @@
{% if context == 'tinymce' %}
<div>
<button type="button" class="btn btn-secondary" data-dismiss="modal">{{ 'Close'|trans }}</button>
{% if splInfo.isFile %}
<button type="button" class="btn btn-primary" id="file-manager-insert" data-value="{{ asset(path) }}">{{ 'Insert'|trans }}</button>
<button type="submit" class="btn btn-primary" form="form-fm-attributes">{{ 'Save'|trans }}</button>
<button type="button" class="btn btn-success ml-3" id="file-manager-insert" data-value="{{ asset(path) }}">{{ 'Insert'|trans }}</button>
{% endif %}
</div>
{% else %}
<div>

View File

@ -18,7 +18,7 @@
{{ 'Directory locked.'|trans }}
</p>
{% else %}
<form action="{{ path('admin_file_manager_upload', {file: file}) }}" id="form-file-manager-upload" method="POST" enctype="multipart/form-data">
<form action="{{ path('admin_file_manager_upload', {file: file}) }}" {% if ajax %}data-form-ajax{% endif %} id="form-file-manager-upload" method="POST" enctype="multipart/form-data">
{{ include('@Core/file_manager/_form.html.twig') }}
</form>
{% endif %}