Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/text.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVinicius Reis <vinicius.reis@nextcloud.com>2022-06-01 19:40:21 +0300
committerJulius Härtl <jus@bitgrid.net>2022-06-09 11:35:37 +0300
commitf7451b505062a2ebf750ac421f6fa159f2ec8a6a (patch)
treed43b4afd1548bf334ac1326474284f4f86d9e013 /src
parent1db24932381f62971edbe4ea88f84d82e0b9aedd (diff)
🩹 (#2463): show a falback image when image can't be loaded
Signed-off-by: Vinicius Reis <vinicius.reis@nextcloud.com>
Diffstat (limited to 'src')
-rw-r--r--src/components/icons.js6
-rw-r--r--src/nodes/ImageView.vue92
2 files changed, 75 insertions, 23 deletions
diff --git a/src/components/icons.js b/src/components/icons.js
index feec62da1..926a4d477 100644
--- a/src/components/icons.js
+++ b/src/components/icons.js
@@ -21,7 +21,6 @@
*
*/
-import MDI_Loading from 'vue-material-design-icons/Loading.vue'
import MDI_Check from 'vue-material-design-icons/Check.vue'
import MDI_CodeTags from 'vue-material-design-icons/CodeTags.vue'
import MDI_Danger from 'vue-material-design-icons/AlertDecagram.vue'
@@ -45,9 +44,11 @@ import MDI_FormatQuote from 'vue-material-design-icons/FormatQuoteClose.vue'
import MDI_FormatStrikethrough from 'vue-material-design-icons/FormatStrikethrough.vue'
import MDI_FormatUnderline from 'vue-material-design-icons/FormatUnderline.vue'
import MDI_Help from 'vue-material-design-icons/HelpCircle.vue'
+import MDI_Image from 'vue-material-design-icons/ImageOutline.vue'
import MDI_Images from 'vue-material-design-icons/ImageMultipleOutline.vue'
import MDI_Info from 'vue-material-design-icons/Information.vue'
import MDI_Link from 'vue-material-design-icons/Link.vue'
+import MDI_Loading from 'vue-material-design-icons/Loading.vue'
import MDI_Lock from 'vue-material-design-icons/Lock.vue'
import MDI_Positive from 'vue-material-design-icons/CheckboxMarkedCircle.vue'
import MDI_Redo from 'vue-material-design-icons/ArrowURightTop.vue'
@@ -57,6 +58,7 @@ import MDI_TableAddColumnBefore from 'vue-material-design-icons/TableColumnPlusB
import MDI_TableAddRowAfter from 'vue-material-design-icons/TableRowPlusAfter.vue'
import MDI_TableAddRowBefore from 'vue-material-design-icons/TableRowPlusBefore.vue'
import MDI_TableSettings from 'vue-material-design-icons/TableCog.vue'
+import MDI_TrashCan from 'vue-material-design-icons/TrashCan.vue'
import MDI_Undo from 'vue-material-design-icons/ArrowULeftTop.vue'
import MDI_Upload from 'vue-material-design-icons/Upload.vue'
import MDI_Warn from 'vue-material-design-icons/Alert.vue'
@@ -109,6 +111,7 @@ export const FormatQuote = makeIcon(MDI_FormatQuote)
export const FormatStrikethrough = makeIcon(MDI_FormatStrikethrough)
export const FormatUnderline = makeIcon(MDI_FormatUnderline)
export const Help = makeIcon(MDI_Help)
+export const Image = makeIcon(MDI_Image)
export const Images = makeIcon(MDI_Images)
export const Info = makeIcon(MDI_Info)
export const Link = makeIcon(MDI_Link)
@@ -121,6 +124,7 @@ export const TableAddColumnBefore = makeIcon(MDI_TableAddColumnBefore)
export const TableAddRowAfter = makeIcon(MDI_TableAddRowAfter)
export const TableAddRowBefore = makeIcon(MDI_TableAddRowBefore)
export const TableSettings = makeIcon(MDI_TableSettings)
+export const TrashCan = makeIcon(MDI_TrashCan)
export const Undo = makeIcon(MDI_Undo)
export const Upload = makeIcon(MDI_Upload)
export const Warn = makeIcon(MDI_Warn)
diff --git a/src/nodes/ImageView.vue b/src/nodes/ImageView.vue
index 8466d7760..5d65f0106 100644
--- a/src/nodes/ImageView.vue
+++ b/src/nodes/ImageView.vue
@@ -22,38 +22,47 @@
<template>
<NodeViewWrapper>
- <div class="image"
+ <div class="image image-view"
data-component="image-view"
- :class="{'icon-loading': !loaded}"
+ :class="{'icon-loading': !loaded, 'image-view--failed': failed}"
:data-src="src">
- <div v-if="imageLoaded && isSupportedImage"
+ <small v-if="errorMessage" class="image__error-message">
+ {{ errorMessage }}
+ </small>
+ <div v-if="canDisplayImage"
v-click-outside="() => showIcons = false"
class="image__view"
@click="showIcons = true"
@mouseover="showIcons = true"
@mouseleave="showIcons = false">
<transition name="fade">
- <img v-show="loaded"
- :src="imageUrl"
- class="image__main"
- @load="onLoaded">
+ <template v-if="!failed">
+ <img v-show="loaded"
+ :src="imageUrl"
+ class="image__main"
+ @load="onLoaded">
+ </template>
+ <template v-else>
+ <ImageBroken class="image__main image__main--broken-icon" :size="100" />
+ </template>
</transition>
<transition name="fade">
<div v-show="loaded" class="image__caption">
<input ref="altInput"
type="text"
+ class="image__caption__input"
:value="alt"
@keyup.enter="updateAlt()">
<div v-if="editor.isEditable && showIcons"
- class="trash-icon"
+ class="image__caption__delete"
title="Delete this image"
@click="deleteNode">
- <TrashCanIcon />
+ <TrashCan />
</div>
</div>
</transition>
</div>
- <div v-else>
+ <div v-else class="image-view__cant_display">
<transition name="fade">
<div v-show="loaded">
<a :href="internalLinkOrImage" target="_blank">
@@ -78,7 +87,8 @@
import { generateUrl } from '@nextcloud/router'
import { NodeViewWrapper } from '@tiptap/vue-2'
import ClickOutside from 'vue-click-outside'
-import TrashCanIcon from 'vue-material-design-icons/TrashCan.vue'
+// import TrashCanIcon from 'vue-material-design-icons/TrashCan.vue'
+import { ImageBroken, TrashCan } from '../components/icons.js'
import store from './../mixins/store.js'
import { useImageResolver } from './../components/EditorWrapper.provider.js'
@@ -111,10 +121,21 @@ const getQueryVariable = (src, variable) => {
}
}
+class ErrorLoadImage extends Error {
+
+ constructor(reason, imageUrl) {
+ super(reason?.message || t('text', 'Fail to load image'))
+ this.reason = reason
+ this.imageUrl = imageUrl
+ }
+
+}
+
export default {
name: 'ImageView',
components: {
- TrashCanIcon,
+ ImageBroken,
+ TrashCan,
NodeViewWrapper,
},
directives: {
@@ -132,9 +153,21 @@ export default {
failed: false,
showIcons: false,
imageUrl: null,
+ errorMessage: null,
}
},
computed: {
+ canDisplayImage() {
+ if (!this.isSupportedImage) {
+ return false
+ }
+
+ if (this.failed && this.loaded) {
+ return true
+ }
+
+ return this.loaded && this.imageLoaded
+ },
imageFileId() {
return getQueryVariable(this.src, 'fileId')
},
@@ -183,20 +216,22 @@ export default {
this.failed = true
this.imageLoaded = false
this.loaded = true
+ this.errorMessage = t('text', 'Unsuported image')
return
}
- this.init().catch((e) => {
- this.onImageLoadFailure()
- })
+ this.init()
+ .catch(this.onImageLoadFailure)
},
methods: {
async init() {
const [url, fallback] = this.$imageResolver.resolve(this.src)
- this.loadImage(url).catch((e) => {
+ return this.loadImage(url).catch((e) => {
if (fallback) {
- this.loadImage(fallback)
+ return this.loadImage(fallback)
// TODO if fallback works, rewrite the url with correct document ID
}
+
+ return Promise.reject(e)
})
},
@@ -206,18 +241,20 @@ export default {
img.onload = () => {
this.imageUrl = imageUrl
this.imageLoaded = true
- resolve()
+ this.loaded = true
+ resolve(imageUrl)
}
img.onerror = (e) => {
- reject(e)
+ reject(new ErrorLoadImage(e, imageUrl))
}
img.src = imageUrl
})
},
- onImageLoadFailure() {
+ onImageLoadFailure(err) {
this.failed = true
this.imageLoaded = false
this.loaded = true
+ this.errorMessage = err.message
},
updateAlt() {
this.alt = this.$refs.altInput.value
@@ -256,6 +293,10 @@ export default {
height: 100px;
}
+ .image__main--broken-icon, .image__error-message {
+ color: var(--color-error);
+ }
+
.image__view {
text-align: center;
position: relative;
@@ -269,6 +310,11 @@ export default {
max-height: calc(100vh - 50px - 50px);
}
+ .image__error-message {
+ display: block;
+ text-align: center;
+ }
+
.fade-enter-active {
transition: opacity .3s ease-in-out;
}
@@ -281,13 +327,15 @@ export default {
opacity: 0;
}
- .trash-icon {
+ .image__caption__delete {
position: absolute;
right: 0;
display: flex;
justify-content: flex-end;
align-items: center;
- svg {
+ width: 20px;
+ height: 20px;
+ &, svg {
cursor: pointer;
}
}