Feature: Send images from widget
This commit is contained in:
committed by
GitHub
parent
e56132c506
commit
6c4e1fdaac
@@ -17,10 +17,10 @@
|
||||
:message-type="messageType"
|
||||
:message="message.content"
|
||||
/>
|
||||
<div v-else class="chat-bubble has-attachment agent">
|
||||
<div v-if="hasImage" class="chat-bubble has-attachment agent">
|
||||
<image-bubble
|
||||
v-if="message.attachment && message.attachment.file_type === 'image'"
|
||||
:url="message.attachment.data_url"
|
||||
:thumb="message.attachment.thumb_url"
|
||||
:readable-time="readableTime"
|
||||
/>
|
||||
</div>
|
||||
@@ -53,9 +53,14 @@ export default {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
hasImage() {
|
||||
const { attachment = {} } = this.message;
|
||||
const { file_type: fileType } = attachment;
|
||||
return fileType === 'image';
|
||||
},
|
||||
showTextBubble() {
|
||||
const { message } = this;
|
||||
return !!message.content && !message.attachment;
|
||||
return !!message.content;
|
||||
},
|
||||
readableTime() {
|
||||
const { created_at: createdAt = '' } = this.message;
|
||||
|
||||
66
app/javascript/widget/components/ChatAttachment.vue
Executable file
66
app/javascript/widget/components/ChatAttachment.vue
Executable file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<file-upload accept="image/*" @input-file="onFileUpload">
|
||||
<span class="attachment-button ">
|
||||
<i v-if="!isUploading.image"></i>
|
||||
<spinner v-if="isUploading" size="small" />
|
||||
</span>
|
||||
</file-upload>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FileUpload from 'vue-upload-component';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
|
||||
export default {
|
||||
components: { FileUpload, Spinner },
|
||||
props: {
|
||||
onAttach: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return { isUploading: false };
|
||||
},
|
||||
methods: {
|
||||
async onFileUpload(file) {
|
||||
this.isUploading = true;
|
||||
try {
|
||||
const thumbUrl = window.URL.createObjectURL(file.file);
|
||||
await this.onAttach({
|
||||
file_type: file.type,
|
||||
file: file.file,
|
||||
thumbUrl,
|
||||
});
|
||||
} catch (error) {
|
||||
// Error
|
||||
}
|
||||
this.isUploading = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
|
||||
.attachment-button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
padding-right: $space-smaller;
|
||||
display: block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
i {
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
background: white center center no-repeat;
|
||||
background-size: contain;
|
||||
background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' fill='none' stroke='%23999a9b' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-paperclip'%3E%3Cpath d='M21 11l-9 9a6 6 0 01-8-8l9-9a4 4 0 016 5L9 17a2 2 0 01-2-2l8-9' /%3E%3C/svg%3E");
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,14 @@
|
||||
<template>
|
||||
<footer class="footer">
|
||||
<ChatInputWrap :on-send-message="onSendMessage" />
|
||||
<ChatInputWrap
|
||||
:on-send-message="handleSendMessage"
|
||||
:on-send-attachment="handleSendAttachment"
|
||||
/>
|
||||
</footer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
import ChatInputWrap from 'widget/components/ChatInputWrap.vue';
|
||||
|
||||
export default {
|
||||
@@ -16,9 +20,16 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
onSendMessage: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
methods: {
|
||||
...mapActions('conversation', ['sendMessage', 'sendAttachment']),
|
||||
handleSendMessage(content) {
|
||||
this.sendMessage({
|
||||
content,
|
||||
});
|
||||
},
|
||||
handleSendAttachment(attachment) {
|
||||
this.sendAttachment({ attachment });
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<div class="chat-message--input">
|
||||
<chat-attchment-button :on-attach="onSendAttachment" />
|
||||
<ChatInputArea v-model="userInput" :placeholder="placeholder" />
|
||||
<ChatSendButton
|
||||
:on-click="handleButtonClick"
|
||||
@@ -12,11 +13,13 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import ChatSendButton from 'widget/components/ChatSendButton.vue';
|
||||
import ChatAttchmentButton from 'widget/components/ChatAttachment.vue';
|
||||
import ChatInputArea from 'widget/components/ChatInputArea.vue';
|
||||
|
||||
export default {
|
||||
name: 'ChatInputWrap',
|
||||
components: {
|
||||
ChatAttchmentButton,
|
||||
ChatSendButton,
|
||||
ChatInputArea,
|
||||
},
|
||||
@@ -30,6 +33,10 @@ export default {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
onSendAttachment: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
<template>
|
||||
<UserMessage
|
||||
v-if="isUserMessage"
|
||||
:message="message.content"
|
||||
:status="message.status"
|
||||
/>
|
||||
<UserMessage v-if="isUserMessage" :message="message" />
|
||||
<AgentMessage v-else :message="message" />
|
||||
</template>
|
||||
|
||||
|
||||
@@ -1,23 +1,45 @@
|
||||
<template>
|
||||
<a :href="url" target="_blank" class="image message-text__wrap">
|
||||
<img :src="url" alt="Picture message" />
|
||||
<span class="time">{{ readableTime }}</span>
|
||||
<a :href="url" target="_blank" class="image">
|
||||
<div class="wrap">
|
||||
<img :src="thumb" alt="Picture message" />
|
||||
<span class="time">{{ readableTime }}</span>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: ['url', 'readableTime'],
|
||||
props: ['url', 'thumb', 'readableTime'],
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
|
||||
.image {
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
display: block;
|
||||
|
||||
.wrap {
|
||||
position: relative;
|
||||
display: flex;
|
||||
max-width: 100%;
|
||||
|
||||
&::before {
|
||||
$color-black: #000;
|
||||
background-image: linear-gradient(
|
||||
-180deg,
|
||||
transparent 3%,
|
||||
$color-black 70%
|
||||
);
|
||||
bottom: 0;
|
||||
content: '';
|
||||
height: 20%;
|
||||
left: 0;
|
||||
opacity: 0.8;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -30,21 +52,5 @@ export default {
|
||||
right: $space-small;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&::before {
|
||||
$color-black: #000;
|
||||
background-image: linear-gradient(
|
||||
-180deg,
|
||||
transparent 3%,
|
||||
$color-black 70%
|
||||
);
|
||||
bottom: 0;
|
||||
content: '';
|
||||
height: 20%;
|
||||
left: 0;
|
||||
opacity: 0.8;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,25 +1,58 @@
|
||||
<template>
|
||||
<div class="user-message">
|
||||
<div class="message-wrap">
|
||||
<UserMessageBubble :message="message" :status="status" />
|
||||
<div class="message-wrap" :class="{ 'in-progress': isInProgress }">
|
||||
<UserMessageBubble
|
||||
v-if="showTextBubble"
|
||||
:message="message.content"
|
||||
:status="message.status"
|
||||
/>
|
||||
<div v-if="hasImage" class="chat-bubble has-attachment user">
|
||||
<image-bubble
|
||||
:url="message.attachment.data_url"
|
||||
:thumb="message.attachment.thumb_url"
|
||||
:readable-time="readableTime"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import UserMessageBubble from 'widget/components/UserMessageBubble.vue';
|
||||
import UserMessageBubble from 'widget/components/UserMessageBubble';
|
||||
import ImageBubble from 'widget/components/ImageBubble';
|
||||
import timeMixin from 'dashboard/mixins/time';
|
||||
|
||||
export default {
|
||||
name: 'UserMessage',
|
||||
components: {
|
||||
UserMessageBubble,
|
||||
ImageBubble,
|
||||
},
|
||||
mixins: [timeMixin],
|
||||
props: {
|
||||
avatarUrl: String,
|
||||
message: String,
|
||||
status: {
|
||||
type: String,
|
||||
default: '',
|
||||
message: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isInProgress() {
|
||||
const { status = '' } = this.message;
|
||||
return status === 'in_progress';
|
||||
},
|
||||
hasImage() {
|
||||
const { attachment = {} } = this.message;
|
||||
const { file_type: fileType } = attachment;
|
||||
|
||||
return fileType === 'image';
|
||||
},
|
||||
showTextBubble() {
|
||||
const { message } = this;
|
||||
return !!message.content;
|
||||
},
|
||||
readableTime() {
|
||||
const { created_at: createdAt = '' } = this.message;
|
||||
return this.messageStamp(createdAt);
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -51,6 +84,15 @@ export default {
|
||||
.message-wrap {
|
||||
margin-right: $space-small;
|
||||
}
|
||||
|
||||
.in-progress {
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
|
||||
.has-attachment {
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="chat-bubble user"
|
||||
:style="{ background: backgroundColor }"
|
||||
:style="{ background: widgetColor }"
|
||||
v-html="formatMessage(message)"
|
||||
></div>
|
||||
</template>
|
||||
@@ -12,14 +12,6 @@ import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'UserMessageBubble',
|
||||
computed: {
|
||||
...mapGetters({
|
||||
widgetColor: 'appConfig/getWidgetColor',
|
||||
}),
|
||||
backgroundColor() {
|
||||
return this.status !== 'in_progress' ? this.widgetColor : '#c0ccda';
|
||||
},
|
||||
},
|
||||
mixins: [messageFormatterMixin],
|
||||
props: {
|
||||
message: String,
|
||||
@@ -27,6 +19,16 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
widgetColor: 'appConfig/getWidgetColor',
|
||||
}),
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
widgetColor: 'appConfig/getWidgetColor',
|
||||
}),
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user