Redesign file browser, file upload (server)

This commit is contained in:
2024-11-12 23:50:21 +01:00
parent 860432ead3
commit 5124ec4e6d
19 changed files with 431 additions and 4095 deletions

242
package-lock.json generated
View File

@@ -1,242 +0,0 @@
{
"name": "eventmaster",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"dependencies": {
"jspdf": "^2.5.2",
"jspdf-autotable": "^3.8.4"
}
},
"node_modules/@babel/runtime": {
"version": "7.26.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.0.tgz",
"integrity": "sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==",
"license": "MIT",
"dependencies": {
"regenerator-runtime": "^0.14.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@types/raf": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz",
"integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==",
"license": "MIT",
"optional": true
},
"node_modules/atob": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
"license": "(MIT OR Apache-2.0)",
"bin": {
"atob": "bin/atob.js"
},
"engines": {
"node": ">= 4.5.0"
}
},
"node_modules/base64-arraybuffer": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz",
"integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/btoa": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/btoa/-/btoa-1.2.1.tgz",
"integrity": "sha512-SB4/MIGlsiVkMcHmT+pSmIPoNDoHg+7cMzmt3Uxt628MTz2487DKSqK/fuhFBrkuqrYv5UCEnACpF4dTFNKc/g==",
"license": "(MIT OR Apache-2.0)",
"bin": {
"btoa": "bin/btoa.js"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/canvg": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.10.tgz",
"integrity": "sha512-qwR2FRNO9NlzTeKIPIKpnTY6fqwuYSequ8Ru8c0YkYU7U0oW+hLUvWadLvAu1Rl72OMNiFhoLu4f8eUjQ7l/+Q==",
"license": "MIT",
"optional": true,
"dependencies": {
"@babel/runtime": "^7.12.5",
"@types/raf": "^3.4.0",
"core-js": "^3.8.3",
"raf": "^3.4.1",
"regenerator-runtime": "^0.13.7",
"rgbcolor": "^1.0.1",
"stackblur-canvas": "^2.0.0",
"svg-pathdata": "^6.0.3"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/canvg/node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"license": "MIT",
"optional": true
},
"node_modules/core-js": {
"version": "3.39.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.39.0.tgz",
"integrity": "sha512-raM0ew0/jJUqkJ0E6e8UDtl+y/7ktFivgWvqw8dNSQeNWoSDLvQ1H/RN3aPXB9tBd4/FhyR4RDPGhsNIMsAn7g==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/css-line-break": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
"integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==",
"license": "MIT",
"optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/dompurify": {
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.5.7.tgz",
"integrity": "sha512-2q4bEI+coQM8f5ez7kt2xclg1XsecaV9ASJk/54vwlfRRNQfDqJz2pzQ8t0Ix/ToBpXlVjrRIx7pFC/o8itG2Q==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optional": true
},
"node_modules/fflate": {
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz",
"integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==",
"license": "MIT"
},
"node_modules/html2canvas": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz",
"integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==",
"license": "MIT",
"optional": true,
"dependencies": {
"css-line-break": "^2.1.0",
"text-segmentation": "^1.0.3"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/jspdf": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/jspdf/-/jspdf-2.5.2.tgz",
"integrity": "sha512-myeX9c+p7znDWPk0eTrujCzNjT+CXdXyk7YmJq5nD5V7uLLKmSXnlQ/Jn/kuo3X09Op70Apm0rQSnFWyGK8uEQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.23.2",
"atob": "^2.1.2",
"btoa": "^1.2.1",
"fflate": "^0.8.1"
},
"optionalDependencies": {
"canvg": "^3.0.6",
"core-js": "^3.6.0",
"dompurify": "^2.5.4",
"html2canvas": "^1.0.0-rc.5"
}
},
"node_modules/jspdf-autotable": {
"version": "3.8.4",
"resolved": "https://registry.npmjs.org/jspdf-autotable/-/jspdf-autotable-3.8.4.tgz",
"integrity": "sha512-rSffGoBsJYX83iTRv8Ft7FhqfgEL2nLpGAIiqruEQQ3e4r0qdLFbPUB7N9HAle0I3XgpisvyW751VHCqKUVOgQ==",
"license": "MIT",
"peerDependencies": {
"jspdf": "^2.5.1"
}
},
"node_modules/performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==",
"license": "MIT",
"optional": true
},
"node_modules/raf": {
"version": "3.4.1",
"resolved": "https://registry.npmjs.org/raf/-/raf-3.4.1.tgz",
"integrity": "sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA==",
"license": "MIT",
"optional": true,
"dependencies": {
"performance-now": "^2.1.0"
}
},
"node_modules/regenerator-runtime": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
"integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
"license": "MIT"
},
"node_modules/rgbcolor": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz",
"integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==",
"license": "MIT OR SEE LICENSE IN FEEL-FREE.md",
"optional": true,
"engines": {
"node": ">= 0.8.15"
}
},
"node_modules/stackblur-canvas": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz",
"integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.1.14"
}
},
"node_modules/svg-pathdata": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz",
"integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/text-segmentation": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz",
"integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==",
"license": "MIT",
"optional": true,
"dependencies": {
"utrie": "^1.0.2"
}
},
"node_modules/utrie": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz",
"integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==",
"license": "MIT",
"optional": true,
"dependencies": {
"base64-arraybuffer": "^1.0.2"
}
}
}
}

View File

@@ -1,6 +0,0 @@
{
"dependencies": {
"jspdf": "^2.5.2",
"jspdf-autotable": "^3.8.4"
}
}

View File

@@ -1,13 +1,19 @@
import { Request, Response, NextFunction, Router } from 'express'
import { deleteAllTables, deleteExerciseProgressTables, prepopulateDatabase, prepopulateExerciseDatabase } from '../scripts/databaseHelper'
import fs from "fs"
export const api = Router()
/**
* Status check endpoint
*/
api.get("/", (req: Request, res: Response, next: NextFunction) => {
res.status(200).send()
})
/**
* Reset the whole database to factory state
* Doesn't effect ExerciseTable and ExerciseGroupTable
*/
api.get("/resetdatabase", async (req: Request, res: Response, next: NextFunction) => {
// Step 1: Delete all data tables
deleteAllTables()
@@ -19,35 +25,13 @@ api.get("/resetdatabase", async (req: Request, res: Response, next: NextFunction
res.status(200).send()
})
/**
* Reset ExerciseTable and ExerciseGroupTable to factory state
*/
api.get("/resetExerciseProgress", async (req: Request, res: Response, next: NextFunction) => {
deleteExerciseProgressTables()
await prepopulateExerciseDatabase()
res.status(200).send()
})
/**
* Get all uploaded file names
*/
api.get("/files", async (req: Request, res: Response) => {
let dirNames = fs.readdirSync("./backend/images")
let result = []
dirNames.forEach(dir => {
let fileNames = fs.readdirSync("./backend/images/" + dir)
result.push({
folder: dir,
files: fileNames.map(file => {
return {
name: file,
size: fs.statSync("./backend/images/" + dir + "/" + file).size,
url: "http://localhost:3000/static/" + dir + "/" + file
}
})
})
})
res.status(200).json(result)
})

View File

@@ -11,7 +11,9 @@ import { calcOverallRating, calcRatingValues } from "../scripts/calcScripts";
export const band = Router()
// Get all bands
/**
* Get all bands
*/
band.get("/", (req: Request, res: Response) => {
let sort = req.query.sort
let count = req.query.count
@@ -64,7 +66,9 @@ band.get("/", (req: Request, res: Response) => {
})
})
// Get all information about one band
/**
* Get all information about one band
*/
band.get("/band/:name", (req: Request, res: Response) => {
Band.findOne({
where: {
@@ -123,7 +127,9 @@ band.get("/band/:name", (req: Request, res: Response) => {
})
// Band search
/**
* Band search
*/
band.get("/search", (req: Request, res: Response) => {
Band.findAll({
where: {
@@ -139,7 +145,9 @@ band.get("/search", (req: Request, res: Response) => {
})
// Edit band
/**
* Edit band
*/
band.patch("/", (req: Request, res: Response) => {
Band.update(req.body, {
where: {
@@ -152,7 +160,9 @@ band.patch("/", (req: Request, res: Response) => {
})
// New band
/**
* New band
*/
band.post("/", (req: Request, res: Response) => {
Band.create(req.body)
.then(result => {
@@ -160,6 +170,9 @@ band.post("/", (req: Request, res: Response) => {
})
})
/**
* Delete a band
*/
band.delete("/", (req: Request, res: Response) => {
Band.destroy({
where: {

View File

@@ -0,0 +1,52 @@
import { Request, Response, NextFunction, Router } from 'express'
import fs from "fs"
import multer from "multer"
const upload = multer({ dest: './backend/images/' })
export const files = Router()
/**
* Get all folders
*/
files.get("/folders", async (req: Request, res: Response) => {
let dirNames = fs.readdirSync("./backend/images")
let result = []
dirNames.forEach(dir => {
result.push({
name: dir,
nrOfItems: fs.readdirSync("./backend/images/" + dir).length
})
})
res.status(200).json(result)
})
/**
* Get all uploaded file names by file name
*/
files.get("/:folder", async (req: Request, res: Response) => {
let result = []
let fileNames = fs.readdirSync("./backend/images/" + req.params.folder + "/")
fileNames.forEach(file => {
result.push({
name: file,
size: fs.statSync("./backend/images/" + req.params.folder + "/" + file).size,
url: "http://localhost:3000/static/" + req.params.folder + "/" + file
})
})
res.status(200).json(result)
})
/**
* Upload a file
*/
files.post("/", upload.single("file"), function (req: Request, res: Response, next: NextFunction) {
console.log(req.file)
res.status(200).send()
})

View File

@@ -11,6 +11,7 @@ import { genre } from './routes/genre.routes'
import { location } from './routes/location.routes'
import { city } from './routes/city.routes'
import { exercises } from './routes/exercise.routes'
import { files } from './routes/files.routes'
const app = express()
const port = 3000
@@ -43,7 +44,7 @@ app.use("/accounts", account)
app.use("/cities", city)
app.use("/concerts", concert)
app.use("/exercises", exercises)
app.use("/files", files)
// Start server
const server = app.listen(port, () => {

File diff suppressed because it is too large Load Diff

View File

@@ -34,6 +34,7 @@
},
"dependencies": {
"@mdi/font": "^7.4.47",
"@types/multer": "^1.4.12",
"@vueuse/core": "^11.1.0",
"axios": "^1.7.7",
"body-parser": "^1.20.2",
@@ -41,6 +42,7 @@
"electron-squirrel-startup": "^1.0.1",
"express": "^4.21.1",
"moment": "^2.30.1",
"multer": "^1.4.5-lts.1",
"pinia": "^2.2.4",
"reflect-metadata": "^0.2.2",
"sequelize": "^6.37.4",

View File

@@ -0,0 +1,45 @@
import axios from "axios"
const BASE_URL = "http://localhost:3000/files"
/**
* Fetch all public folders on server
*
* @returns Response from server
*/
export function fetchFolderNames() {
return axios.get(BASE_URL + "/folders")
}
/**
* Fetch all static file names
*
* @param dirName Name of folder where to scan files
*
* @returns Response from server
*/
export function fetchFileNames(dirName: string) {
return axios.get(BASE_URL + "/" + dirName)
}
/**
* Upload a file to the server
*
* @param file File to store on server
*
* @returns Response from server
*/
export function postFile(file, folder: string) {
let formData = new FormData()
formData.append("file", file)
formData.append("folder", folder)
console.log(formData)
return axios.post(BASE_URL, formData, {
headers: {
"Content-Type": "multipart/form-data"
}
})
}

View File

@@ -27,13 +27,4 @@ export function resetDatabase() {
*/
export function resetExerciseProgress() {
return axios.get(BASE_URL + "/resetExerciseProgress")
}
/**
* Fetch all static file names
*
* @returns Response from server
*/
export function fetchFileNames() {
return axios.get(BASE_URL + "/files")
}

View File

@@ -238,7 +238,10 @@
},
"user": "Angaben zur Person",
"registrationNumber": "Matrikelnummer",
"yourFullName": "Vollständiger Name"
"yourFullName": "Vollständiger Name",
"chooseFile": "Datei auswählen",
"chooseDestinationFolder": "Zielordner auswählen",
"upload": "Hochladen"
},
"genre": {
"withoutBand": "ohne Band"

View File

@@ -238,7 +238,10 @@
},
"user": "About person",
"registrationNumber": "Matrikel number",
"yourFullName": "Full name"
"yourFullName": "Full name",
"chooseFile": "Choose file",
"chooseDestinationFolder": "Choose destination folder",
"upload": "Upload"
},
"genre": {
"withoutBand": "without Band"

View File

@@ -8,6 +8,7 @@ import { useGenreStore } from '@/stores/genre.store';
import { usePreferencesStore } from '@/stores/preferences.store';
import dashboardCard from './dashboardCard.vue';
import { useOrderStore } from '@/stores/order.store';
import { useFilesStore } from '@/stores/files.store';
const concertStore = useConcertStore()
const bandStore = useBandStore()
@@ -17,10 +18,11 @@ const locationStore = useLocationStore()
const exerciseStore = useExerciseStore()
const preferencesStore = usePreferencesStore()
const orderStore = useOrderStore()
const filesStore = useFilesStore()
exerciseStore.solveExercise(2, 1)
preferencesStore.getStaticFiles()
filesStore.getStaticFolders()
bandStore.getBands()
locationStore.getLocations()
genreStore.getGenres()
@@ -98,10 +100,10 @@ orderStore.getAllOrders()
<dashboard-card
:title="$t('misc.file', 2)"
icon="mdi-file"
:first-line="preferencesStore.staticFiles.reduce((counter, obj) => {
return counter += obj.files.length
}, 0) + ' ' + $t('misc.file', 2)"
:second-line="preferencesStore.staticFiles.length + ' ' + $t('misc.folder', 2)"
:first-line="filesStore.staticFolders.reduce((counter, obj) => {
return counter + obj.nrOfItems
}, 0) + ' ' + $t('misc.file', 2)"
:second-line="filesStore.staticFolders.length + ' ' + $t('misc.folder', 2)"
button-route="/admin/files"
:loading="preferencesStore.fetchInProgress"
/>

View File

@@ -1,15 +0,0 @@
<script setup lang="ts">
import actionDialog from '@/components/basics/actionDialog.vue';
const showDialog = defineModel("showDialog")
defineProps({
url: String
})
</script>
<template>
<action-dialog v-model="showDialog" max-width="500">
<v-img :src="url" max-height="400" />
</action-dialog>
</template>

View File

@@ -0,0 +1,64 @@
<script setup lang="ts">
import actionDialog from '@/components/basics/actionDialog.vue';
import outlinedButton from '@/components/basics/outlinedButton.vue';
import { useFilesStore } from '@/stores/files.store';
import { ref } from 'vue';
const filesStore = useFilesStore()
const test = ref()
</script>
<template>
<action-dialog
v-model="filesStore.showFileUploadDialog"
:title="$t('misc.uploadFile')"
icon="mdi-file"
max-width="800"
>
<v-form :model-value="test">
<v-container>
<v-row>
<v-col>
<v-file-input
v-model="filesStore.fileUpload"
clearable
:label="$t('misc.chooseFile')"
:disabled="filesStore.fetchInProgress"
/>
</v-col>
</v-row>
<v-row>
<v-col>
<v-radio-group
v-model="filesStore.fileUploadDir"
:label="$t('misc.chooseDestinationFolder')"
:disabled="filesStore.fetchInProgress"
>
<v-radio
v-for="folder of filesStore.staticFolders"
:label="folder.name + '/'"
:value="folder.name"
/>
</v-radio-group>
</v-col>
</v-row>
</v-container>
<v-btn type="submit">Submit</v-btn>
</v-form>
<template #actions>
<outlined-button
@click="filesStore.uploadFile"
prepend-icon="mdi-file-upload"
color="green"
:disabled="filesStore.fileUploadDir.length == 0 || filesStore.fileUpload == undefined"
:loading="filesStore.fetchInProgress"
>
{{ $t('misc.upload') }}
</outlined-button>
</template>
</action-dialog>
</template>

View File

@@ -1,44 +1,64 @@
<script setup lang="ts">
import adminDataLayout from '@/layouts/adminDataLayout.vue';
import { usePreferencesStore } from '@/stores/preferences.store';
import filePreviewDialog from './filePreviewDialog.vue';
import { ref } from 'vue';
import FileUploadDialog from './fileUploadDialog.vue';
import { useFilesStore } from '@/stores/files.store';
const preferencesStore = usePreferencesStore()
const showDialog = ref(false)
const filesStore = useFilesStore()
const showPreviewDialog = ref(false)
const previewFile = ref("")
preferencesStore.getStaticFiles()
filesStore.getStaticFolders()
</script>
<template>
<admin-data-layout
:add-button-string="$t('misc.uploadFile')"
:fetch-in-progress="preferencesStore.fetchInProgress"
:on-add-click="() => { /** todo */ }"
:fetch-in-progress="filesStore.fetchInProgress"
:on-add-click="() => { filesStore.showFileUploadDialog = true }"
>
<v-row>
<v-col
v-for="folder of preferencesStore.staticFiles"
cols="12"
md="3"
sm="6"
>
<v-row >
<v-col cols="2" class="border">
<v-list>
<v-list-subheader>{{ folder.folder }}/</v-list-subheader>
<v-list-item
v-for="file of folder.files"
:title="file.name"
:subtitle="Math.round(file.size / 1024) + ' KB'"
@click="() => { previewFile = file.url; showDialog = true }"
<v-list-item
v-for="folder of filesStore.staticFolders"
:key="folder.name"
:value="folder"
:title="folder.name + '/'"
@click="filesStore.selectedFolder = folder; filesStore.getStaticFiles()"
/>
</v-list>
</v-col>
<v-col cols="4" class="border">
<v-skeleton-loader
:loading="filesStore.fetchInProgress"
type="list-item-two-line"
>
<v-list max-height="800" class="w-100">
<v-list-item
v-for="file of filesStore.staticFiles"
:title="file.name"
:value="file.name"
:subtitle="Math.round(file.size / 1024) + ' KB'"
@click="() => { filesStore.selectedFile = file }"
/>
</v-list>
</v-skeleton-loader>
</v-col>
<v-col class="border">
<v-img
v-if="filesStore.selectedFile != undefined"
:src="filesStore.selectedFile.url" max-height="400" />
</v-col>
</v-row>
</admin-data-layout>
<file-preview-dialog
v-model:show-dialog="showDialog"
v-model:show-dialog="showPreviewDialog"
:url="previewFile"
/>
<file-upload-dialog />
</template>

View File

@@ -0,0 +1,61 @@
import { fetchFileNames, fetchFolderNames, postFile } from "@/data/api/files.api";
import { defineStore } from "pinia";
import { ref } from "vue";
export const useFilesStore = defineStore('filesStore', {
state: () => ({
/** Request to server sent, waiting for data response */
fetchInProgress: ref(false),
staticFolders: ref<Array<{name: string, nrOfItems: number}>>([]),
selectedFolder: ref<{name: string, nrOfItems: number}>(),
/** List of files on the server */
staticFiles: ref<Array<{name: string, size: number, url: string}>>([]),
selectedFile: ref<{name: string, size: number, url: string}>(),
showFileUploadDialog: ref(false),
fileUpload: ref(),
fileUploadDir: ref(""),
}),
actions: {
async getStaticFolders() {
this.fetchInProgress = true
fetchFolderNames()
.then(res => {
this.staticFolders = res.data
this.fetchInProgress = false
})
},
/**
* Request all available static files on server
*/
async getStaticFiles() {
this.fetchInProgress = true
fetchFileNames(this.selectedFolder.name)
.then(res => {
this.staticFiles = res.data
this.fetchInProgress = false
})
},
async uploadFile() {
this.fetchInProgress = true
postFile(this.uploadFile, this.fileUploadDir)
.then(response => {
console.log(response)
this.showFileUploadDialog = false
this.fetchInProgress = false
})
},
}
})

View File

@@ -3,7 +3,7 @@ import { useLocalStorage } from "@vueuse/core";
import { ThemeEnum } from "../data/enums/themeEnums";
import { LanguageEnum } from "../data/enums/languageEnum";
import { ref } from "vue";
import { fetchFileNames, fetchServerState, resetDatabase, resetExerciseProgress } from "@/data/api/mainApi";
import { fetchServerState,resetDatabase, resetExerciseProgress } from "@/data/api/mainApi";
import { ServerStateEnum } from "@/data/enums/serverStateEnum";
import { BannerStateEnum } from "@/data/enums/bannerStateEnum";
import { useFeedbackStore } from "./feedback.store";
@@ -32,9 +32,6 @@ export const usePreferencesStore = defineStore('preferencesStore', {
/** Show the "Factory reset" confirm dialog */
showFactoryResetDialog: ref(false),
/** List of files on the server */
staticFiles: ref([]),
/** Marks the first run of the app */
firstStartup: useLocalStorage<Boolean>("hackmycart/preferencesStore/firstStartup", true),
@@ -108,19 +105,6 @@ export const usePreferencesStore = defineStore('preferencesStore', {
})
},
/**
* Request all available static files on server
*/
async getStaticFiles() {
this.fetchInProgress = true
fetchFileNames()
.then(res => {
this.staticFiles = res.data
this.fetchInProgress = false
})
},
/**
* Reset all store values to factory state
*/

View File

@@ -16,5 +16,5 @@
"backend/**/*.ts",
"backend/**/*.json",
"backend/images/**/**/*"
]
, "backend/server.js" ]
}