More general improvements

This commit is contained in:
Gabriel Massadas
2024-02-24 22:06:22 +00:00
parent b5d6fe0917
commit 01e0a8e6d1
8 changed files with 286 additions and 123 deletions
+12 -3
View File
@@ -123,13 +123,22 @@ export const apiHandler = {
newKey: encode(newKey)
})
},
updateMetadata: (bucket, key, metadata) => {
return api.post(
updateMetadata: async (bucket, key, metadata) => {
let prefix = ''
if (key.includes('/')) {
prefix = key.replace(key.split('/').pop(), '')
}
const resp = await api.post(
`/buckets/${bucket}/${encode(key)}`,
{
customMetadata: metadata
}
)
if (resp.status === 200) {
return mapFile(resp.data, prefix)
}
},
multipartCreate: (file, key, bucket) => {
return api.post(`/buckets/${bucket}/multipart/create`, null, {
@@ -193,7 +202,7 @@ export const apiHandler = {
let contentFolders = []
while (truncated) {
const response = await this.listObjects(bucket, prefix, delimiter, cursor)
const response = await apiHandler.listObjects(bucket, prefix, delimiter, cursor)
truncated = response.data.truncated
cursor = response.data.cursor
@@ -77,6 +77,13 @@ export default defineComponent({
this.$bus.emit('fetchFiles')
this.reset()
this.q.notify({
group: false,
icon: 'done', // we add an icon
spinner: false, // we reset the spinner setting so the icon can be displayed
message: 'File renamed!',
timeout: 2500 // we will timeout it in 2.5s
})
},
deleteConfirm: async function() {
this.deleteModal = false
@@ -90,7 +97,7 @@ export default defineComponent({
timeout: 0
})
console.log(this.deleteFolderContents)
// console.log(this.deleteFolderContents)
for (const [i, innerFile] of this.deleteFolderContents.entries()) {
if (innerFile.key) {
await apiHandler.deleteObject(innerFile.key, this.selectedBucket)
@@ -1,24 +1,33 @@
<template>
<div class="q-pa-md">
<div class="flex column">
<div class="q-pa-md" style="height: 100%">
<div class="flex column" style="height: 100%">
<q-btn color="green" icon="add" stack class="q-mb-lg" label="New">
<q-menu>
<q-list>
<q-item clickable v-close-popup @click="$refs.createFolder.open()">
<q-item-section>
<q-item-label><q-icon name="create_new_folder" size="sm"/> New Folder</q-item-label>
<q-item-label>
<q-icon name="create_new_folder" size="sm" />
New Folder
</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="$bus.emit('openFilesUploader')">
<q-item-section>
<q-item-label><q-icon name="upload_file" size="sm"/> Upload Files</q-item-label>
<q-item-label>
<q-icon name="upload_file" size="sm" />
Upload Files
</q-item-label>
</q-item-section>
</q-item>
<q-item clickable v-close-popup @click="$bus.emit('openFoldersUploader')">
<q-item-section>
<q-item-label><q-icon name="folder" size="sm"/> Upload Folders</q-item-label>
<q-item-label>
<q-icon name="folder" size="sm" />
Upload Folders
</q-item-label>
</q-item-section>
</q-item>
</q-list>
@@ -27,9 +36,41 @@
<q-btn class="q-mb-sm" @click="gotoFiles" color="blue" icon="folder_copy" label="Files" stack />
<q-btn class="q-mb-sm" @click="gotoEmail" color="blue" icon="email" label="Email" stack />
<q-btn class="q-mb-sm q-mt-auto q-mb-0" @click="alert=true" color="secondary" icon="question_mark" label="Info"
stack />
</div>
</div>
<q-dialog v-model="alert" persistent no-esc-dismiss no-route-dismiss no-backdrop-dismiss>
<q-card>
<q-card-section>
<div class="text-h6">🎉 Welcome to the new Dashboard v2! 🚀</div>
</q-card-section>
<q-card-section class="q-pt-none">
We're thrilled to introduce our revamped interface, designed to enhance your experience and productivity. As you
explore the new features and improvements, feel free to provide feedback or report any issues you encounter.
Your input helps us fine-tune the dashboard to meet your needs better.<br>
<br>
<template v-if="firstTimeAlert">
To revisit this message in the future, simply click on the question mark icon located in the left corner. Your
feedback is invaluable to us, so don't hesitate to reach out with any thoughts or concerns.<br>
<br>
</template>
Please report issues here: <a href="https://github.com/G4brym/R2-Explorer/issues/58" target="_blank">https://github.com/G4brym/R2-Explorer/issues/58</a><br>
<br>
Thank you for being a part of our journey towards excellence! 🌟<br>
<br>
Best regards<br>
</q-card-section>
<q-card-actions align="right">
<q-btn flat label="OK" color="primary" v-close-popup />
</q-card-actions>
</q-card>
</q-dialog>
<create-folder ref="createFolder" />
</template>
@@ -39,37 +80,51 @@ import { useMainStore } from "stores/main-store";
import CreateFolder from "components/files/CreateFolder.vue";
export default defineComponent({
name: 'LeftSidebar',
name: "LeftSidebar",
data: function() {
return {
alert: false,
firstTimeAlert: true
};
},
components: { CreateFolder },
methods: {
gotoEmail: function() {
if(this.selectedApp !== 'email')
this.changeApp('email')
if (this.selectedApp !== "email")
this.changeApp("email");
},
gotoFiles: function() {
if(this.selectedApp !== 'files')
this.changeApp('files')
if (this.selectedApp !== "files")
this.changeApp("files");
},
changeApp: function(app) {
this.$router.push({ name: `${app}-home`, params: { bucket: this.selectedBucket }})
this.$router.push({ name: `${app}-home`, params: { bucket: this.selectedBucket } });
}
},
computed: {
selectedBucket: function () {
return this.$route.params.bucket
selectedBucket: function() {
return this.$route.params.bucket;
},
selectedApp: function () {
return this.$route.name.split('-')[0]
selectedApp: function() {
return this.$route.name.split("-")[0];
}
},
mounted: function() {
const alertSeen = localStorage.getItem("DASH_V2_ALERT");
if (!alertSeen) {
this.alert = true;
localStorage.setItem("DASH_V2_ALERT", true);
}
},
setup() {
const mainStore = useMainStore();
return {
mainStore,
mainStore
};
},
})
}
});
</script>
<style scoped>
@@ -1,7 +1,7 @@
<template>
<q-btn dense flat round icon="menu" @click="$emit('toggle')" />
<q-toolbar-title class="text-bold">
<q-toolbar-title style="overflow: unset" class="text-bold">
<q-avatar>
<img src="/v2.0/logo-white.svg">
</q-avatar>
@@ -291,8 +291,9 @@ export default {
z-index: -1;
display: flex;
position: absolute;
height: 100%;
width: 100%;
right: 0;
left: 0;
bottom: 0;
top: 0;
flex-direction: column;
}
@@ -7,20 +7,25 @@
<q-btn push icon="arrow_back" :to="{ name: `email-folder`, params: { bucket: $route.params.bucket, folder: $route.params.folder }}">
<q-tooltip>Back</q-tooltip>
</q-btn>
<q-btn push icon="chevron_left">
<q-tooltip>More recent</q-tooltip>
</q-btn>
<q-btn push icon="chevron_right">
<q-tooltip>More older</q-tooltip>
</q-btn>
<q-btn push icon="mark_email_unread">
<q-tooltip>Mark email as unread</q-tooltip>
</q-btn>
<!-- <q-btn push icon="chevron_left">-->
<!-- <q-tooltip>More recent</q-tooltip>-->
<!-- </q-btn>-->
<!-- <q-btn push icon="chevron_right">-->
<!-- <q-tooltip>More older</q-tooltip>-->
<!-- </q-btn>-->
<template v-if="fileHead">
<q-btn push icon="mark_email_unread" @click="markAsUnread" v-if="fileHead.customMetadata.read === 'true'">
<q-tooltip>Mark email as unread</q-tooltip>
</q-btn>
<q-btn push icon="mark_email_read" @click="markAsRead" v-else>
<q-tooltip>Mark email as read</q-tooltip>
</q-btn>
</template>
</q-btn-group>
</q-card-section>
<q-card-section vertical>
<h5 class="font-18">{{ file.subject }}</h5>
<h5 class="font-18 q-my-none">{{ file.subject }}</h5>
</q-card-section>
<q-card-section horizontal class="q-px-sm">
@@ -55,29 +60,16 @@
<h6 class="q-my-md">Attachments</h6>
<div class="row">
<div class="col-xl-4 col-lg-6 col-md-6" v-for="attachment of file.attachments" :key="attachment.filename">
<div class="card mb-1 shadow-none border">
<div class="p-2">
<div class="row align-items-center">
<div class="col-auto">
<div class="avatar-sm">
<span class="avatar-title bg-soft-primary text-primary rounded">
.{{ attachment.filename.split(".").pop() }}
</span>
</div>
</div>
<div class="col ps-0">
<span class="text-muted fw-bold">{{ attachment.filename }}</span>
<!-- <p class="mb-0">{{ bytesToSize(attachment.size) }}</p>-->
</div>
<div class="col-auto">
<a :href="attachment.downloadUrl" class="btn btn-link btn-lg text-muted">
<i class="bi bi-download"></i>
</a>
</div>
</div>
</div>
</div>
<div class="col-md-4" v-for="attachment of file.attachments" :key="attachment.filename">
<q-card>
<q-card-section class="q-pa-sm flex" style="align-items: center">
<q-icon name="description" size="md" color="blue" class="q-mr-sm"/>
{{ attachment.filename }}
<q-btn color="white" text-color="black" icon="download" class="q-mr-0 q-ml-auto" />
</q-card-section>
</q-card>
</div>
</div>
</q-card-actions>
@@ -102,12 +94,14 @@ import { defineComponent } from "vue";
import { api } from "boot/axios";
import { useMainStore } from "stores/main-store";
import { apiHandler, decode, encode, timeSince } from "../../appUtils";
import { useQuasar } from "quasar";
export default defineComponent({
name: "EmailFolderPage",
data: function() {
return {
file: null,
fileHead: null,
timeInterval: null
};
},
@@ -120,6 +114,10 @@ export default defineComponent({
},
selectedFile: function() {
return this.$route.params.file;
},
filePath: function() {
const fileName = decode(this.selectedFile)
return `.r2-explorer/emails/${this.selectedFolder}/${fileName}`
}
},
watch: {
@@ -136,40 +134,65 @@ export default defineComponent({
this.resizeIframe()
},
resizeIframe() {
this.$refs.renderWindow.style.height = this.$refs.renderWindow.contentWindow.document.documentElement.scrollHeight + "px";
if (this.$refs.renderWindow) {
this.$refs.renderWindow.style.height = this.$refs.renderWindow.contentWindow.document.documentElement.scrollHeight + "px";
}
},
fetchEmail: async function() {
const self = this
const fileName = decode(this.selectedFile)
const filePath = `.r2-explorer/emails/${this.selectedFolder}/${fileName}`
const fileData = await apiHandler.downloadFile(this.selectedBucket, filePath, {})
const fileData = await apiHandler.downloadFile(this.selectedBucket, this.filePath, {})
const filename = fileName.split(".json")[0];
for (const att of fileData.data.attachments) {
att.downloadUrl = `${this.mainStore.serverUrl}/api/buckets/${this.selectedBucket}/${encode(`${filename}/${att.filename}`)}`;
}
this.file = fileData.data;
console.log(this.file)
this.timeInterval = setInterval(function() {
this.resizeIframe()
}, 400);
try {
// Up to 1.0.6 there was a bug in the file head endpoint
const fileHead = await apiHandler.headFile(this.selectedBucket, filePath)
if (fileHead.data.customMetadata.read === false) {
await apiHandler.updateMetadata(this.selectedBucket, filePath, {
...fileHead.data.customMetadata,
apiHandler.headFile(this.selectedBucket, this.filePath).then(async (obj) => {
if (obj.customMetadata.read === 'false') {
self.fileHead = await apiHandler.updateMetadata(self.selectedBucket, self.filePath, {
...obj.customMetadata,
read: true
});
} else {
self.fileHead = obj
}
} catch (e) {
// Empty
}
})
this.timeInterval = setInterval(function() {
self.resizeIframe()
}, 400);
},
markAsUnread: async function () {
this.fileHead = await apiHandler.updateMetadata(this.selectedBucket, this.filePath, {
...this.fileHead.customMetadata,
read: false
});
this.q.notify({
group: false,
icon: 'done', // we add an icon
spinner: false, // we reset the spinner setting so the icon can be displayed
message: 'Email marked as unread!',
timeout: 2500 // we will timeout it in 2.5s
})
},
markAsRead: async function () {
this.fileHead = await apiHandler.updateMetadata(this.selectedBucket, this.filePath, {
...this.fileHead.customMetadata,
read: true
});
this.q.notify({
group: false,
icon: 'done', // we add an icon
spinner: false, // we reset the spinner setting so the icon can be displayed
message: 'Email marked as read!',
timeout: 2500 // we will timeout it in 2.5s
})
}
},
created() {
@@ -177,7 +200,8 @@ export default defineComponent({
},
setup() {
return {
mainStore: useMainStore()
mainStore: useMainStore(),
q: useQuasar()
};
}
});
@@ -11,6 +11,7 @@
:hide-pagination="true"
:rows-per-page-options="[0]"
:flat="true"
table-class="email-list"
@row-click="rowClick">
<template v-slot:loading>
@@ -24,6 +25,28 @@
</div>
</template>
<template v-slot:body-cell="prop">
<q-td :props="prop" :class="rowClass(prop)">
{{prop.value}}
</q-td>
</template>
<template v-slot:header>
<!-- sfd-->
</template>
<template v-slot:body-cell-sender="prop">
<q-td :props="prop" class="email-sender" :class="rowClass(prop)">
{{prop.value}}
</q-td>
</template>
<template v-slot:body-cell-subject="prop">
<q-td :props="prop" class="email-subject" :class="rowClass(prop)">
{{prop.value}}
</q-td>
</template>
<template v-slot:no-data>
<div class="full-width q-my-lg" v-if="!loading">
<h6 class="flex items-center justify-center">
@@ -34,44 +57,10 @@
</template>
<template v-slot:body-cell-has_attachments="prop">
<td>
<q-td :props="prop" :class="rowClass(prop)">
<q-icon v-if="prop.row.has_attachments" name="attachment" size="sm" color="black" />
</td>
</template>
<template v-slot:body-cell-options="">
<td class="text-right">
<q-btn round flat icon="more_vert" size="sm">
<q-menu>
<q-list style="min-width: 100px">
<q-item clickable v-close-popup>
<q-item-section>New tab</q-item-section>
</q-item>
<q-item clickable v-close-popup>
<q-item-section>New incognito tab</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-close-popup>
<q-item-section>Recent tabs</q-item-section>
</q-item>
<q-item clickable v-close-popup>
<q-item-section>History</q-item-section>
</q-item>
<q-item clickable v-close-popup>
<q-item-section>Downloads</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-close-popup>
<q-item-section>Settings</q-item-section>
</q-item>
<q-separator />
<q-item clickable v-close-popup>
<q-item-section>Help &amp; Feedback</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
</td>
<q-icon v-else size="sm" color="white" />
</q-td>
</template>
</q-table>
<template v-if="!hasMorePages">
@@ -119,6 +108,13 @@ export default defineComponent({
align: "left",
sortable: false
},
{
name: "lastModified",
required: true,
align: "left",
field: "lastModified",
sortable: false
},
{
name: "has_attachments",
required: true,
@@ -126,13 +122,6 @@ export default defineComponent({
field: "has_attachments",
sortable: false
},
{
name: "lastModified",
required: true,
align: "left",
field: "lastModified",
sortable: false
}
]
};
},
@@ -150,6 +139,9 @@ export default defineComponent({
}
},
methods: {
rowClass: function (prop) {
return prop.row.customMetadata.read === 'true' ? 'email-read' : 'email-unread'
},
rowClick: function(evt, row, index) {
const file = row.key.replace(/^.*[\\/]/, "");
// const folder = row.key.replace(file, '')
@@ -238,8 +230,8 @@ export default defineComponent({
this.indexCursors = indexData.cursors.reverse()
await this.$refs.infScroll.setIndex(-1) // First page is 0
await this.$refs.infScroll.trigger()
await this.loadNextPage(0, () => {})
await this.$refs.infScroll.setIndex(0) // First page is 0
this.loadMoreAutomatically = false
@@ -286,3 +278,55 @@ export default defineComponent({
}
});
</script>
<style>
.email-read {
background-color: #f3f7f9;
color: grey;
}
.email-unread {
font-weight: 500;
}
.email-sender {
width: 200px;
overflow-x: hidden;
white-space: nowrap;
flex-shrink: 0;
text-overflow: ellipsis;
.mobile-subject {
display: none;
}
}
.email-subject {
overflow-x: hidden;
white-space: nowrap;
flex-grow: 1;
text-overflow: ellipsis;
}
.email-list table , .email-list tbody {
width: 100%;
display: block;
}
.email-list td {
vertical-align: middle !important;
}
.email-list tr {
display: flex;
width: 100%;
justify-content: center;
//width: 100%;
//display: block;
&:hover {
box-shadow: 0 2px 2px -2px gray;
z-index: 10
}
}
</style>
@@ -17,6 +17,7 @@
:rows-per-page-options="[0]"
column-sort-order="da"
:flat="true"
table-class="file-list"
@row-dblclick="openRowClick"
@row-click="openRowDlbClick"
>
@@ -265,3 +266,25 @@ export default defineComponent({
},
})
</script>
<style>
.file-list table , .file-list tbody , .file-list thead {
width: 100%;
display: block;
}
.file-list td:first-of-type, .file-list th:first-of-type {
overflow-x: hidden;
white-space: nowrap;
flex-grow: 1;
text-overflow: ellipsis;
}
.file-list tr {
display: flex;
width: 100%;
justify-content: center;
}
</style>