diff --git a/src/domain/session/room/RoomViewModel.js b/src/domain/session/room/RoomViewModel.js index 7a179c0f..5e6ee998 100644 --- a/src/domain/session/room/RoomViewModel.js +++ b/src/domain/session/room/RoomViewModel.js @@ -200,7 +200,11 @@ export class RoomViewModel extends ViewModel { if (!file.blob.mimeType.startsWith("image/")) { return this._sendFile(file); } - const image = await this.platform.loadImage(file.blob); + let image = await this.platform.loadImage(file.blob); + const limit = await this.platform.settingsStorage.getInt("sentImageSizeLimit"); + if (limit && image.maxDimension > limit) { + image = await image.scale(limit); + } const content = { body: file.name, msgtype: "m.image", diff --git a/src/domain/session/settings/SettingsViewModel.js b/src/domain/session/settings/SettingsViewModel.js index 0a1f223c..2c072305 100644 --- a/src/domain/session/settings/SettingsViewModel.js +++ b/src/domain/session/settings/SettingsViewModel.js @@ -36,10 +36,26 @@ export class SettingsViewModel extends ViewModel { this._sessionBackupViewModel = this.track(new SessionBackupViewModel(this.childOptions({session}))); this._closeUrl = this.urlCreator.urlUntilSegment("session"); this._estimate = null; + + this.sentImageSizeLimit = null; + this.minSentImageSizeLimit = 400; + this.maxSentImageSizeLimit = 4000; + } + + setSentImageSizeLimit(size) { + if (size > this.maxSentImageSizeLimit || size < this.minSentImageSizeLimit) { + this.sentImageSizeLimit = null; + this.platform.settingsStorage.remove("sentImageSizeLimit"); + } else { + this.sentImageSizeLimit = Math.round(size); + this.platform.settingsStorage.setInt("sentImageSizeLimit", size); + } + this.emitChange("sentImageSizeLimit"); } async load() { this._estimate = await this.platform.estimateStorageUsage(); + this.sentImageSizeLimit = await this.platform.settingsStorage.getInt("sentImageSizeLimit"); this.emitChange(""); } diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 98a37f22..1b2226f2 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -18,6 +18,7 @@ import {createFetchRequest} from "./dom/request/fetch.js"; import {xhrRequest} from "./dom/request/xhr.js"; import {StorageFactory} from "../../matrix/storage/idb/StorageFactory.js"; import {SessionInfoStorage} from "../../matrix/sessioninfo/localstorage/SessionInfoStorage.js"; +import {SettingsStorage} from "./dom/SettingsStorage.js"; import {OlmWorker} from "../../matrix/e2ee/OlmWorker.js"; import {RootView} from "./ui/RootView.js"; import {Clock} from "./dom/Clock.js"; @@ -78,7 +79,6 @@ async function loadOlmWorker(paths) { return olmWorker; } - export class Platform { constructor(container, paths, cryptoExtras = null) { this._paths = paths; @@ -94,6 +94,7 @@ export class Platform { this.crypto = new Crypto(cryptoExtras); this.storageFactory = new StorageFactory(this._serviceWorkerHandler); this.sessionInfoStorage = new SessionInfoStorage("hydrogen_sessions_v1"); + this.settingsStorage = new SettingsStorage("hydrogen_setting_v1_"); this.estimateStorageUsage = estimateStorageUsage; this.random = Math.random; if (typeof fetch === "function") { diff --git a/src/platform/web/dom/SettingsStorage.js b/src/platform/web/dom/SettingsStorage.js new file mode 100644 index 00000000..0b3e81a8 --- /dev/null +++ b/src/platform/web/dom/SettingsStorage.js @@ -0,0 +1,37 @@ +/* +Copyright 2020 The Matrix.org Foundation C.I.C. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +export class SettingsStorage { + constructor(prefix) { + this._prefix = prefix; + } + + async setInt(key, value) { + window.localStorage.setItem(`${this._prefix}${key}`, value); + } + + async getInt(key) { + const value = window.localStorage.getItem(`${this._prefix}${key}`); + if (typeof value === "string") { + return parseInt(value, 10); + } + return; + } + + async remove(key) { + window.localStorage.removeItem(`${this._prefix}${key}`); + } +} diff --git a/src/platform/web/ui/css/themes/element/theme.css b/src/platform/web/ui/css/themes/element/theme.css index 8fd72ef3..a4d7c4e6 100644 --- a/src/platform/web/ui/css/themes/element/theme.css +++ b/src/platform/web/ui/css/themes/element/theme.css @@ -677,6 +677,7 @@ only loads when the top comes into view*/ .Settings .row .content { margin-left: 4px; + flex: 1; } .Settings .row.code .content { @@ -688,6 +689,12 @@ only loads when the top comes into view*/ margin: 0 8px; } +.Settings .row .content input[type=range] { + width: 100%; + max-width: 300px; + min-width: 160px; +} + .Settings .row { margin: 4px 0px; display: flex; diff --git a/src/platform/web/ui/general/html.js b/src/platform/web/ui/general/html.js index 96f52c25..a965a6ee 100644 --- a/src/platform/web/ui/general/html.js +++ b/src/platform/web/ui/general/html.js @@ -94,7 +94,7 @@ export const TAG_NAMES = { [HTML_NS]: [ "br", "a", "ol", "ul", "li", "div", "h1", "h2", "h3", "h4", "h5", "h6", "p", "strong", "em", "span", "img", "section", "main", "article", "aside", - "pre", "button", "time", "input", "textarea", "label", "form", "progress"], + "pre", "button", "time", "input", "textarea", "label", "form", "progress", "output"], [SVG_NS]: ["svg", "circle"] }; diff --git a/src/platform/web/ui/session/settings/SettingsView.js b/src/platform/web/ui/session/settings/SettingsView.js index e350d806..564424e8 100644 --- a/src/platform/web/ui/session/settings/SettingsView.js +++ b/src/platform/web/ui/session/settings/SettingsView.js @@ -46,10 +46,30 @@ export class SettingsView extends TemplateView { row(vm.i18n`Session key`, vm.fingerprintKey, "code"), t.h3("Session Backup"), t.view(new SessionBackupSettingsView(vm.sessionBackupViewModel)), + t.h3("Preferences"), + row(vm.i18n`Compress images when sending`, this._imageCompressionRange(t, vm)), t.h3("Application"), row(vm.i18n`Version`, version), row(vm.i18n`Storage usage`, vm => `${vm.storageUsage} / ${vm.storageQuota}`), ]) ]); } + + _imageCompressionRange(t, vm) { + const step = 32; + const min = Math.ceil(vm.minSentImageSizeLimit / step) * step; + const max = (Math.floor(vm.maxSentImageSizeLimit / step) + 1) * step; + return [t.input({ + type: "range", + step, + min, + max, + value: vm => vm.sentImageSizeLimit || max, + onInput: evt => vm.setSentImageSizeLimit(parseInt(evt.target.value, 10)), + }), " ", t.output(vm => { + return vm.sentImageSizeLimit ? + vm.i18n`resize to ${vm.sentImageSizeLimit}px` : + vm.i18n`no compression`; + })]; + } }