Optionally scale images when sending

Default is "scale". Currently scales image so that longest side is 640px.
This commit is contained in:
N-Pex 2021-05-11 21:03:54 +02:00
parent 91dfb0bc8e
commit 5276a46afa
4 changed files with 331 additions and 83 deletions

97
package-lock.json generated
View file

@ -1,18 +1,21 @@
{ {
"name": "keanuapp-weblite", "name": "keanuapp-weblite",
"version": "0.1.4", "version": "0.1.5",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"version": "0.1.4", "version": "0.1.5",
"dependencies": { "dependencies": {
"aes-js": "^3.1.2", "aes-js": "^3.1.2",
"axios": "^0.21.0", "axios": "^0.21.0",
"clean-insights-sdk": "^2.4.1", "clean-insights-sdk": "^2.4.1",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"data-uri-to-buffer": "^3.0.1",
"dayjs": "^1.10.3", "dayjs": "^1.10.3",
"fix-webm-duration": "^1.0.0", "fix-webm-duration": "^1.0.0",
"image-resize": "^1.1.5",
"image-size": "^1.0.0",
"intersection-observer": "^0.11.0", "intersection-observer": "^0.11.0",
"js-sha256": "^0.9.0", "js-sha256": "^0.9.0",
"json-web-key": "^0.4.0", "json-web-key": "^0.4.0",
@ -21,6 +24,7 @@
"matrix-js-sdk": "^9.4.1", "matrix-js-sdk": "^9.4.1",
"md-gum-polyfill": "^1.0.0", "md-gum-polyfill": "^1.0.0",
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz", "olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"pretty-bytes": "^5.6.0",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"recordrtc": "^5.6.2", "recordrtc": "^5.6.2",
@ -4820,6 +4824,14 @@
"node": ">=0.10" "node": ">=0.10"
} }
}, },
"node_modules/data-uri-to-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
"integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==",
"engines": {
"node": ">= 6"
}
},
"node_modules/dayjs": { "node_modules/dayjs": {
"version": "1.10.4", "version": "1.10.4",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz",
@ -7319,6 +7331,28 @@
"node": ">= 4" "node": ">= 4"
} }
}, },
"node_modules/image-resize": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/image-resize/-/image-resize-1.1.5.tgz",
"integrity": "sha512-oLSSXVzAmjhwSe36cGNmaSAid8xEXADlS6k+hXqXij3SMPhf9zUI6g3dHuNDV9nVmaD4jja36+k5PWmOiuI1wg==",
"dependencies": {
"jquery": "^3.4.1"
}
},
"node_modules/image-size": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.0.tgz",
"integrity": "sha512-JLJ6OwBfO1KcA+TvJT+v8gbE6iWbj24LyDNFgFEN0lzegn6cC6a/p3NIDaepMsJjQjlUWqIC7wJv8lBFxPNjcw==",
"dependencies": {
"queue": "6.0.2"
},
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/import-cwd": { "node_modules/import-cwd": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
@ -8167,8 +8201,7 @@
"node_modules/jquery": { "node_modules/jquery": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==", "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw=="
"peer": true
}, },
"node_modules/js-message": { "node_modules/js-message": {
"version": "1.0.7", "version": "1.0.7",
@ -10782,6 +10815,17 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
"integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pretty-error": { "node_modules/pretty-error": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz",
@ -11093,6 +11137,14 @@
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
"dev": true "dev": true
}, },
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"dependencies": {
"inherits": "~2.0.3"
}
},
"node_modules/randombytes": { "node_modules/randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -19479,6 +19531,11 @@
"assert-plus": "^1.0.0" "assert-plus": "^1.0.0"
} }
}, },
"data-uri-to-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz",
"integrity": "sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og=="
},
"dayjs": { "dayjs": {
"version": "1.10.4", "version": "1.10.4",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz",
@ -21454,6 +21511,22 @@
"integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
"dev": true "dev": true
}, },
"image-resize": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/image-resize/-/image-resize-1.1.5.tgz",
"integrity": "sha512-oLSSXVzAmjhwSe36cGNmaSAid8xEXADlS6k+hXqXij3SMPhf9zUI6g3dHuNDV9nVmaD4jja36+k5PWmOiuI1wg==",
"requires": {
"jquery": "^3.4.1"
}
},
"image-size": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.0.0.tgz",
"integrity": "sha512-JLJ6OwBfO1KcA+TvJT+v8gbE6iWbj24LyDNFgFEN0lzegn6cC6a/p3NIDaepMsJjQjlUWqIC7wJv8lBFxPNjcw==",
"requires": {
"queue": "6.0.2"
}
},
"import-cwd": { "import-cwd": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz", "resolved": "https://registry.npmjs.org/import-cwd/-/import-cwd-2.1.0.tgz",
@ -22075,8 +22148,7 @@
"jquery": { "jquery": {
"version": "3.6.0", "version": "3.6.0",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz",
"integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==", "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw=="
"peer": true
}, },
"js-message": { "js-message": {
"version": "1.0.7", "version": "1.0.7",
@ -24229,6 +24301,11 @@
"dev": true, "dev": true,
"optional": true "optional": true
}, },
"pretty-bytes": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
"integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg=="
},
"pretty-error": { "pretty-error": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.2.tgz",
@ -24486,6 +24563,14 @@
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
"dev": true "dev": true
}, },
"queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"requires": {
"inherits": "~2.0.3"
}
},
"randombytes": { "randombytes": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",

View file

@ -12,8 +12,11 @@
"axios": "^0.21.0", "axios": "^0.21.0",
"clean-insights-sdk": "^2.4.1", "clean-insights-sdk": "^2.4.1",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"data-uri-to-buffer": "^3.0.1",
"dayjs": "^1.10.3", "dayjs": "^1.10.3",
"fix-webm-duration": "^1.0.0", "fix-webm-duration": "^1.0.0",
"image-resize": "^1.1.5",
"image-size": "^1.0.0",
"intersection-observer": "^0.11.0", "intersection-observer": "^0.11.0",
"js-sha256": "^0.9.0", "js-sha256": "^0.9.0",
"json-web-key": "^0.4.0", "json-web-key": "^0.4.0",
@ -22,6 +25,7 @@
"matrix-js-sdk": "^9.4.1", "matrix-js-sdk": "^9.4.1",
"md-gum-polyfill": "^1.0.0", "md-gum-polyfill": "^1.0.0",
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz", "olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
"pretty-bytes": "^5.6.0",
"qrcode": "^1.4.4", "qrcode": "^1.4.4",
"raw-loader": "^4.0.2", "raw-loader": "^4.0.2",
"recordrtc": "^5.6.2", "recordrtc": "^5.6.2",

View file

@ -16,7 +16,10 @@
ref="messageOperations" ref="messageOperations"
:style="opStyle" :style="opStyle"
:emojis="recentEmojis" :emojis="recentEmojis"
v-on:close="showContextMenu = false;showContextMenuAnchor = null;" v-on:close="
showContextMenu = false;
showContextMenuAnchor = null;
"
v-if="selectedEvent && showContextMenu" v-if="selectedEvent && showContextMenu"
v-on:addreaction="addReaction" v-on:addreaction="addReaction"
v-on:addquickreaction="addQuickReaction" v-on:addquickreaction="addQuickReaction"
@ -33,7 +36,10 @@
<avatar-operations <avatar-operations
ref="avatarOperations" ref="avatarOperations"
:style="avatarOpStyle" :style="avatarOpStyle"
v-on:close="showAvatarMenu = false;showAvatarMenuAnchor = null;" v-on:close="
showAvatarMenu = false;
showAvatarMenuAnchor = null;
"
v-on:start-private-chat="startPrivateChat($event)" v-on:start-private-chat="startPrivateChat($event)"
v-if="selectedEvent && showAvatarMenu" v-if="selectedEvent && showAvatarMenu"
:room="room" :room="room"
@ -47,7 +53,10 @@
@notify="handleChatContainerResize" @notify="handleChatContainerResize"
/> />
<CreatedRoomWelcomeHeader v-if="showCreatedRoomWelcomeHeader" v-on:close="closeCreateRoomWelcomeHeader" /> <CreatedRoomWelcomeHeader
v-if="showCreatedRoomWelcomeHeader"
v-on:close="closeCreateRoomWelcomeHeader"
/>
<div <div
v-for="(event, index) in events" v-for="(event, index) in events"
@ -208,17 +217,20 @@
</v-btn> </v-btn>
</v-col> </v-col>
<v-col v-if="config.useShortCodeStickers" class="input-area-button text-center flex-grow-0 flex-shrink-1"> <v-col
<v-btn v-if="config.useShortCodeStickers"
v-if="!showRecorder" class="input-area-button text-center flex-grow-0 flex-shrink-1"
icon >
large <v-btn
color="black" v-if="!showRecorder"
@click="showStickerPicker" icon
:disabled="attachButtonDisabled" large
> color="black"
<v-icon large>face</v-icon> @click="showStickerPicker"
</v-btn> :disabled="attachButtonDisabled"
>
<v-icon large>face</v-icon>
</v-btn>
</v-col> </v-col>
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1"> <v-col class="input-area-button text-center flex-grow-0 flex-shrink-1">
@ -254,16 +266,52 @@
</v-container> </v-container>
<div v-if="currentImageInputPath"> <div v-if="currentImageInputPath">
<v-dialog v-model="currentImageInputPath" class="ma-0 pa-0" width="50%"> <v-dialog v-model="currentImageInputPath" class="ma-0 pa-0" :width="$vuetify.breakpoint.smAndUp ? '50%' : '85%'">
<v-card class="ma-0 pa-0"> <v-card class="ma-0 pa-0">
<v-card-text class="ma-0 pa-0"> <v-card-text class="ma-0 pa-2">
<v-img <v-img
v-if="currentImageInput" v-if="currentImageInput && currentImageInput.image"
:aspect-ratio="1" :aspect-ratio="1"
:src="currentImageInput" :src="currentImageInput.image"
contain contain
style="max-height: 50vh" style="max-height: 50vh; background-color: #e2e2e2"
/> />
<div>
file: {{ currentImageInputPath.name }}
<span
v-if="
currentImageInput &&
currentImageInput.scaled &&
currentImageInput.useScaled
"
>
{{ currentImageInput.scaledDimensions.width }} x
{{ currentImageInput.scaledDimensions.height }}</span
>
<span
v-else-if="currentImageInput && currentImageInput.dimensions"
>
{{ currentImageInput.dimensions.width }} x
{{ currentImageInput.dimensions.height }}</span
>
<span
v-if="
currentImageInput &&
currentImageInput.scaled &&
currentImageInput.useScaled
"
>
({{ formatBytes(currentImageInput.scaledSize) }})</span
>
<span v-else>
({{ formatBytes(currentImageInputPath.size) }})</span
>
<v-switch
v-if="currentImageInput && currentImageInput.scaled"
label="Scale image"
v-model="currentImageInput.useScaled"
/>
</div>
<div v-if="currentSendError">{{ currentSendError }}</div> <div v-if="currentSendError">{{ currentSendError }}</div>
<div v-else>{{ currentSendProgress }}</div> <div v-else>{{ currentSendProgress }}</div>
</v-card-text> </v-card-text>
@ -286,22 +334,33 @@
</v-dialog> </v-dialog>
</div> </div>
<MessageOperationsBottomSheet ref="messageOperationsSheet" xv-show="showEmojiPicker"> <MessageOperationsBottomSheet
ref="messageOperationsSheet"
xv-show="showEmojiPicker"
>
<MessageOperationsPicker <MessageOperationsPicker
v-on:close="showEmojiPicker = false" v-on:close="showEmojiPicker = false"
v-if="selectedEvent" v-if="selectedEvent"
v-on:addreaction="addReaction" v-on:addreaction="addReaction"
v-on:addquickreaction="addQuickReaction" v-on:addquickreaction="addQuickReaction"
v-on:addreply="addReply(selectedEvent)" v-on:addreply="addReply(selectedEvent)"
v-on:edit="edit(selectedEvent)" v-on:edit="edit(selectedEvent)"
v-on:redact="redact(selectedEvent)" v-on:redact="redact(selectedEvent)"
v-on:download="download(selectedEvent)" v-on:download="download(selectedEvent)"
:event="selectedEvent" :event="selectedEvent"
/> />
<VEmojiPicker ref="emojiPicker" style="width: 100%" @select="emojiSelected" /> <VEmojiPicker
ref="emojiPicker"
style="width: 100%"
@select="emojiSelected"
/>
</MessageOperationsBottomSheet> </MessageOperationsBottomSheet>
<StickerPickerBottomSheet ref="stickerPickerSheet" style="z-index:10" v-on:selectSticker="sendSticker" /> <StickerPickerBottomSheet
ref="stickerPickerSheet"
style="z-index: 10"
v-on:selectSticker="sendSticker"
/>
<!-- "NOT ALLOWED FOR GUEST ACCOUNTS" dialog --> <!-- "NOT ALLOWED FOR GUEST ACCOUNTS" dialog -->
<v-dialog v-model="showNotAllowedForGuests" class="ma-0 pa-0" width="50%"> <v-dialog v-model="showNotAllowedForGuests" class="ma-0 pa-0" width="50%">
@ -324,7 +383,7 @@
<v-container <v-container
fluid fluid
fill-height fill-height
style="position: absolute;background-color:rgba(0,0,0,0.2)" style="position: absolute; background-color: rgba(0, 0, 0, 0.2)"
v-if="!initialLoadDone || loading" v-if="!initialLoadDone || loading"
> >
<v-row align="center" justify="center"> <v-row align="center" justify="center">
@ -342,6 +401,7 @@
</template> </template>
<script> <script>
import Vue from "vue";
import { TimelineWindow, EventTimeline } from "matrix-js-sdk"; import { TimelineWindow, EventTimeline } from "matrix-js-sdk";
import MessageIncomingText from "./messages/MessageIncomingText"; import MessageIncomingText from "./messages/MessageIncomingText";
import MessageIncomingFile from "./messages/MessageIncomingFile"; import MessageIncomingFile from "./messages/MessageIncomingFile";
@ -373,14 +433,18 @@ import ChatHeader from "./ChatHeader";
import VoiceRecorder from "./VoiceRecorder"; import VoiceRecorder from "./VoiceRecorder";
import RoomInfoBottomSheet from "./RoomInfoBottomSheet"; import RoomInfoBottomSheet from "./RoomInfoBottomSheet";
import CreatedRoomWelcomeHeader from "./CreatedRoomWelcomeHeader"; import CreatedRoomWelcomeHeader from "./CreatedRoomWelcomeHeader";
import MessageOperationsBottomSheet from './MessageOperationsBottomSheet'; import MessageOperationsBottomSheet from "./MessageOperationsBottomSheet";
import stickers from '../plugins/stickers'; import stickers from "../plugins/stickers";
import StickerPickerBottomSheet from './StickerPickerBottomSheet'; import StickerPickerBottomSheet from "./StickerPickerBottomSheet";
import BottomSheet from './BottomSheet.vue'; import BottomSheet from "./BottomSheet.vue";
import config from "../assets/config"; import config from "../assets/config";
import ImageResize from "image-resize";
const sizeOf = require("image-size");
const dataUriToBuffer = require("data-uri-to-buffer");
const prettyBytes = require("pretty-bytes");
const READ_RECEIPT_TIMEOUT = 5000; /* How long a message must have been visible before the read marker is updated */ const READ_RECEIPT_TIMEOUT = 5000; /* How long a message must have been visible before the read marker is updated */
const WINDOW_BUFFER_SIZE = 0.3 /** Relative window height of when we start paginating. Always keep this much loaded before and after our scroll position! */ const WINDOW_BUFFER_SIZE = 0.3; /** Relative window height of when we start paginating. Always keep this much loaded before and after our scroll position! */
// from https://kirbysayshi.com/2013/08/19/maintaining-scroll-position-knockoutjs-list.html // from https://kirbysayshi.com/2013/08/19/maintaining-scroll-position-knockoutjs-list.html
function ScrollPosition(node) { function ScrollPosition(node) {
@ -448,14 +512,14 @@ export default {
}, },
data() { data() {
return { return {
config: config, config: config,
events: [], events: [],
currentInput: "", currentInput: "",
typingMembers: [], typingMembers: [],
timelineWindow: null, timelineWindow: null,
/** true if we are currently paginating */ /** true if we are currently paginating */
timelineWindowPaginating: false, timelineWindowPaginating: false,
scrollPosition: null, scrollPosition: null,
@ -504,7 +568,7 @@ export default {
showCreatedRoomWelcomeHeader: false, showCreatedRoomWelcomeHeader: false,
/** An array of recent emojis. Used in the "message operations" popup. */ /** An array of recent emojis. Used in the "message operations" popup. */
recentEmojis: [] recentEmojis: [],
}; };
}, },
@ -627,7 +691,7 @@ export default {
}, },
debugging() { debugging() {
return (window.location.host || "").startsWith("localhost"); return (window.location.host || "").startsWith("localhost");
} },
}, },
watch: { watch: {
@ -679,13 +743,18 @@ export default {
methods: { methods: {
onRoomJoined(initialEventId) { onRoomJoined(initialEventId) {
// Was this room just created (by you)? Show a small info header in // Was this room just created (by you)? Show a small info header in
// that case! // that case!
const createEvent = this.room.currentState.getStateEvents("m.room.create",""); const createEvent = this.room.currentState.getStateEvents(
"m.room.create",
""
);
if (createEvent) { if (createEvent) {
const creatorId = createEvent.getContent().creator; const creatorId = createEvent.getContent().creator;
if (creatorId == this.$matrix.currentUserId && createEvent.getLocalAge() < (5 * 60000) /* 5 minutes */) { if (
creatorId == this.$matrix.currentUserId &&
createEvent.getLocalAge() < 5 * 60000 /* 5 minutes */
) {
this.showCreatedRoomWelcomeHeader = true; this.showCreatedRoomWelcomeHeader = true;
} }
} }
@ -713,7 +782,8 @@ export default {
const getMoreIfNeeded = function _getMoreIfNeeded() { const getMoreIfNeeded = function _getMoreIfNeeded() {
const container = self.$refs.chatContainer; const container = self.$refs.chatContainer;
if ( if (
container.scrollHeight <= (1 + 2 * WINDOW_BUFFER_SIZE) * container.clientHeight && container.scrollHeight <=
(1 + 2 * WINDOW_BUFFER_SIZE) * container.clientHeight &&
self.timelineWindow && self.timelineWindow &&
self.timelineWindow.canPaginate(EventTimeline.BACKWARDS) self.timelineWindow.canPaginate(EventTimeline.BACKWARDS)
) { ) {
@ -770,20 +840,27 @@ export default {
}, },
scrollToEndOfTimeline() { scrollToEndOfTimeline() {
if (this.timelineWindow && this.timelineWindow.canPaginate(EventTimeline.FORWARDS)) { if (
this.timelineWindow &&
this.timelineWindow.canPaginate(EventTimeline.FORWARDS)
) {
this.loading = true; this.loading = true;
// Instead of paging though ALL history, just reload a timeline at the live marker... // Instead of paging though ALL history, just reload a timeline at the live marker...
var timelineWindow = new TimelineWindow(this.$matrix.matrixClient, this.room.getUnfilteredTimelineSet(), {}); var timelineWindow = new TimelineWindow(
const self = this; this.$matrix.matrixClient,
timelineWindow this.room.getUnfilteredTimelineSet(),
.load(null, 20) {}
.then(() => { );
self.timelineWindow = timelineWindow; const self = this;
self.events = self.timelineWindow.getEvents(); timelineWindow
}) .load(null, 20)
.finally(() => { .then(() => {
this.loading = false; self.timelineWindow = timelineWindow;
}); self.events = self.timelineWindow.getEvents();
})
.finally(() => {
this.loading = false;
});
} else { } else {
// Can't paginate, just scroll to bottom of window! // Can't paginate, just scroll to bottom of window!
this.smoothScrollToEnd(); this.smoothScrollToEnd();
@ -876,7 +953,11 @@ export default {
if (event.getSender() != this.$matrix.currentUserId) { if (event.getSender() != this.$matrix.currentUserId) {
if (event.getContent().msgtype == "m.image") { if (event.getContent().msgtype == "m.image") {
// For SVG, make downloadable // For SVG, make downloadable
if (event.getContent().info && event.getContent().info.mimetype && event.getContent().info.mimetype.startsWith("image/svg")) { if (
event.getContent().info &&
event.getContent().info.mimetype &&
event.getContent().info.mimetype.startsWith("image/svg")
) {
return MessageIncomingFile; return MessageIncomingFile;
} }
return MessageIncomingImage; return MessageIncomingImage;
@ -893,7 +974,11 @@ export default {
} else { } else {
if (event.getContent().msgtype == "m.image") { if (event.getContent().msgtype == "m.image") {
// For SVG, make downloadable // For SVG, make downloadable
if (event.getContent().info && event.getContent().info.mimetype && event.getContent().info.mimetype.startsWith("image/svg")) { if (
event.getContent().info &&
event.getContent().info.mimetype &&
event.getContent().info.mimetype.startsWith("image/svg")
) {
return MessageOutgoingImage; return MessageOutgoingImage;
} }
return MessageOutgoingImage; return MessageOutgoingImage;
@ -945,14 +1030,18 @@ export default {
// Scrolled to top // Scrolled to top
this.handleScrolledToTop(); this.handleScrolledToTop();
} else if ( } else if (
container.scrollHeight - container.scrollTop.toFixed(0) - container.clientHeight <= bufferHeight container.scrollHeight -
container.scrollTop.toFixed(0) -
container.clientHeight <=
bufferHeight
) { ) {
this.handleScrolledToBottom(false); this.handleScrolledToBottom(false);
} }
this.showScrollToEnd = this.showScrollToEnd =
container.scrollHeight - container.scrollTop.toFixed(0) > container.scrollHeight - container.scrollTop.toFixed(0) >
container.clientHeight || (this.timelineWindow && container.clientHeight ||
this.timelineWindow.canPaginate(EventTimeline.FORWARDS)); (this.timelineWindow &&
this.timelineWindow.canPaginate(EventTimeline.FORWARDS));
this.restartRRTimer(); this.restartRRTimer();
}, },
@ -1046,16 +1135,68 @@ export default {
if (event.target.files && event.target.files[0]) { if (event.target.files && event.target.files[0]) {
var reader = new FileReader(); var reader = new FileReader();
reader.onload = (e) => { reader.onload = (e) => {
const file = event.target.files[0];
this.currentSendShowSendButton = true; this.currentSendShowSendButton = true;
this.currentImageInput = e.target.result; if (file.type.startsWith("image/")) {
this.currentImageInputPath = event.target.files[0]; this.currentImageInput = {
image: e.target.result,
dimensions: null,
};
try {
this.currentImageInput.dimensions = sizeOf(
dataUriToBuffer(e.target.result)
);
// Need to resize?
const w = this.currentImageInput.dimensions.width;
const h = this.currentImageInput.dimensions.height;
if (w > 640 || h > 640) {
var aspect = w / h;
var newWidth = parseInt((w > h ? 640 : 640 * aspect).toFixed());
var newHeight = parseInt(
(w > h ? 640 / aspect : 640).toFixed()
);
var imageResize = new ImageResize({
format: "png",
width: newWidth,
height: newHeight,
outputType: "blob",
});
imageResize
.play(event.target)
.then((img) => {
Vue.set(
this.currentImageInput,
"scaled",
new File([img], file.name, {
type: img.type,
lastModified: Date.now(),
})
);
Vue.set(this.currentImageInput, "useScaled", true);
Vue.set(this.currentImageInput, "scaledSize", img.size);
Vue.set(this.currentImageInput, "scaledDimensions", {
width: newWidth,
height: newHeight,
});
})
.catch((err) => {
console.error("Resize failed:", err);
});
}
} catch (error) {
console.error("Failed to get image dimensions: " + error);
}
}
console.log(this.currentImageInput);
this.currentImageInputPath = file;
}; };
reader.readAsDataURL(event.target.files[0]); reader.readAsDataURL(event.target.files[0]);
} }
}, },
showStickerPicker() { showStickerPicker() {
this.$refs.stickerPickerSheet.open(); this.$refs.stickerPickerSheet.open();
}, },
onUploadProgress(p) { onUploadProgress(p) {
@ -1068,12 +1209,22 @@ export default {
}, },
sendAttachment(withText) { sendAttachment(withText) {
this.$refs.attachment.value = null;
if (this.currentImageInputPath) { if (this.currentImageInputPath) {
var inputFile = this.currentImageInputPath;
if (
this.currentImageInput &&
this.currentImageInput.scaled &&
this.currentImageInput.useScaled
) {
// Send scaled version of image instead!
inputFile = this.currentImageInput.scaled;
}
this.currentSendProgress = null; this.currentSendProgress = null;
this.currentSendOperation = util.sendImage( this.currentSendOperation = util.sendImage(
this.$matrix.matrixClient, this.$matrix.matrixClient,
this.roomId, this.roomId,
this.currentImageInputPath, inputFile,
this.onUploadProgress this.onUploadProgress
); );
this.currentSendOperation this.currentSendOperation
@ -1095,6 +1246,7 @@ export default {
}, },
cancelSendAttachment() { cancelSendAttachment() {
this.$refs.attachment.value = null;
if (this.currentSendOperation) { if (this.currentSendOperation) {
this.currentSendOperation.reject("Canceled"); this.currentSendOperation.reject("Canceled");
} }
@ -1195,7 +1347,7 @@ export default {
showMoreMessageOperations(e) { showMoreMessageOperations(e) {
this.addReaction(e); this.addReaction(e);
}, },
addReaction(e) { addReaction(e) {
const event = e.event; const event = e.event;
// Store the event we are reacting to, so that we know where to // Store the event we are reacting to, so that we know where to
@ -1306,21 +1458,26 @@ export default {
}, },
startPrivateChat(e) { startPrivateChat(e) {
this.$matrix.getOrCreatePrivateChat(e.event.getSender()) this.$matrix
.then(room => { .getOrCreatePrivateChat(e.event.getSender())
.then((room) => {
this.$nextTick(() => { this.$nextTick(() => {
this.$navigation.push( this.$navigation.push(
{ {
name: "Chat", name: "Chat",
params: { roomId: util.sanitizeRoomId(room.getCanonicalAlias() || room.roomId) }, params: {
roomId: util.sanitizeRoomId(
room.getCanonicalAlias() || room.roomId
),
},
}, },
-1 -1
); );
}); });
}) })
.catch(err => { .catch((err) => {
console.error(err); console.error(err);
}) });
}, },
closeContextMenusIfOpen(e) { closeContextMenusIfOpen(e) {
@ -1466,8 +1623,11 @@ export default {
return; return;
} }
this.recentEmojis = []; this.recentEmojis = [];
} },
formatBytes(bytes) {
return prettyBytes(bytes);
},
}, },
}; };
</script> </script>

View file

@ -562,7 +562,6 @@ export default {
// //
const createRoomOptions = { const createRoomOptions = {
visibility: "private", // Not listed! visibility: "private", // Not listed!
//name: this.roomName,
preset: "private_chat", preset: "private_chat",
initial_state: [ initial_state: [
{ {