feat: 新增聊天表情

This commit is contained in:
kuaifan 2022-04-17 18:31:45 +08:00
parent f5b663b900
commit 8cbc3a9667
151 changed files with 11330 additions and 75 deletions

View File

@ -206,17 +206,28 @@ class WebSocketDialogMsg extends AbstractModel
*/ */
public static function formatMsg($text, $dialog_id) public static function formatMsg($text, $dialog_id)
{ {
// 图片 // 图片 [:IMAGE:className:width:height:src:alt:]
preg_match_all("/<img\s*src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"(.*?)>(<\/img>)*/s", $text, $matchs); preg_match_all("/<img\s*src=\"data:image\/(png|jpg|jpeg);base64,(.*?)\"(.*?)>(<\/img>)*/s", $text, $matchs);
foreach ($matchs[2] as $key => $base64) { foreach ($matchs[2] as $key => $base64) {
$tmpPath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/"; $tmpPath = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
Base::makeDir(public_path($tmpPath)); Base::makeDir(public_path($tmpPath));
$tmpPath .= md5s($base64) . "." . $matchs[1][$key]; $tmpPath .= md5s($base64) . "." . $matchs[1][$key];
if (file_put_contents(public_path($tmpPath), base64_decode($base64))) { if (file_put_contents(public_path($tmpPath), base64_decode($base64))) {
$text = str_replace($matchs[0][$key], "[:IMG:{$tmpPath}:]", $text); $imagesize = getimagesize(public_path($tmpPath));
$text = str_replace($matchs[0][$key], "[:IMAGE:browse:{$imagesize[0]}:{$imagesize[1]}:{$tmpPath}::]", $text);
} }
} }
// @成员 #任务 // 表情图片
preg_match_all("/<img class=\"emoticon\"(.*?)>/s", $text, $matchs);
foreach ($matchs[1] as $key => $str) {
preg_match("/data-asset=\"(.*?)\"/", $str, $matchAsset);
preg_match("/data-name=\"(.*?)\"/", $str, $matchName);
if (file_exists(public_path($matchAsset[1]))) {
$imagesize = getimagesize(public_path($matchAsset[1]));
$text = str_replace($matchs[0][$key], "[:IMAGE:emoticon:{$imagesize[0]}:{$imagesize[1]}:{$matchAsset[1]}:{$matchName[1]}:]", $text);
}
}
// @成员、#任务
preg_match_all("/<span class=\"mention\"(.*?)>.*?<\/span>.*?<\/span>.*?<\/span>/s", $text, $matchs); preg_match_all("/<span class=\"mention\"(.*?)>.*?<\/span>.*?<\/span>.*?<\/span>/s", $text, $matchs);
foreach ($matchs[1] as $key => $str) { foreach ($matchs[1] as $key => $str) {
preg_match("/data-denotation-char=\"(.*?)\"/", $str, $matchChar); preg_match("/data-denotation-char=\"(.*?)\"/", $str, $matchChar);
@ -227,7 +238,7 @@ class WebSocketDialogMsg extends AbstractModel
// 过滤标签 // 过滤标签
$text = strip_tags($text, '<p>'); $text = strip_tags($text, '<p>');
$text = preg_replace("/\<p.*?\>/i", "<p>", $text); $text = preg_replace("/\<p.*?\>/i", "<p>", $text);
$text = preg_replace("/\[:IMG:(.*?):\]/i", "<img src=\"{{RemoteURL}}$1\"/>", $text); $text = preg_replace("/\[:IMAGE:(.*?):(.*?):(.*?):(.*?):(.*?):\]/i", "<img class=\"$1\" width=\"$2\" height=\"$3\" src=\"{{RemoteURL}}$4\" alt=\"$5\"/>", $text);
$text = preg_replace("/\[:@:(.*?):(.*?):\]/i", "<span class=\"mention user\" data-id=\"$1\">@$2</span>", $text); $text = preg_replace("/\[:@:(.*?):(.*?):\]/i", "<span class=\"mention user\" data-id=\"$1\">@$2</span>", $text);
return preg_replace("/\[:#:(.*?):(.*?):\]/i", "<span class=\"mention task\" data-id=\"$1\">#$2</span>", $text); return preg_replace("/\[:#:(.*?):(.*?):\]/i", "<span class=\"mention task\" data-id=\"$1\">#$2</span>", $text);
} }

View File

@ -69,7 +69,6 @@
"view-design-hi": "^4.7.0-20", "view-design-hi": "^4.7.0-20",
"vue": "^2.6.14", "vue": "^2.6.14",
"vue-clipboard2": "^0.3.3", "vue-clipboard2": "^0.3.3",
"vue-emoji-picker": "^1.0.3",
"vue-kityminder-ggg": "^1.3.10", "vue-kityminder-ggg": "^1.3.10",
"vue-loader": "^15.9.8", "vue-loader": "^15.9.8",
"vue-resize-observer": "^2.0.16", "vue-resize-observer": "^2.0.16",

View File

@ -375,6 +375,19 @@
*/ */
getDialogMention(dialog) { getDialogMention(dialog) {
return dialog ? (dialog.mention || 0) : 0 return dialog ? (dialog.mention || 0) : 0
},
/**
* 返回文本信息预览格式
* @param text
* @returns {*}
*/
getMsgTextPreview(text) {
if (!text) return '';
text = text.replace(/<img\s+class="emoticon"[^>]*?alt="(\S+)"[^>]*?>/g, "[$1]")
text = text.replace(/<img\s+class="emoticon"[^>]*?>/g, `[${$A.L('表情')}]`)
text = text.replace(/<img\s+class="browse"[^>]*?>/g, `[${$A.L('图片')}]`)
return text.replace(/<[^>]+>/g,"")
} }
}); });

View File

@ -898,9 +898,7 @@ export default {
let body = ''; let body = '';
switch (type) { switch (type) {
case 'text': case 'text':
body = msg.text; body = $A.getMsgTextPreview(msg.text)
body = body.replace(/<img src=".*?"\/>/g, `[${this.$L('图片')}]`)
body = body.replace(/<[^>]+>/g,"")
break; break;
case 'file': case 'file':
body = '[' + this.$L(msg.type == 'img' ? '图片信息' : '文件信息') + ']' body = '[' + this.$L(msg.type == 'img' ? '图片信息' : '文件信息') + ']'

View File

@ -0,0 +1,87 @@
<template>
<div class="chat-emoji-wrapper">
<ul class="chat-emoji-box overlay-y" :class="type">
<li v-for="item in list" @click="onSelect(item)">
<img v-if="item.type === 'emoticon'" :src="item.src" :title="item.name" :alt="item.name"/>
<span v-else v-html="item.html" :title="item.name"></span>
</li>
</ul>
<ul class="chat-emoji-menu">
<li :class="{active: type === 'emoji'}" @click="type='emoji'">
<span>&#128512;</span>
</li>
<li v-for="item in emoticonList" :class="{active: type === 'emoticon' && emoticonPath == item.path}" @click="onEmoticon(item.path)">
<img :title="item.name" :alt="item.name" :src="item.src"/>
</li>
</ul>
</div>
</template>
<script>
export default {
name: 'ChatEmoji',
props: {
},
data() {
return {
type: 'emoji',
emoticonPath: '',
};
},
mounted() {
},
computed: {
list() {
if (this.type === 'emoji') {
if (!$A.isArray(window.emojiData)) {
return [];
}
return window.emojiData.sort(function (a, b) {
return a.emoji_order - b.emoji_order;
}).map(item => {
return {
type: 'emoji',
name: item.name,
html: item.code_decimal,
}
})
} else if (this.type === 'emoticon') {
const data = this.emoticonList.find(({path}) => path === this.emoticonPath)
if (data) {
return data.list;
}
}
return [];
},
emoticonList() {
if ($A.isArray(window.emoticonData)) {
let baseUrl = $A.apiUrl("../images/emoticon")
return window.emoticonData.map(data => {
data.src = `${baseUrl}/${data.path}/${data.icon}`
data.list = data.list.map(item => {
item.type = `emoticon`
item.asset = `images/emoticon/${data.path}/${item.path}`
item.src = `${baseUrl}/${data.path}/${item.path}`
return item
})
return data;
});
}
return [];
}
},
methods: {
onEmoticon(path) {
this.type = 'emoticon';
this.emoticonPath = path;
},
onSelect(item) {
this.$emit('on-select', item)
}
}
}
</script>

View File

@ -4,26 +4,39 @@
<div class="chat-input-toolbar"> <div class="chat-input-toolbar">
<slot name="toolbarBefore"/> <slot name="toolbarBefore"/>
<ETooltip placement="top" :content="$L('表情')"><i class="taskfont" @click="onToolbar('emoji')">&#xe7ad;</i></ETooltip> <EPopover
<ETooltip placement="top" :content="$L('选择会员')"><i class="taskfont" @click="onToolbar('user')">&#xe78f;</i></ETooltip> v-model="showEmoji"
<ETooltip placement="top" :content="$L('选择任务')"><i class="taskfont" @click="onToolbar('task')">&#xe7d6;</i></ETooltip> :visibleArrow="false"
popperClass="chat-input-emoji-popover">
<ETooltip slot="reference" :disabled="showEmoji" placement="top" :content="$L('表情')">
<i class="taskfont" @click="onToolbar('emoji')">&#xe7ad;</i>
</ETooltip>
<ChatEmoji @on-select="onSelectEmoji"/>
</EPopover>
<EDropdown <ETooltip placement="top" :content="$L('选择会员')">
trigger="hover" <i class="taskfont" @click="onToolbar('user')">&#xe78f;</i>
placement="top" </ETooltip>
@command="onToolbar"> <ETooltip placement="top" :content="$L('选择任务')">
<i class="taskfont" @click="onToolbar('task')">&#xe7d6;</i>
</ETooltip>
<EPopover
v-model="showMore"
:visibleArrow="false"
popperClass="chat-input-more-popover">
<ETooltip slot="reference" :disabled="showMore" placement="top" :content="$L('展开')">
<i class="taskfont">&#xe790;</i> <i class="taskfont">&#xe790;</i>
<EDropdownMenu slot="dropdown" class="chat-input-dropdown-menu"> </ETooltip>
<EDropdownItem command="image"> <div class="chat-input-popover-item" @click="onToolbar('image')">
<i class="taskfont">&#xe64a;</i> <i class="taskfont">&#xe64a;</i>
{{$L('图片')}} {{$L('图片')}}
</EDropdownItem> </div>
<EDropdownItem command="file"> <div class="chat-input-popover-item" @click="onToolbar('file')">
<i class="taskfont">&#xe786;</i> <i class="taskfont">&#xe786;</i>
{{$L('文件')}} {{$L('文件')}}
</EDropdownItem> </div>
</EDropdownMenu> </EPopover>
</EDropdown>
<div class="toolbar-spacing"></div> <div class="toolbar-spacing"></div>
@ -40,9 +53,11 @@ import {mapGetters, mapState} from "vuex";
import Quill from 'quill'; import Quill from 'quill';
import "quill-mention"; import "quill-mention";
import ChatEmoji from "./emoji";
export default { export default {
name: 'ChatInput', name: 'ChatInput',
components: {ChatEmoji},
props: { props: {
dialogId: { dialogId: {
type: Number, type: Number,
@ -94,6 +109,9 @@ export default {
userList: null, userList: null,
taskList: null, taskList: null,
showMore: false,
showEmoji: false,
}; };
}, },
mounted() { mounted() {
@ -266,7 +284,22 @@ export default {
}, },
send() { send() {
this.$emit('on-send', this.quill) this.$emit('on-send')
},
onSelectEmoji(item) {
if (!this.quill) {
return;
}
if (item.type === 'emoji') {
let element = document.createElement('span');
element.innerHTML = item.html;
this.quill.insertText(this.quill.getSelection(true).index, element.innerHTML);
element = null;
} else if (item.type === 'emoticon') {
this.$emit('on-send', `<img class="emoticon" data-asset="${item.asset}" data-name="${item.name}" src="${item.src}"/>`)
}
this.showEmoji = false;
}, },
onToolbar(action) { onToolbar(action) {
@ -286,6 +319,10 @@ export default {
} }
}, },
onMoreVisibleChange(v) {
this.showMore = v;
},
openMenu(char) { openMenu(char) {
if (!this.quill) { if (!this.quill) {
return; return;
@ -302,6 +339,26 @@ export default {
} }
}, },
getProjectId() {
let object = null;
if (this.dialogId > 0) {
object = this.cacheProjects.find(({dialog_id}) => dialog_id == this.dialogId);
if (object) {
return object.id;
}
object = this.cacheTasks.find(({dialog_id}) => dialog_id == this.dialogId);
if (object) {
return object.project_id;
}
} else if (this.taskId > 0) {
object = this.cacheTasks.find(({id}) => id == this.taskId);
if (object) {
return object.project_id;
}
}
return 0;
},
getSource(mentionChar) { getSource(mentionChar) {
return new Promise(resolve => { return new Promise(resolve => {
switch (mentionChar) { switch (mentionChar) {
@ -448,26 +505,6 @@ export default {
} }
}) })
}, },
getProjectId() {
let object = null;
if (this.dialogId > 0) {
object = this.cacheProjects.find(({dialog_id}) => dialog_id == this.dialogId);
if (object) {
return object.id;
}
object = this.cacheTasks.find(({dialog_id}) => dialog_id == this.dialogId);
if (object) {
return object.project_id;
}
} else if (this.taskId > 0) {
object = this.cacheTasks.find(({id}) => id == this.taskId);
if (object) {
return object.project_id;
}
}
return 0;
}
} }
} }
</script> </script>

View File

@ -225,11 +225,22 @@ export default {
}, },
viewText({target}) { viewText({target}) {
if (target.nodeName === "IMG") { switch (target.nodeName) {
case "IMG":
if (target.classList.contains('browse')) {
this.viewPicture(target.currentSrc); this.viewPicture(target.currentSrc);
} else if (target.classList.contains('mention') && target.classList.contains('task')) { } else {
this.$store.state.previewImageIndex = 0;
this.$store.state.previewImageList = [target.currentSrc];
}
break;
case "SPAN":
if (target.classList.contains('mention') && target.classList.contains('task')) {
this.$store.dispatch("openTask", $A.runNum(target.getAttribute("data-id"))); this.$store.dispatch("openTask", $A.runNum(target.getAttribute("data-id")));
} }
break;
}
}, },
viewFile() { viewFile() {
@ -264,7 +275,7 @@ export default {
if (item.type === 'file') { if (item.type === 'file') {
return ['jpg', 'jpeg', 'gif', 'png'].includes(item.msg.ext); return ['jpg', 'jpeg', 'gif', 'png'].includes(item.msg.ext);
} else if (item.type === 'text') { } else if (item.type === 'text') {
return item.msg.text.match(/<img src="(.*?)"\/>/); return item.msg.text.match(/<img\s+class="browse"[^>]*?>/);
} }
} }
return false; return false;
@ -277,9 +288,10 @@ export default {
if (type === 'file') { if (type === 'file') {
list.push(msg.path) list.push(msg.path)
} else if (type === 'text') { } else if (type === 'text') {
const array = msg.text.match(/<img src="(.*?)"\/>/g); const baseUrl = $A.apiUrl('../');
const array = msg.text.match(/<img\s+class="browse"[^>]*?src="(.*?)"[^>]*?>/g);
array.some(res => { array.some(res => {
list.push(res.match(/<img src="(.*?)"\/>/)[1].replace(/\{\{RemoteURL\}\}/g, $A.apiUrl('../'))) list.push(res.match(/<img\s+class="browse"[^>]*?src="(.*?)"[^>]*?>/)[1].replace(/\{\{RemoteURL\}\}/g, baseUrl))
}) })
} }
}) })

View File

@ -317,14 +317,18 @@ export default {
methods: { methods: {
sendMsg(text) { sendMsg(text) {
let msgText;
if (typeof text === "string" && text) { if (typeof text === "string" && text) {
this.msgText = text; msgText = text;
this.$refs.input.focus(); } else {
msgText = this.msgText;
this.msgText = '';
} }
if (this.msgText == '') { if (msgText == '' && this.isDesktop) {
this.$refs.input.focus();
return; return;
} }
this.msgText = this.msgText.replace(/<\/span> <\/p>$/, "</span></p>") msgText = msgText.replace(/<\/span> <\/p>$/, "</span></p>")
// //
let tempId = $A.randomString(16); let tempId = $A.randomString(16);
this.tempMsgs.push({ this.tempMsgs.push({
@ -333,7 +337,7 @@ export default {
type: 'text', type: 'text',
userid: this.userId, userid: this.userId,
msg: { msg: {
text: this.msgText, text: msgText,
}, },
}); });
if (!this.isDesktop) { if (!this.isDesktop) {
@ -346,7 +350,7 @@ export default {
url: 'dialog/msg/sendtext', url: 'dialog/msg/sendtext',
data: { data: {
dialog_id: this.dialogId, dialog_id: this.dialogId,
text: this.msgText, text: msgText,
}, },
method: 'post' method: 'post'
}).then(({data}) => { }).then(({data}) => {
@ -356,8 +360,6 @@ export default {
$A.modalError(msg); $A.modalError(msg);
this.tempMsgs = this.tempMsgs.filter(({id}) => id != tempId) this.tempMsgs = this.tempMsgs.filter(({id}) => id != tempId)
}); });
//
this.msgText = '';
}, },
sendFileMsg(files) { sendFileMsg(files) {

View File

@ -404,10 +404,7 @@ export default {
if ($A.isJson(data)) { if ($A.isJson(data)) {
switch (data.type) { switch (data.type) {
case 'text': case 'text':
let text = data.msg.text; return $A.getMsgTextPreview(data.msg.text)
text = text.replace(/<img src=".*?"\/>/g, `[${this.$L('图片')}]`)
text = text.replace(/<[^>]+>/g,"")
return text
case 'file': case 'file':
if (data.msg.type == 'img') { if (data.msg.type == 'img') {
return `[${this.$L('图片')}]` return `[${this.$L('图片')}]`

View File

@ -30,6 +30,7 @@
float: left; float: left;
max-width: 100%; max-width: 100%;
min-width: calc(100% - 175px); min-width: calc(100% - 175px);
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
.ql-editor { .ql-editor {
padding: 4px 7px; padding: 4px 7px;
@ -203,9 +204,11 @@
background-color: #cccccc; background-color: #cccccc;
} }
> i { > i {
display: inline-block;
cursor: pointer; cursor: pointer;
padding: 0 5px; padding: 0 5px;
font-size: 20px; font-size: 20px;
line-height: 28px;
&.disabled { &.disabled {
opacity: 0.5; opacity: 0.5;
} }
@ -213,22 +216,118 @@
margin-right: -5px; margin-right: -5px;
} }
} }
.el-dropdown { .el-tooltip.taskfont {
padding: 0 5px; display: inline-block;
> i {
cursor: pointer; cursor: pointer;
padding: 0 5px;
font-size: 20px; font-size: 20px;
line-height: 28px;
} }
} }
} }
.chat-input-emoji-popover {
padding: 0;
overflow: hidden;
} }
.chat-input-dropdown-menu {
> li { .chat-input-more-popover {
min-width: 100px;
padding: 8px;
.chat-input-popover-item {
display: flex; display: flex;
align-items: center; align-items: center;
font-size: 14px;
cursor: pointer;
line-height: 36px;
padding: 0 8px;
border-radius: 4px;
&:hover {
background-color: #ecf5ff;
}
> i { > i {
font-size: 20px; font-size: 20px;
margin-right: 8px; margin-right: 8px;
} }
} }
} }
.chat-emoji-wrapper {
display: flex;
flex-direction: column;
.chat-emoji-box {
width: 360px;
height: 280px;
padding: 8px;
overflow-x: hidden;
word-break: break-all;
box-sizing: content-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
> li {
width: 40px;
height: 40px;
line-height: 40px;
font-size: 22px;
text-align: center;
display: inline-block;
cursor: pointer;
user-select: none;
transition: transform 0.3s;
> img {
max-width: 100%;
max-height: 100%;
}
&:hover {
transform: scale(1.4);
}
}
&.emoticon {
> li {
width: 72px;
height: 72px;
padding: 8px;
}
}
}
.chat-emoji-menu {
width: 376px;
height: 40px;
line-height: 40px;
background-color: #f2f4f7;
display: flex;
align-items: center;
overflow: auto;
&::-webkit-scrollbar {
display: none;
}
> li {
list-style: none;
cursor: pointer;
display: flex;
align-items: center;
height: 100%;
&.active {
background-color: #fff;
}
> span {
padding: 0 13px;
font-size: 22px;
}
> img {
width: 24px;
height: 24px;
padding: 0 12px;
box-sizing: content-box;
}
}
}
}

View File

@ -40,6 +40,7 @@
opacity: .2; opacity: .2;
z-index: 1; z-index: 1;
} }
.dialog-title { .dialog-title {
padding-right: 52px; padding-right: 52px;
} }
@ -48,6 +49,7 @@
.dialog-avatar { .dialog-avatar {
flex-shrink: 0; flex-shrink: 0;
margin-right: 12px; margin-right: 12px;
.user-avatar, .user-avatar,
.icon-avatar { .icon-avatar {
width: 42px; width: 42px;
@ -56,6 +58,7 @@
flex-grow: 0; flex-grow: 0;
flex-shrink: 0; flex-shrink: 0;
} }
.icon-avatar { .icon-avatar {
display: flex; display: flex;
align-items: center; align-items: center;
@ -64,9 +67,11 @@
font-size: 26px; font-size: 26px;
background-color: #61B2F9; background-color: #61B2F9;
color: #ffffff; color: #ffffff;
&.project { &.project {
background-color: #6E99EB; background-color: #6E99EB;
} }
&.task { &.task {
background-color: #9B96DF; background-color: #9B96DF;
font-size: 24px; font-size: 24px;
@ -226,7 +231,6 @@
align-items: flex-start; align-items: flex-start;
.content-text { .content-text {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
color: #333333; color: #333333;
> pre { > pre {
@ -237,19 +241,30 @@
white-space: pre-wrap; white-space: pre-wrap;
word-wrap: break-word; word-wrap: break-word;
word-break: break-word; word-break: break-word;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
img { img {
cursor: pointer; cursor: pointer;
max-width: 220px; max-width: 220px;
max-height: 220px; max-height: 220px;
vertical-align: bottom;
&.emoticon {
max-width: 150px;
max-height: 150px;
} }
}
.mention { .mention {
color: $flow-status-end-color; color: $flow-status-end-color;
background-color: transparent; background-color: transparent;
user-select: auto; user-select: auto;
margin-right: 0; margin-right: 0;
> span { > span {
margin: 0; margin: 0;
} }
&.task { &.task {
cursor: pointer; cursor: pointer;
} }
@ -553,6 +568,7 @@
width: 100%; width: 100%;
display: flex; display: flex;
align-items: center; align-items: center;
.chat-input-wrapper { .chat-input-wrapper {
flex: 1; flex: 1;
width: 0; width: 0;

View File

@ -178,6 +178,8 @@
line-height: 24px; line-height: 24px;
display: flex; display: flex;
align-items: center; align-items: center;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
.common-avatar, .common-avatar,
.last-self { .last-self {
flex-shrink: 0; flex-shrink: 0;

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 285 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 324 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 293 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 375 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 415 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 181 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Some files were not shown because too many files have changed in this diff Show More