feat: upload pending assets

pull/6775/head
NGPixel 11 months ago
parent dd3335c158
commit 1fb130988a
No known key found for this signature in database
GPG Key ID: B755FB6870B30F63

@ -1530,6 +1530,7 @@
"editor.pageRel.title": "Add Page Relation",
"editor.pageRel.titleEdit": "Edit Page Relation",
"editor.pageScripts.title": "Page Scripts",
"editor.pendingAssetsUploading": "Uploading assets...",
"editor.props.alias": "Alias",
"editor.props.allowComments": "Allow Comments",
"editor.props.allowCommentsHint": "Enable commenting abilities on this page.",

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="wZGpzcUawlversdxtSVKMa" x1="22.255" x2="28.545" y1="18.269" y2="43.563" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#wZGpzcUawlversdxtSVKMa)" d="M31.789,24.789l-6.728-6.728c-0.586-0.586-1.536-0.586-2.121,0l-6.728,6.728 C15.764,25.236,16.081,26,16.713,26H21v16c0,0.552,0.448,1,1,1h4c0.552,0,1-0.448,1-1V26h4.287 C31.919,26,32.236,25.236,31.789,24.789z"/><linearGradient id="wZGpzcUawlversdxtSVKMb" x1="42" x2="42" y1="4.513" y2="16.282" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#wZGpzcUawlversdxtSVKMb)" d="M43.828,9.828L39,5v10c0,0.552,0.448,1,1,1h4c0.552,0,1-0.448,1-1v-2.343 C45,11.596,44.579,10.579,43.828,9.828z"/><linearGradient id="wZGpzcUawlversdxtSVKMc" x1="6" x2="6" y1="4.513" y2="16.282" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#wZGpzcUawlversdxtSVKMc)" d="M9,5L4.172,9.828C3.421,10.579,3,11.596,3,12.657V15c0,0.552,0.448,1,1,1h4 c0.552,0,1-0.448,1-1V5z"/><linearGradient id="wZGpzcUawlversdxtSVKMd" x1="9" x2="39" y1="8" y2="8" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0362b0"/><stop offset=".112" stop-color="#036abd"/><stop offset=".258" stop-color="#036fc5"/><stop offset=".5" stop-color="#0370c8"/><stop offset=".742" stop-color="#036fc5"/><stop offset=".888" stop-color="#036abd"/><stop offset="1" stop-color="#0362b0"/></linearGradient><rect width="30" height="6" x="9" y="5" fill="url(#wZGpzcUawlversdxtSVKMd)"/></svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

@ -28,6 +28,8 @@
q-item-label From File Manager...
q-item(
clickable
@click='getAssetFromClipboard'
v-close-popup
)
q-item-section(side)
q-icon(name='las la-clipboard', color='brown')
@ -251,11 +253,10 @@
import { reactive, ref, shallowRef, nextTick, onMounted, watch, onBeforeUnmount } from 'vue'
import { useMeta, useQuasar, setCssVar } from 'quasar'
import { useI18n } from 'vue-i18n'
import { get, flatten, last, times, startsWith, debounce } from 'lodash-es'
import { find, get, last, times, startsWith, debounce } from 'lodash-es'
import { DateTime } from 'luxon'
import * as monaco from 'monaco-editor'
import { Position, Range } from 'monaco-editor'
import { v4 as uuid } from 'uuid'
import { useEditorStore } from 'src/stores/editor'
import { usePageStore } from 'src/stores/page'
@ -477,6 +478,43 @@ function openEditorSettings () {
siteStore.$patch({ overlay: 'EditorMarkdownConfig' })
}
async function getAssetFromClipboard () {
try {
const permission = await navigator.permissions.query({
name: 'clipboard-read'
})
if (permission.state === 'denied') {
throw new Error('Not allowed to read clipboard.')
}
const clipboardContents = await navigator.clipboard.read()
let hasValidItem = false
for (const item of clipboardContents) {
const imageType = find(item.types, t => t.startsWith('image/'))
if (imageType) {
hasValidItem = true
const blob = await item.getType(imageType)
const blobUrl = editorStore.addPendingAsset(blob)
insertAtCursor({
content: `![](${blobUrl})`
})
}
}
if (!hasValidItem) {
throw new Error('No supported content found in the Clipboard.')
}
} catch (err) {
return $q.notify({
type: 'negative',
message: 'Unable to copy from Clipboard',
caption: err.message
})
}
}
function reloadEditorContent () {
editor.getModel().setValue(pageStore.content)
}
// MOUNTED
onMounted(async () => {
@ -624,12 +662,7 @@ onMounted(async () => {
editor.getContainerDomNode().addEventListener('drop', ev => {
ev.preventDefault()
for (const file of ev.dataTransfer.files) {
const blobUrl = URL.createObjectURL(file)
editorStore.pendingAssets.push({
id: uuid(),
file,
blobUrl
})
const blobUrl = editorStore.addPendingAsset(file)
if (file.type.startsWith('image')) {
insertAtCursor({
content: `![${file.name}](${blobUrl})`
@ -652,6 +685,7 @@ onMounted(async () => {
EVENT_BUS.on('insertAsset', insertAssetClb)
EVENT_BUS.on('openEditorSettings', openEditorSettings)
EVENT_BUS.on('reloadEditorContent', reloadEditorContent)
// this.$root.$on('editorInsert', opts => {
// switch (opts.kind) {
@ -689,6 +723,7 @@ onMounted(async () => {
onBeforeUnmount(() => {
EVENT_BUS.off('insertAsset', insertAssetClb)
EVENT_BUS.off('openEditorSettings', openEditorSettings)
EVENT_BUS.off('reloadEditorContent', reloadEditorContent)
if (editor) {
editor.dispose()
}

@ -51,7 +51,7 @@
q-item(v-for='item of editorStore.pendingAssets')
q-item-section(side)
q-icon(name='las la-file-image')
q-item-section {{ item.file.name }}
q-item-section {{ item.fileName }}
q-item-section(side)
q-btn.acrylic-btn(
color='negative'

@ -289,6 +289,7 @@ async function saveChanges (closeAfter = false) {
}
async function saveChangesCommit (closeAfter = false) {
await processPendingAssets()
$q.loading.show()
try {
await pageStore.pageSave()
@ -315,6 +316,7 @@ async function saveChangesCommit (closeAfter = false) {
async function createPage () {
// Handle home page creation flow
if (pageStore.path === 'home') {
await processPendingAssets()
$q.loading.show()
try {
await pageStore.pageSave()
@ -347,6 +349,8 @@ async function createPage () {
itemFileName: pageStore.path
}
}).onOk(async ({ path, title }) => {
await processPendingAssets()
$q.loading.show()
try {
pageStore.$patch({
@ -372,6 +376,17 @@ async function createPage () {
})
}
async function processPendingAssets () {
if (editorStore.pendingAssets?.length > 0) {
return new Promise((resolve, reject) => {
$q.dialog({
component: defineAsyncComponent(() => import('../components/UploadPendingAssetsDialog.vue')),
persistent: true
}).onOk(resolve).onCancel(reject)
})
}
}
async function editPage () {
$q.loading.show()
await pageStore.pageEdit()

@ -0,0 +1,115 @@
<template lang="pug">
q-dialog(ref='dialogRef', @hide='onDialogHide', persistent)
q-card(style='min-width: 350px; max-width: 450px;')
q-card-section.card-header
q-icon(name='img:/_assets/icons/fluent-upload.svg', left, size='sm')
span {{t(`editor.pendingAssetsUploading`)}}
q-card-section
.q-pa-md.text-center
img(src='/_assets/illustrations/undraw_upload.svg', style='width: 150px;')
q-linear-progress(
indeterminate
size='lg'
rounded
)
.q-mt-sm.text-center.text-caption {{ state.current }} / {{ state.total }}
</template>
<script setup>
import gql from 'graphql-tag'
import { useI18n } from 'vue-i18n'
import { useDialogPluginComponent, useQuasar } from 'quasar'
import { computed, onMounted, reactive } from 'vue'
import { useEditorStore } from 'src/stores/editor'
import { useSiteStore } from 'src/stores/site'
import { usePageStore } from 'src/stores/page'
// EMITS
defineEmits([
...useDialogPluginComponent.emits
])
// QUASAR
const { dialogRef, onDialogHide, onDialogOK, onDialogCancel } = useDialogPluginComponent()
const $q = useQuasar()
// STORES
const editorStore = useEditorStore()
const pageStore = usePageStore()
const siteStore = useSiteStore()
// I18N
const { t } = useI18n()
// DATA
const state = reactive({
current: 1,
total: 1
})
// MOUNTED
onMounted(async () => {
state.total = editorStore.pendingAssets.length ?? 0
state.current = 0
await new Promise(resolve => setTimeout(resolve, 500))
try {
for (const item of editorStore.pendingAssets) {
state.current++
const resp = await APOLLO_CLIENT.mutate({
context: {
uploadMode: true
},
mutation: gql`
mutation uploadAssets (
$folderId: UUID
$locale: String
$siteId: UUID
$files: [Upload!]!
) {
uploadAssets (
folderId: $folderId
locale: $locale
siteId: $siteId
files: $files
) {
operation {
succeeded
message
}
}
}
`,
variables: {
folderId: null, // TODO: Upload to page specific folder
siteId: siteStore.id,
locale: 'en', // TODO: use current locale
files: [item.file]
}
})
if (!resp?.data?.uploadAssets?.operation?.succeeded) {
throw new Error(resp?.data?.uploadAssets?.operation?.message || 'An unexpected error occured.')
}
pageStore.content = pageStore.content.replaceAll(item.blobUrl, `/${item.fileName}`)
URL.revokeObjectURL(item.blobUrl)
}
editorStore.pendingAssets = []
EVENT_BUS.emit('reloadEditorContent')
onDialogOK()
} catch (err) {
$q.notify({
type: 'negative',
message: err.message
})
onDialogCancel()
}
})
</script>

@ -1,9 +1,19 @@
import { defineStore } from 'pinia'
import gql from 'graphql-tag'
import { clone } from 'lodash-es'
import { v4 as uuid } from 'uuid'
import { useSiteStore } from './site'
const imgMimeExt = {
'image/jpeg': 'jpg',
'image/gif': 'gif',
'image/png': 'png',
'image/webp': 'webp',
'image/svg+xml': 'svg',
'image/tiff': 'tif'
}
export const useEditorStore = defineStore('editor', {
state: () => ({
isActive: false,
@ -33,6 +43,29 @@ export const useEditorStore = defineStore('editor', {
}
},
actions: {
addPendingAsset (data) {
const blobUrl = URL.createObjectURL(data)
if (data instanceof File) {
this.pendingAssets.push({
id: uuid(),
kind: 'file',
file: data,
fileName: data.name,
blobUrl
})
} else {
const fileId = uuid()
const fileName = `${fileId}.${imgMimeExt[data.type] || 'dat'}`
this.pendingAssets.push({
id: fileId,
kind: 'blob',
file: new File(data, fileName, { type: data.type }),
fileName,
blobUrl
})
}
return blobUrl
},
async fetchConfigs () {
const siteStore = useSiteStore()
try {

Loading…
Cancel
Save