fix file upload

This commit is contained in:
francois 2025-05-08 19:19:39 +02:00
parent 991562a456
commit 3f1a8b14cb
13 changed files with 724 additions and 256 deletions

View File

@ -14,13 +14,4 @@
<dependencies>
<nextcloud min-version="29" max-version="29"/>
</dependencies>
<navigations>
<navigation>
<id>webtransfer</id>
<name>Web Transfer</name>
<route>webtransfer.page.index</route>
<icon>app.svg</icon>
<type>link</type>
</navigation>
</navigations>
</info>

View File

@ -2,6 +2,7 @@
return [
'routes' => [
['name' => 'page#main', 'url' => '/', 'verb' => 'GET'],
['name' => 'page#post', 'url' => '/post', 'verb' => 'POST'],
['name' => 'page#zipDrop', 'url' => '/zipDrop', 'verb' => 'GET'],
['name' => 'page#getFile', 'url' => '/getFile', 'verb' => 'GET']
]
];

View File

@ -31,17 +31,107 @@ class PageController extends Controller {
);
}
// Route de la page d'extraction du zip
#[NoCSRFRequired]
#[NoAdminRequired]
#[FrontpageRoute(verb: 'POST', url: '/zipDeposit')]
public function post($archiveUrl, $token) {
$request = $this->request;
$parameters = array('archiveUrl' => $archiveUrl, 'token' => $token);
#[OpenAPI(OpenAPI::SCOPE_IGNORE)]
#[FrontpageRoute(verb: 'GET', url: '/zipDrop')]
public function zipDrop() {
// Récupérer le paramètre url (compatible GET et POST)
$url = $this->request->getParam('url');
$mode = $this->request->getParam('mode');
if (!$url) {
return new \OCP\AppFramework\Http\DataResponse([
'error' => 'Le paramètre url est manquant'
], 400);
}
if (!$mode) {
return new \OCP\AppFramework\Http\DataResponse([
'error' => 'Le paramètre mode est manquant'
], 400);
}
// Optionnel : Validation de l'URL
if (filter_var($url, FILTER_VALIDATE_URL) === false) {
return new \OCP\AppFramework\Http\DataResponse([
'error' => 'url n\'est pas une URL valide'
], 400);
}
$parameters = array('archiveUrl' => $url, 'mode' => $mode);
// Réponse de succès
return new TemplateResponse(
Application::APP_ID,
'index',
$parameters
);
}
// Route pour download le fichier
#[NoCSRFRequired]
#[NoAdminRequired]
#[OpenAPI(OpenAPI::SCOPE_IGNORE)]
#[FrontpageRoute(verb: 'GET', url: '/getFile')]
public function getFile() {
// Récupérer les données envoyées dans la requête
$zipUrl = $this->request->getParam('url');
// Initialiser les paramètres de réponse
$parameters = [
'status' => 'error', // Par défaut, la réponse indique une erreur
'message' => '',
'data' => null
];
// Valider l'URL
if (!$zipUrl || filter_var($zipUrl, FILTER_VALIDATE_URL) === false) {
$parameters['message'] = 'Invalid URL';
return new JsonResponse($parameters, 400); // 400 Bad Request
}
try {
// Initialiser cURL
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $zipUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10); // Timeout de 10 secondes
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); // Suivre les redirections
// Récupérer le contenu
$response = curl_exec($ch);
// Gérer les erreurs cURL
if (curl_errno($ch)) {
$parameters['message'] = 'cURL error: ' . curl_error($ch);
curl_close($ch);
return new JsonResponse(['parameters' => $parameters, 'status' => 500]); // 500 Internal Server Error
}
// Vérifier le code HTTP
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($httpCode !== 200) {
$parameters['message'] = "HTTP error: $httpCode";
return new JsonResponse(['parameters' => $parameters, 'status' => $httpCode]);
}
// Encodage explicite en UTF-8 si nécessaire
if (!mb_detect_encoding($response, 'UTF-8', true)) {
$response = utf8_encode($response);
}
// Si tout est OK, construire la réponse
$parameters['status'] = 'success';
$parameters['message'] = 'File retrieved successfully';
$parameters['data'] = $response; // Encodage Base64 pour éviter les problèmes d'encodage JSON
return new JsonResponse(['parameters' => $parameters, 'status' => 200]); // 200 OK
} catch (\Exception $e) {
$parameters['message'] = 'Exception: ' . $e->getMessage();
return new JsonResponse(['parameters' => $parameters, 'status' => 500]); // 500 Internal Server Error
}
}
}

154
package-lock.json generated
View File

@ -13,6 +13,8 @@
"@nextcloud/files": "^3.10.0",
"@nextcloud/initial-state": "^2.2.0",
"@nextcloud/vue": "^8.20.0",
"file-type": "^19.6.0",
"i18next": "^24.0.2",
"jszip": "^3.10.1",
"vue": "^2.7.16",
"vue-material-design-icons": "^5.3.1"
@ -1818,6 +1820,12 @@
"license": "MIT",
"peer": true
},
"node_modules/@sec-ant/readable-stream": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
"integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
"license": "MIT"
},
"node_modules/@shikijs/core": {
"version": "1.23.1",
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-1.23.1.tgz",
@ -1881,6 +1889,12 @@
"dev": true,
"license": "MIT"
},
"node_modules/@tokenizer/token": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz",
"integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==",
"license": "MIT"
},
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@ -6711,6 +6725,24 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-type": {
"version": "19.6.0",
"resolved": "https://registry.npmjs.org/file-type/-/file-type-19.6.0.tgz",
"integrity": "sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==",
"license": "MIT",
"dependencies": {
"get-stream": "^9.0.1",
"strtok3": "^9.0.1",
"token-types": "^6.0.0",
"uint8array-extras": "^1.3.0"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sindresorhus/file-type?sponsor=1"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@ -7063,6 +7095,22 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-stream": {
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
"integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
"license": "MIT",
"dependencies": {
"@sec-ant/readable-stream": "^0.4.1",
"is-stream": "^4.0.1"
},
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/get-symbol-description": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz",
@ -7782,6 +7830,37 @@
"node": ">=10.18"
}
},
"node_modules/i18next": {
"version": "24.0.2",
"resolved": "https://registry.npmjs.org/i18next/-/i18next-24.0.2.tgz",
"integrity": "sha512-D88xyIGcWAKwBTAs4RSqASi8NXR/NhCVSTM4LDbdoU8qb/5dcEZjNCLDhtQBB7Epw/Cp1w2vH/3ujoTbqLSs5g==",
"funding": [
{
"type": "individual",
"url": "https://locize.com"
},
{
"type": "individual",
"url": "https://locize.com/i18next.html"
},
{
"type": "individual",
"url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project"
}
],
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2"
},
"peerDependencies": {
"typescript": "^5"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/ical.js": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ical.js/-/ical.js-2.1.0.tgz",
@ -7820,7 +7899,6 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"dev": true,
"funding": [
{
"type": "github",
@ -7835,8 +7913,7 @@
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause",
"peer": true
"license": "BSD-3-Clause"
},
"node_modules/ignore": {
"version": "5.3.2",
@ -8523,6 +8600,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-stream": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
"integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-string": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
@ -11011,6 +11100,19 @@
"node": ">=0.12"
}
},
"node_modules/peek-readable": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-5.3.1.tgz",
"integrity": "sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==",
"license": "MIT",
"engines": {
"node": ">=14.16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@ -13486,6 +13588,23 @@
"integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==",
"license": "MIT"
},
"node_modules/strtok3": {
"version": "9.1.1",
"resolved": "https://registry.npmjs.org/strtok3/-/strtok3-9.1.1.tgz",
"integrity": "sha512-FhwotcEqjr241ZbjFzjlIYg6c5/L/s4yBGWSMvJ9UoExiSqL+FnFA/CaeZx17WGaZMS/4SOZp8wH18jSS4R4lw==",
"license": "MIT",
"dependencies": {
"@tokenizer/token": "^0.3.0",
"peek-readable": "^5.3.1"
},
"engines": {
"node": ">=16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/style-loader": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/style-loader/-/style-loader-4.0.0.tgz",
@ -14213,6 +14332,23 @@
"node": ">=0.6"
}
},
"node_modules/token-types": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/token-types/-/token-types-6.0.0.tgz",
"integrity": "sha512-lbDrTLVsHhOMljPscd0yitpozq7Ga2M5Cvez5AjGg8GASBjtt6iERCAJ93yommPmz62fb45oFIXHEZ3u9bfJEA==",
"license": "MIT",
"dependencies": {
"@tokenizer/token": "^0.3.0",
"ieee754": "^1.2.1"
},
"engines": {
"node": ">=14.16"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/Borewit"
}
},
"node_modules/tree-dump": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/tree-dump/-/tree-dump-1.0.2.tgz",
@ -14555,6 +14691,18 @@
"license": "MIT",
"peer": true
},
"node_modules/uint8array-extras": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.4.0.tgz",
"integrity": "sha512-ZPtzy0hu4cZjv3z5NW9gfKnNLjoz4y6uv4HlelAjDK7sY/xOkKZv9xK/WQpcsBB3jEybChz9DPC2U/+cusjJVQ==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",

View File

@ -11,7 +11,9 @@
"dev": "NODE_ENV=development webpack --config webpack.js --progress",
"watch": "NODE_ENV=development webpack --config webpack.js --progress --watch",
"lint": "eslint src",
"stylelint": "stylelint src/**/*.vue src/**/*.scss src/**/*.css"
"stylelint": "stylelint src/**/*.vue src/**/*.scss src/**/*.css",
"tailwind": "npx tailwindcss -i ./src/input.css -o ./src/output.css",
"buildAll": "npm i && npm run tailwind && npm run build"
},
"browserslist": [
"extends @nextcloud/browserslist-config"
@ -21,6 +23,8 @@
"@nextcloud/files": "^3.10.0",
"@nextcloud/initial-state": "^2.2.0",
"@nextcloud/vue": "^8.20.0",
"file-type": "^19.6.0",
"i18next": "^24.0.2",
"jszip": "^3.10.1",
"vue": "^2.7.16",
"vue-material-design-icons": "^5.3.1"

View File

@ -1,16 +1,16 @@
<template>
<div id="app" class="h-full w-full bg-black/80">
<div id="app" class="h-full w-full dark:bg-black/80 bg-white/80">
<!-- Conteneur principal, ajustement en flex-row à partir de sm -->
<div class="h-full w-full flex flex-col sm:flex-row">
<!-- Première section -->
<div
class="w-full sm:w-1/3 max-sm:h-2/5 p-4 sm:m-6 sm:mr-0 rounded-xl bg-NcBlack/40">
<WebContentViewer @zip-upload="handleZipUpload" @file-upload="handleFileUpload" @dragEnded="toggleDragEnded" zipUrl="http://localhost:8000/dummyZip.zip"/>
class="w-full sm:w-1/3 max-sm:h-2/5 p-4 sm:m-6 sm:mr-0 rounded-xl dark:bg-NcBlack/40 bg-white/80">
<WebContentViewer :translate="translate" @file-upload="handleFileUpload" @dragEnded="toggleDragEnded" :zipUrl="zipUrl"/>
</div>
<!-- Deuxième section -->
<div
class="w-full sm:w-2/3 max-sm:h-3/5 p-4 sm:m-6 sm:ml-4 bg-NcBlack rounded-xl">
<FileTable :file="sharedFile" :zip="zip" :dragEnded="dragEnded" @dragEnded="toggleDragEnded"/>
class="w-full sm:w-2/3 max-sm:h-3/5 p-4 sm:m-6 sm:ml-4 dark:bg-NcBlack bg-white rounded-xl">
<FileTable :file="sharedFile" :dragEnded="dragEnded" :translate="translate" @dragEnded="toggleDragEnded"/>
</div>
</div>
</div>
@ -21,6 +21,16 @@ import FileTable from './components/FileTable.vue';
import WebContentViewer from './components/WebContentViewer.vue';
import './output.css';
// Traduction
import i18next from "i18next";
import file from "./assets/traduction.json";
await i18next.init({
lng: navigator.language.split('-')[0],
fallbackLng: "en",
resources: file,
});
export default {
name: 'App',
components: {
@ -28,9 +38,11 @@ export default {
WebContentViewer
},
data() {
let zipUrl = document.getElementById('archiveInfos').getAttribute('dataarchiveurl');
//console.log(zipUrl)
return {
zipUrl,
sharedFile: null,
zip: null,
dragEnded: false,
};
},
@ -38,14 +50,13 @@ export default {
handleFileUpload(file) {
this.sharedFile = file;
},
handleZipUpload(zip) {
this.zip = zip;
},
toggleDragEnded(){
this.dragEnded = !this.dragEnded;
this.zip = null;
this.sharedFile = null;
},
translate(id) {
return i18next.t(id)
},
},
}
</script>

View File

@ -1,7 +1,7 @@
<template>
<div class="fixed inset-0 flex items-center justify-center bg-gray-700 bg-opacity-50 z-50" @click="closeModal">
<div class="bg-NcBlack rounded-lg shadow-lg p-6 w-96" @click.stop>
<h2 class="text-lg font-semibold mb-4">Modifier le nom du fichier</h2>
<div class="dark:bg-NcBlack bg-white rounded-lg shadow-lg p-6 w-96" @click.stop>
<h2 class="text-lg font-semibold mb-4">{{ translate('modify.file.name') }}</h2>
<input
type="text"
v-model="newFileName"
@ -11,8 +11,8 @@
class="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<div class="flex justify-end mt-4 space-x-2">
<button @click="save" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">Sauvegarder</button>
<button @click="closeModal" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">Annuler</button>
<button @click="save" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">{{ translate('rename') }}</button>
<button @click="closeModal" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">{{ translate('cancel') }}</button>
</div>
</div>
</div>
@ -29,13 +29,19 @@ export default {
isDirectory:{
type: Boolean,
required: true,
},
translate: {
type: Function,
Required: true,
}
},
data() {
var newFileName = this.initialFileName;
var extension = '';
let newFileName = this.initialFileName;
let extension = '';
let nbParts = 1;
if(!this.isDirectory) {
let nameSplit = newFileName.split('.');
nbParts = nameSplit.length;
if (nameSplit.length > 1) {
extension = nameSplit.pop();
}
@ -44,6 +50,7 @@ export default {
return {
newFileName,
extension,
nbParts
};
},
methods: {
@ -71,17 +78,36 @@ export default {
},
onInputChange() {
if (!this.isDirectory) {
const fileNameWithoutExtension = this.newFileName.slice(0, this.newFileName.lastIndexOf('.'));
this.newFileName = this.removeExtensionSurplus(this.newFileName);
let lastIndex = this.newFileName.lastIndexOf('.');
let fileNameWithoutExtension;
if(lastIndex != -1) {
fileNameWithoutExtension = this.newFileName.slice(0, lastIndex);
}
else {
fileNameWithoutExtension = this.newFileName.slice(0);
}
const newFileNameWithOriginalExtension = fileNameWithoutExtension + '.' + this.extension;
// Si l'extension est différente de celle d'origine, on la rétablit
if (this.extension !== '' && this.newFileName !== newFileNameWithOriginalExtension) {
// Vous pouvez ici vérifier si l'extension a été modifiée et la rétablir
this.newFileName = newFileNameWithOriginalExtension;
}
}
},
removeExtensionSurplus(name){
let splitName = name.split('.');
if(this.nbParts != splitName.length) {
let lenExtension = this.extension.length;
return name.slice(0, name.length - lenExtension);
}
else{
return name;
}
}
},
};
</script>

View File

@ -1,43 +1,43 @@
<template>
<div class="fixed inset-0 flex items-center justify-center bg-gray-700 bg-opacity-50 z-50">
<div v-if="!displayRename && !displayOverwrite" class="bg-NcBlack rounded-lg shadow-lg p-6 w-96">
<h2 class="text-lg font-semibold mb-4">Le fichier existe déjà</h2>
<p>Le fichier "{{ fileName }}" existe déjà. Que voulez-vous faire ?</p>
<div v-if="!displayRename && !displayOverwrite" class="dark:bg-NcBlack bg-white rounded-lg shadow-lg p-6 w-96">
<h2 class="text-lg font-semibold mb-4">{{ translate('file.already.exist') }}</h2>
<p>{{ translate('file.pt.1') }}{{ fileName }}{{ translate('file.pt.2') }}</p>
<div class="flex justify-end mt-4 space-x-2">
<button @click="toggleOverwrite">Écraser</button>
<button v-if="!isDirectory" @click="toggleRename">Renommer</button>
<button @click="onCancel">Annuler</button>
<button @click="toggleOverwrite">{{ translate('overwrite') }}</button>
<button v-if="!isDirectory" @click="toggleRename">{{ translate('rename') }}</button>
<button @click="onCancel">{{ translate('cancel') }}</button>
</div>
</div>
<!-- Renommer le fichier -->
<div v-if="displayRename" class="bg-NcBlack rounded-lg shadow-lg p-6 w-96">
<h2 class="text-lg font-semibold mb-4">Modifier le nom du fichier</h2>
<div v-if="displayRename" class="dark:bg-NcBlack bg-white rounded-lg shadow-lg p-6 w-96">
<h2 class="text-lg font-semibold mb-4">{{ translate('change.file.name') }}</h2>
<input
type="text"
v-model="newFileName"
@input="onInputChange"
@keyup.enter="save"
placeholder="Entrez le nom du fichier"
:placeholder="translate('enter.file.name')"
class="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<div class="flex justify-end mt-4 space-x-2">
<button @click="save" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">Valider</button>
<button @click="toggleRename" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">Annuler</button>
<button @click="save" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">{{ translate('confirm') }}</button>
<button @click="toggleRename" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">{{ translate('cancel') }}</button>
</div>
</div>
<!-- Appliquer l'ecrasement a tous -->
<div v-if="displayOverwrite" class="bg-NcBlack rounded-lg shadow-lg p-6 w-96">
<h2 class="text-lg font-semibold mb-4">Vous allez ecraser le fichier/dossier</h2>
<div v-if="displayOverwrite" class="dark:bg-NcBlack bg-white rounded-lg shadow-lg p-6 w-96">
<h2 class="text-lg font-semibold mb-4">{{ translate('you.are.going.to.erase.file.folder') }}</h2>
<div class="flex items-center content-evenly">
<input type="checkbox" v-model="forAll" />
<p>Appliquer à tous*</p>
<p>{{ translate('apply.to.all.*') }}</p>
</div>
<p class="text-xs text-gray-400">* ecrasera tous les fichiers qui ont le même nom sur votre dépôt</p>
<p class="text-xs text-gray-400">{{ translate('*.text') }}</p>
<div class="flex justify-end mt-4 space-x-2">
<button @click="onOverwrite" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">Valider</button>
<button @click="toggleOverwrite" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">Annuler</button>
<button @click="onOverwrite" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">{{ translate('confirm') }}</button>
<button @click="toggleOverwrite" class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">{{ translate('cancel') }}</button>
</div>
</div>
</div>
@ -53,6 +53,10 @@ export default {
isDirectory:{
type: Boolean,
required: true,
},
translate: {
type: Function,
Required: true,
}
},
data() {

View File

@ -6,13 +6,13 @@
:class="getClassButton('default')"
@click="changeTab('default')"
>
Tous les fichiers
{{ translate('all.files') }}
</button>
<button
:class="getClassButton('favorites')"
@click="changeTab('favorites')"
>
Favoris
{{ translate('favorites') }}
</button>
</div>
@ -27,10 +27,10 @@
</NcBreadcrumb>
<template #actions>
<div class="flex items-center ml-2">
<button v-if="!isTransfering" @click="toggleAddFilePopup"
<button v-if="!isTransfering" @click="toggleAddFilePopup" :disabled="currentTab === 'favorites' && current_dir === '/'"
class="flex items-center space-x-2 bg-blue-100 text-blue-600 font-medium px-4 py-2 rounded-md hover:bg-blue-200 transition">
<Plus :size="20" />
<span>Nouveau</span>
<span>{{translate('new')}}</span>
</button>
<div v-else>
<ProgressBar :value="transferProgress" :color="transferStatus" />
@ -44,18 +44,18 @@
<!-- Popup pour la création de fichier -->
<div v-if="isAddFilePopupVisible"
class="fixed inset-0 flex items-center justify-center bg-gray-700 bg-opacity-50 z-50">
<div class="bg-NcBlack rounded-lg shadow-lg p-6 w-96">
<h2 class="text-lg font-semibold mb-4">Créer un nouveau fichier</h2>
<input v-model="newFileName" type="text" placeholder="Nom du fichier"
<div class="dark:bg-NcBlack bg-white rounded-lg shadow-lg p-6 w-96">
<h2 class="text-lg font-semibold mb-4">{{ translate('create.new.file') }}</h2>
<input v-model="newFileName" type="text" :placeholder="translate('name.of.file')"
class="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" />
<div class="flex justify-end mt-4 space-x-2">
<button @click="toggleAddFilePopup"
class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">
Annuler
</button>
<button @click="createNewFile"
class="px-4 py-2 bg-blue-600 text-white rounded-md hover:bg-blue-700 transition">
Créer
{{translate('create')}}
</button>
<button @click="toggleAddFilePopup"
class="px-4 py-2 bg-gray-200 text-gray-700 rounded-md hover:bg-gray-300 transition">
{{translate('cancel')}}
</button>
</div>
</div>
@ -63,10 +63,10 @@
<!-- En-tête -->
<div class="flex h-12 items-center border-b border-gray-300">
<div class="w-7/12 px-4 py-2 text-gray-500 font-semibold border-r border-gray-300">Nom</div>
<div class="w-2/12 px-4 py-2 text-gray-500 font-semibold border-r border-gray-300">Type</div>
<div class="w-2/12 px-4 py-2 text-gray-500 font-semibold">Taille</div>
<div class="w-1/12 px-4 py-2 text-gray-500 font-semibold">Options</div>
<div class="w-7/12 px-4 py-2 text-gray-500 font-semibold border-r border-gray-300">{{ translate('name') }}</div>
<div class="w-2/12 px-4 py-2 text-gray-500 font-semibold border-r border-gray-300">{{ translate('type') }}</div>
<div class="w-2/12 px-4 py-2 text-gray-500 font-semibold">{{ translate('size') }}</div>
<div class="w-1/12 px-4 py-2 text-gray-500 font-semibold">{{ translate('options') }}</div>
</div>
<!-- Contenu -->
@ -78,12 +78,12 @@
@dragleave.prevent="onDragLeave($event)" @dragend="onDragEnd">
<div v-for="file in files" :key="file.filename"
class="flex h-16 items-center hover:bg-NcGray rounded-lg border-b last:border-b-0 border-gray-300"
class="flex h-16 items-center dark:hover:bg-NcGray hover:bg-NcWhite rounded-lg border-b last:border-b-0 border-gray-300 cursor-pointer"
@click="handleClickElem(file)">
<!-- Nom -->
<div class="w-7/12 flex items-center px-4 py-2 border-r border-gray-300">
<div class="w-12 h-12 flex items-center justify-center">
<div class="w-7/12 flex items-center px-4 py-2 border-r border-gray-300 cursor-pointer">
<div class="w-12 h-12 flex items-center justify-cente cursor-pointer">
<template v-if="file.type === 'directory'">
<svg fill="currentColor" viewBox="0 0 24 24" class="text-NcBlue w-10 h-10 ">
<path
@ -92,7 +92,7 @@
</svg>
</template>
<template v-if="file.type === 'file' && file.basename.split('.').pop() !== 'zip'">
<div :class="['flex items-center justify-center']">
<div :class="['flex items-center justify-center cursor-pointer']">
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"
class="w-10 h-10"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2">
@ -114,46 +114,45 @@
</div>
<!-- Type -->
<div class="w-2/12 px-4 py-2 border-r border-gray-300">
<div class="w-2/12 px-4 py-2 border-r border-gray-300 cursor-pointer">
{{ file.type === 'directory' ? 'Dossier' : 'Fichier' }}
</div>
<!-- Taille -->
<div class="w-2/12 px-4 py-2">
<div class="w-2/12 px-4 py-2 cursor-pointer">
{{ file.type === 'directory' ? '-' : formatFileSize(file.size) }}
</div>
<!-- Options -->
<div class="w-1/12 px-4 py-2" @click.stop>
<NcActions>
<NcActionButton @click="deleteElem(file)">
<NcActionButton @click="deleteElem(file)" :closeAfterClick="true">
<template #icon>
<Delete :size="20" />
</template>
Supprimer
{{ translate('delete') }}
</NcActionButton>
<NcActionButton @click="editElem(file)">
<NcActionButton @click="editElem(file)" :closeAfterClick="true">
<template #icon>
<Pencil :size="20" />
</template>
Editer
{{ translate('edit') }}
</NcActionButton>
</NcActions>
</div>
</div>
</div>
<EditFileName v-if="!editDialogDisabled" :initialFileName="initialFileName" :isDirectory="isDirectory"
<EditFileName v-if="!editDialogDisabled" :initialFileName="initialFileName" :isDirectory="isDirectory" :translate="translate"
@update="updateFileName" @close="closeEditDialog">
</EditFileName>
<FileExistsDialog v-if="!fileExistDialogDisabled" :fileName="initialFileName" :isDirectory="isDirectory" @overwrite="setOverwrite" @rename="setRename" @cancel="cancelDrop">
<FileExistsDialog v-if="!fileExistDialogDisabled" :fileName="initialFileName" :isDirectory="isDirectory" :translate="translate"
@overwrite="setOverwrite" @rename="setRename" @cancel="cancelDrop">
</FileExistsDialog>
</div>
</div>
</template>
<script>
// NextCloud Components
import { getClient, getRootPath, getFavoriteNodes } from '@nextcloud/files/dav';
@ -191,13 +190,13 @@ export default {
type: Object,
default: null,
},
zip: {
type: Object,
default: null,
},
dragEnded: {
type: Boolean,
Required: true,
},
translate: {
type: Function,
Required: true,
}
},
watch: {
@ -211,6 +210,7 @@ export default {
},
data() {
return {
trad: null,
files: [], // Liste des fichiers et dossiers récupérés
root_path: getRootPath(),
current_dir: '/',
@ -363,7 +363,7 @@ export default {
await this.fetchFiles();
}
else {
alert(`Vous ne pouvez pas creer le dossier : ${this.newFileName} car un autre dossier porte deja le meme nom.`);
alert(this.translate("cant.create.folder") + this.newFileName + this.translate('already.exists'));
}
} catch (error) {
console.error('Erreur lors de la création du fichier :', error);
@ -407,26 +407,10 @@ export default {
try {
this.isTransfering = true;
const file = this.file;
const zip = this.zip;
console.log(file);
console.log(zip);
if (!file) return;
if (!file && !zip) return;
if (zip) {
const response = await fetch(zip.url);
this.transferProgress = 25;
if (!response.ok) {
throw new Error(`Erreur lors du téléchargement : ${response.statusText}`);
}
const zipFile = await response.arrayBuffer();
this.transferProgress = 50;
await this.moveFileToTarget({
name: zip.name,
content: zipFile
}, '');
this.transferProgress = 100;
if (file.isList) {
await this.moveListOfFiles(file);
} else {
if (file.isDirectory) {
await this.moveFilesOfFolder(file, '');
@ -440,6 +424,7 @@ export default {
this.transferProgress = 100;
}
}
this.isTransfering = false;
this.transferProgress = 0;
this.cancelOperation = false;
@ -456,6 +441,19 @@ export default {
}
this.isDroppable = true;
},
async moveListOfFiles(files) {
for (const file of files.children) {
this.transferProgress += 100 / files.children.length;
if (file.isDirectory) {
await this.moveFilesOfFolder(file, file.parentPath + '/');
} else {
if (file.content && typeof file.content.arrayBuffer === 'function') {
file.content = await file.content.arrayBuffer();
}
await this.moveFileToTarget(file, '');
}
}
},
async moveFilesOfFolder(folder, parentPath) {
await this.createFolder(folder, parentPath + '/');
const checkChildrenInChildren = (folder) => {
@ -613,7 +611,7 @@ export default {
await client.moveFile(oldName, newName);
}
else {
alert(`Vous ne pouvez pas renommez le fichier/dossier : ${names.newFileName} car un autre fichier/dossier porte deja le meme nom.`);
alert(this.translate('cant.rename') + names.newFileName + this.translate('already.exists'));
}
}
catch (error) {

View File

@ -1,117 +1,126 @@
<template>
<div class="flex flex-col h-full w-full border">
<!-- Breadcrumb -->
<div class="flex flex-row mt-1 items-start container">
<NcBreadcrumbs class="max-h-8">
<NcBreadcrumb name="Home" title="Title of the Home folder" @click="handleClickBreadcrumb(-1)">
</NcBreadcrumb>
<NcBreadcrumb v-if="getBreadcrumbParts().length > 0" v-for="(part, index) in breadcrumbParts"
:key="index" :name="part" @click="handleClickBreadcrumb(index)">
</NcBreadcrumb>
</NcBreadcrumbs>
</div>
<div class="flex h-12 items-center border-b border-gray-300">
<div class="w-5/6 px-4 py-2 text-gray-500 font-semibold border-r border-gray-300">Nom</div>
<div class="w-1/6 px-4 py-2 text-gray-500 font-semibold">Taille</div>
</div>
<!-- Fichier .zip -->
<div class="flex h-16 hover:bg-NcGray items-center pl-4 cursor-pointer rounded-lg border-b last:border-b-0 border-gray-300" v-if="!isLoading && zipContent.length !== 0"
draggable="true" @dragstart="dragZip()" @dragend="onDragEnd">
<template>
<div class="flex items-center justify-center cursor-pointer">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" class="w-10 h-10 ">
<path fill="#969696" d="M5.12,5H18.87L17.93,4H5.93L5.12,5M20.54,5.23C20.83,5.57 21,6 21,6.5V19A2,2 0 0,1 19,21H5A2,2 0 0,1 3,19V6.5C3,6 3.17,5.57 3.46,5.23L4.84,3.55C5.12,3.21 5.53,3 6,3H18C18.47,3 18.88,3.21 19.15,3.55L20.54,5.23M6,18H12V15H6V18Z"/>
</svg>
</div>
</template>
<div class="w-5/6 flex items-center px-4 py-2 cursor-pointer">
<div class="truncate max-sm:max-w-32 max-w-64 cursor-pointer">{{ zipName }}</div>
</div>
<div class="w-1/6 py-2 cursor-pointer">
{{ formatFileSize(zipSize) }}
<div @click.stop class="flex items-center cursor-pointer">
<input type="checkbox" id="checkbox-file"
class="NIQUE TA MERE CA CHANGE RIEN PARCE QUE HTML/CSS C DE LA MERDE"
@change="handleCheckAll($event)" v-model="checkedAll" />
</div>
<div class="ml-2 w-5/6 px-4 py-2 text-gray-500 font-semibold border-r border-gray-300">{{
translate('name')}}</div>
<div class="w-1/6 px-4 py-2 text-gray-500 font-semibold">{{ translate('size') }}</div>
</div>
<!-- Archive depliee -->
<div v-if="!isLoading && zipContent.length !== 0" class="overflow-y-auto h-full">
<div v-for="(file, index) in sortedFiles" :key="file.fullPath" class="flex flex-col">
<div v-for="(file, index) in cachedSortedFiles" :key="file.fullPath" class="flex flex-col">
<div class="flex h-16 hover:bg-NcGray items-center pl-4 cursor-pointer rounded-lg border-b last:border-b-0 border-gray-300"
:style="{
'padding-left': `${0.5 * (file.depth + 1)}rem`
}"
@click="toggleFolder(file)" v-if="file.isDirectory" draggable="true" @dragstart="onDragStart(file)" @dragend="onDragEnd">
<div class="w-5/6 flex items-center py-2 border-r border-gray-300 cursor-pointer">
<div class="w-12 h-12 flex items-center justify-center cursor-pointer">
<template>
<svg fill="currentColor" viewBox="0 0 24 24" class="text-NcBlue w-10 h-10 ">
<path
d="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z">
</path>
</svg>
</template>
</div>
<div class="w-4/6 flex items-center py-2 border-r border-gray-300 cursor-pointer">
<!-- Icône dynamique pour plié/déplié -->
<div class="flex flex-row w-full gap-2">
<div v-if="isVisible(file)" @click.stop class="flex items-center cursor-pointer">
<input type="checkbox" id="checkbox-file"
class="NIQUE TA MERE SA CHANGE RIEN PARCE QUE HTML/CSS C DE LA MERDE"
@change="handleCheckboxChange(file, $event)" :checked="isChecked(file)" />
</div>
<div class="flex w-full h-16 dark:hover:bg-NcGray hover:bg-NcWhite items-center pl-4 cursor-pointer rounded-lg border-b last:border-b-0 border-gray-300"
@click="toggleFolder(file)" v-if="file.isDirectory && isVisible(file)" draggable="true"
@dragstart="onDragStart(file)" @dragend="onDragEnd">
<div class="w-5/6 flex items-center py-2 border-r border-gray-300 cursor-pointer">
<div class="w-12 h-12 flex items-center justify-center cursor-pointer">
<component :is="folderMap[file.fullPath] ? ChevronDownIcon : ChevronRightIcon"
class="text-NcBlue w-6 h-6" />
<template>
<svg fill="currentColor" viewBox="0 0 24 24" class="text-NcBlue w-10 h-10 ">
<path
d="M10,4H4C2.89,4 2,4.89 2,6V18A2,2 0 0,0 4,20H20A2,2 0 0,0 22,18V8C22,6.89 21.1,6 20,6H12L10,4Z">
</path>
</svg>
</template>
</div>
<span class="ml-2 truncate cursor-pointer">{{ file.name }}</span>
<div class="w-4/6 flex items-center py-2 border-r border-gray-300 cursor-pointer">
<span class="ml-2 truncate cursor-pointer">{{ file.name }}</span>
</div>
</div>
<div class="w-1/6 px-4 py-2 cursor-pointer">-</div>
</div>
<div class="flex h-16 w-full dark:hover:bg-NcGray hover:bg-NcWhite items-center pl-4 cursor-pointer rounded-lg border-b last:border-b-0 border-gray-300"
v-else-if="isVisible(file)" draggable="true" @dragstart="onDragStart(file, $event)">
<template>
<div class="flex items-center justify-center cursor-pointer">
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"
class="w-10 h-10"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2">
<path
d="M6 22c-.55 0-1.021-.196-1.412-.587A1.927 1.927 0 0 1 4 20V4c0-.55.196-1.021.588-1.413A1.926 1.926 0 0 1 6 2h8l6 6v12a1.93 1.93 0 0 1-.587 1.413A1.93 1.93 0 0 1 18 22H6Z"
style="fill:#969696;fill-rule:nonzero"
transform="matrix(.7 0 0 .7 -.43 -.388)" />
</svg>
</div>
</template>
<div class="w-5/6 flex items-center px-4 py-2 cursor-pointer">
<div class="truncate max-sm:max-w-32 max-w-96 cursor-pointer">{{ file.name }}</div>
</div>
<div class="w-1/6 py-2 cursor-pointer">
{{ formatFileSize(file.size) }}
</div>
</div>
<div class="w-1/6 px-4 py-2 cursor-pointer">-</div>
</div>
<div class="flex h-16 hover:bg-NcGray items-center pl-4 cursor-pointer rounded-lg border-b last:border-b-0 border-gray-300"
:style="{
'padding-left': `${0.5 * (file.depth + 1)}rem`
}"
v-else draggable="true" @dragstart="onDragStart(file, $event)">
<template>
<div class="flex items-center justify-center cursor-pointer">
<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg" xml:space="preserve"
class="w-10 h-10"
style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2">
<path
d="M6 22c-.55 0-1.021-.196-1.412-.587A1.927 1.927 0 0 1 4 20V4c0-.55.196-1.021.588-1.413A1.926 1.926 0 0 1 6 2h8l6 6v12a1.93 1.93 0 0 1-.587 1.413A1.93 1.93 0 0 1 18 22H6Z"
style="fill:#969696;fill-rule:nonzero" transform="matrix(.7 0 0 .7 -.43 -.388)" />
</svg>
</div>
</template>
<div class="w-5/6 flex items-center px-4 py-2 cursor-pointer">
<div class="truncate max-sm:max-w-32 max-w-96 cursor-pointer">{{ file.name }}</div>
</div>
<div class="w-1/6 py-2 cursor-pointer">
{{ formatFileSize(file.size) }}
</div>
</div>
</div>
</div>
<div v-if="isLoading" class="flex h-full items-center justify-center">
<component :is="Loading" class="text-white w-24 h-24 animate-spin" :size="40" />
</div>
<div v-if="!isLoading && zipContent.length === 0" class="flex h-full items-center justify-center">
<span class="text-gray-500">Aucun contenu à afficher</span>
<span class="text-gray-500">{{ translate('no.content') }}</span>
</div>
</div>
</template>
<script>
import NcBreadcrumbs from '@nextcloud/vue/dist/Components/NcBreadcrumbs.js';
import NcBreadcrumb from '@nextcloud/vue/dist/Components/NcBreadcrumb.js';
import JSZip from 'jszip';
import ChevronRightIcon from 'vue-material-design-icons/ChevronRight.vue';
import ChevronDownIcon from 'vue-material-design-icons/ChevronDown.vue';
import Loading from 'vue-material-design-icons/Loading.vue';
import { ref } from 'vue';
import path from 'path';
import {fileTypeFromBuffer} from 'file-type';
export default {
name: 'WebContentViewer',
components: {
NcBreadcrumbs,
NcBreadcrumb
},
data() {
return {
zipContent: [],
folderMap: {},
archiveUrl: '',
token: '',
mode : '',
ChevronRightIcon,
ChevronDownIcon,
isLoading: ref(false),
Loading,
zipName: '',
zipSize: 0,
currentDir: '',
breadcrumbParts: [],
cochedFiles: [],
checkedAll: false,
cachedSortedFiles: null,
};
},
props: {
@ -119,8 +128,12 @@ export default {
type: String,
required: true,
},
translate: {
type: Function,
Required: true,
}
},
computed: {
computed:{
sortedFiles() {
const flattenAndSort = (files, parentPath = '') => {
const flatList = [];
@ -143,96 +156,189 @@ export default {
return flatList.sort((a, b) => a.fullPath.localeCompare(b.fullPath));
};
return flattenAndSort(this.zipContent);
},
},
watch: {
sortedFiles: {
handler(newVal) {
this.cachedSortedFiles = newVal; // Met à jour la variable chaque fois que sortedFiles change
},
immediate: true, // Met à jour dès que le composant est monté
},
},
async mounted() {
this.isLoading = true;
await this.loadZipContent();
const webTransferDiv = document.getElementById('archiveInfos');
if (webTransferDiv) {
this.archiveUrl = webTransferDiv.dataset.archiveUrl;
this.token = webTransferDiv.dataset.token;
this.mode = webTransferDiv.getAttribute('feur');
} else {
console.error('Pas d\'informations pour recuperer l\'archive');
}
await this.loadZipContent();
this.isLoading = false;
},
methods: {
async loadZipContent() {
try {
const response = await fetch(this.zipUrl);
const zipData = await response.blob();
this.zipName = this.zipUrl.split('/').pop();
const zip = await JSZip.loadAsync(zipData);
this.zipSize = zipData.size;
var baseUrl = OC.generateUrl('/apps/webtransfer/getFile');
let fullUrl = baseUrl + '?url=' + encodeURIComponent(this.zipUrl);
const files = [];
let response = await fetch(fullUrl);
let responseJson = await response.json();
zip.forEach((relativePath, file) => {
const pathParts = relativePath.split('/').filter(Boolean);
let currentLevel = files;
const zipData = responseJson.parameters.data;
const first10Chars = zipData.substring(0,4);
for (let i = 0; i < pathParts.length; i++) {
const partName = pathParts[i];
const isDirectory = i < pathParts.length - 1 || file.dir;
let existing = currentLevel.find(f => f.name === partName && f.isDirectory === isDirectory);
if(this.mode === "zip") {
// Check si le debut du fichier correspond a celui d'un zip
if(!this.isNotToBeUncompressed() && (first10Chars === 'PK\x03\x04' || first10Chars === 'PK\x05\x06' || first10Chars === 'PK\x07\x08')) {
this.zipName = this.zipUrl.split('/').pop();
const zip = await JSZip.loadAsync(zipData);
this.zipSize = zipData.size;
let promise;
const files = [];
if (!isDirectory) {
promise = file.async("blob").then(content => {
existing.content = content;
zip.forEach((relativePath, file) => {
const pathParts = relativePath.split('/').filter(Boolean);
let currentLevel = files;
for (let i = 0; i < pathParts.length; i++) {
const partName = pathParts[i];
const isDirectory = i < pathParts.length - 1 || file.dir;
let existing = currentLevel.find(f => f.name === partName && f.isDirectory === isDirectory);
let promise;
if (!isDirectory) {
promise = file.async("blob").then(content => {
existing.content = content;
});
}
if (!existing) {
existing = {
name: pathParts[i],
isDirectory,
size: isDirectory ? 0 : file._data.uncompressedSize,
content: isDirectory ? null : '', // Initialiser 'content' pour les fichiers
children: isDirectory ? [] : null,
depth: pathParts.length, // Profondeur du fichier dans l'arborescence
//remove the name of the file from the path
parentPath: i > 0 ? pathParts[i - 1] : '',
unzip: promise
};
currentLevel.push(existing);
}
if (isDirectory) {
currentLevel = existing.children;
}
}
});
// Attendre que tous les contenus de fichier soient extraits
this.zipContent = files;
// Initialiser folderMap
const initializeFolderMap = (files, parentPath = '') => {
files.forEach(file => {
const fullPath = parentPath ? `${parentPath}/${file.name}` : file.name;
this.$set(this.folderMap, fullPath, false);
if (file.isDirectory && file.children) {
initializeFolderMap(file.children, fullPath);
}
});
}
};
if (!existing) {
existing = {
name: pathParts[i],
isDirectory,
size: isDirectory ? 0 : file._data.uncompressedSize,
content: isDirectory ? null : '', // Initialiser 'content' pour les fichiers
children: isDirectory ? [] : null,
depth: pathParts.length, // Profondeur du fichier dans l'arborescence
//remove the name of the file from the path
parentPath: i > 0 ? pathParts[i - 1] : '',
unzip: promise
};
currentLevel.push(existing);
}
if (isDirectory) {
currentLevel = existing.children;
}
initializeFolderMap(this.zipContent);
console.log('Contenu du ZIP chargé avec succès');
}
else {
throw new Error("ERROR : file is not a ZIP file.");
}
}
else if(this.mode == "file"){
const uint8Array = new Uint8Array(zipData.length);
for (let i = 0; i <zipData.length; i++) {
uint8Array[i] = zipData.charCodeAt(i);
}
});
// Attendre que tous les contenus de fichier soient extraits
this.zipContent = files;
// Initialiser folderMap
const initializeFolderMap = (files, parentPath = '') => {
files.forEach(file => {
const fullPath = parentPath ? `${parentPath}/${file.name}` : file.name;
this.$set(this.folderMap, fullPath, false);
if (file.isDirectory && file.children) {
initializeFolderMap(file.children, fullPath);
}
});
};
initializeFolderMap(this.zipContent);
console.log('Contenu du ZIP chargé avec succès');
try {
let type = await fileTypeFromBuffer(uint8Array);
const file = new File([uint8Array], 'file.' + type.ext, {type: type.mime});
let entry = [{
name: file.name,
isDirectory: false,
size: file.size,
content: uint8Array, // Initialiser 'content' pour les fichiers
children: null,
depth: 0, // Profondeur du fichier dans l'arborescence
//remove the name of the file from the path
parentPath: '',
}]
this.zipContent = entry;
console.log('Fichier chargé avec succès');
} catch (e) {
console.log('Erreur lors du telechargement du fichier.');
}
}
else {
throw new Error("ERROR : unknowed mode :", this.mode);
}
} catch (error) {
console.error('Erreur lors du chargement du contenu du ZIP :', error);
}
},
isNotToBeUncompressed(){
/**
* Vérifie si l'archive à extraire fait partie des fichiers qu'il ne faut pas extraire
*
* @returns true si c'est un type de fichier à ne pas extraire, false sinon
*/
const forbiddenExtensions = ['.docx', '.xlsx', '.odt', '.epub', '.pptx'];
const fileName = this.zipUrl.split('/').pop().toLowerCase();
const hasForbiddenExtension = forbiddenExtensions.some(ext => fileName.endsWith(ext));
if (hasForbiddenExtension) {
return true;
}
else {
return false;
}
},
onDragEnd(event) {
event.preventDefault();
this.$emit('dragEnded');
},
handleCheckboxChange(file, event) {
if (event.target.checked) {
this.cocheFile(file);
} else {
this.decocheFile(file);
}
},
getFullPath(file) {
if (!file.parentPath || file.parentPath === '') {
return file.name;
} else {
return `${file.parentPath}/${file.name}`;
}
},
cocheFile(file) {
if (!this.cochedFiles.some(f => this.getFullPath(f) === this.getFullPath(file))) {
this.cochedFiles.push(file);
}
},
decocheFile(file) {
this.cochedFiles = this.cochedFiles.filter(f => this.getFullPath(f) !== this.getFullPath(file));
},
isChecked(file) {
return this.cochedFiles.some(f => this.getFullPath(f) === this.getFullPath(file));
},
formatFileSize(size) {
if (size < 1024) return `${size} B`;
if (size < 1024 * 1024) return `${(size / 1024).toFixed(2)} KB`;
@ -241,19 +347,33 @@ export default {
},
toggleFolder(file) {
if (!file.isDirectory) return;
this.uncheckAll();
const currentState = this.folderMap[file.fullPath];
const parentPath = file.parentPath;
if (!currentState) {
this.currentDir = file.fullPath;
}
else {
this.currentDir = parentPath;
}
this.$set(this.folderMap, file.fullPath, !currentState);
if (parentPath !== '') {
const parentState = this.folderMap[parentPath];
this.$set(this.folderMap, parentPath, !parentState);
}
this.breadcrumbParts = this.getBreadcrumbParts()
},
async dragZip() {
try {
const zip = {name: this.zipName, url: this.zipUrl};
const zip = { name: this.zipName, url: this.zipUrl };
this.$emit('zip-upload', zip);
} catch (error) {
console.error('Erreur lors du drag du ZIP :', error);
}
},
async onDragStart(file) {
async onDragStart(file, event) {
const getFilesFromFolder = (folder) => {
const files = [];
if (!folder.children || folder.children.length === 0) return files;
@ -269,19 +389,93 @@ export default {
return files;
};
try {
if (file.isDirectory) {
const files = getFilesFromFolder(file);
const filesToUnzip = files.map(file => file.unzip);
await Promise.all(filesToUnzip);
} else {
await file.unzip;
if (this.cochedFiles.length > 0) {
const folder = {
// Si des fichiers sont cochés, utiliser cette liste
name: file.name,
isDirectory: true,
isList: true,
children: this.cochedFiles,
unzip: Promise.all(this.cochedFiles.map(file => file.unzip))
};
try {
await folder.unzip;
this.$emit('file-upload', folder);
} catch (error) {
console.error('Erreur lors du drag start :', error);
}
} else {
// Logique existante pour un seul fichier/dossier
try {
if (file.isDirectory) {
const files = getFilesFromFolder(file);
const filesToUnzip = files.map(file => file.unzip);
await Promise.all(filesToUnzip);
} else {
await file.unzip;
}
this.$emit('file-upload', file);
} catch (error) {
console.error('Erreur lors du drag start :', error);
}
this.$emit('file-upload', file);
} catch (error) {
console.error('Erreur lors du drag start :', error);
}
},
isVisible(file) {
let parentPath = file.parentPath;
if (this.currentDir === parentPath) {
return true;
}
else {
return false;
}
},
getBreadcrumbParts() {
// Si le currentDir est un simple '/', on le renvoie sous forme de tableau vide.
if (this.currentDir === '') return [];
return this.currentDir.split('/').filter(part => part);
},
generateCrumbHref(index) {
const parts = this.breadcrumbParts.slice(0, index + 1);
return parts.join('/');
},
handleClickBreadcrumb(index) {
this.uncheckAll();
if (this.isTransfering) return;
let dir = '';
if (index >= -1) {
dir = this.generateCrumbHref(index);
}
this.currentDir = dir;
this.breadcrumbParts = this.getBreadcrumbParts();
let file = {
fullPath: dir,
parentPath: this.generateCrumbHref(index - 1),
isDirectory: true,
};
Object.keys(this.folderMap).forEach(key => {
this.folderMap[key] = false;
});
this.toggleFolder(file)
},
uncheckAll() {
this.cochedFiles = [];
this.checkedAll = false;
},
handleCheckAll(event) {
this.sortedFiles.forEach(file => {
if(this.isVisible(file)) {
if (event.target.checked) {
this.cocheFile(file);
} else {
this.decocheFile(file);
}
}
})
}
},
};
</script>

View File

@ -4,12 +4,14 @@ module.exports = {
"./src/**/*.{html,js,jsx,vue}", // Fichiers dans le dossier `src`
"./templates/**/*.{html,php}", // Fichiers dans le dossier `templates`
],
darkMode: ['selector', ':is([data-themes="dark"], [data-themes="dark-highcontrast"], [data-themes="default"])'],
theme: {
extend: {
colors: {
NcBlack: '#171717',
NcBlue: '#0072c3',
NcGray: '#212121',
NcWhite: '#ededed',
},
},
},

View File

@ -7,14 +7,14 @@ use OCP\Util;
Util::addScript(OCA\WebTransfer\AppInfo\Application::APP_ID, 'main');
$archiveUrl = isset($_['archiveUrl']) ? $_['archiveUrl'] : ''; // Valeur par défaut vide si non définie
$token = isset($_['token']) ? $_['token'] : ''; // Valeur par défaut vide si non définie
$mode = isset($_['mode']) ? $_['mode'] : '';
?>
<div id="webtransfer">
</div>
<div id="archiveInfos"
data-archive-url="<?php echo htmlspecialchars($archiveUrl); ?>"
data-token="<?php echo htmlspecialchars($token); ?>"
dataarchiveurl="<?php echo htmlspecialchars($archiveUrl); ?>"
feur="<?php echo $mode?>"
>
</div>

View File

@ -13,5 +13,4 @@ webpackConfig.module.rules.push({
type: 'asset/source',
})
module.exports = webpackConfig