Show file name and size on images on hover (#6511)
* Show simple file name and size on images/videos Fixes https://github.com/vector-im/element-web/issues/18197 * i18n * Fix bad merge * Add hover state tracking * Only show on timeline-like objects * Match new design requirements * Remove video support (deemed not needed) * Colouring and sizing from design * Include file name in lightbox * Revert changes to videos since we don't need them * i18n * Iterate PR Co-authored-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
parent
f54d54b3ff
commit
f51a6b6da4
7 changed files with 52 additions and 4 deletions
|
@ -79,6 +79,11 @@ $button-gap: 24px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mx_ImageView_title {
|
||||||
|
color: $lightbox-fg-color;
|
||||||
|
font-size: $font-12px;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_ImageView_toolbar {
|
.mx_ImageView_toolbar {
|
||||||
padding-right: 16px;
|
padding-right: 16px;
|
||||||
pointer-events: initial;
|
pointer-events: initial;
|
||||||
|
|
|
@ -17,6 +17,28 @@ limitations under the License.
|
||||||
|
|
||||||
$timeline-image-border-radius: 8px;
|
$timeline-image-border-radius: 8px;
|
||||||
|
|
||||||
|
.mx_MImageBody_banner {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 4px;
|
||||||
|
left: 4px;
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: $timeline-image-border-radius;
|
||||||
|
font-size: $font-15px;
|
||||||
|
|
||||||
|
pointer-events: none; // let the cursor go through to the media underneath
|
||||||
|
|
||||||
|
// Trying to match the width of the image is surprisingly difficult, so arbitrarily break it off early.
|
||||||
|
max-width: min(100%, 350px);
|
||||||
|
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
// Hardcoded colours because it's the same on all themes
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
.mx_MImageBody_placeholder {
|
.mx_MImageBody_placeholder {
|
||||||
// Position the placeholder on top of the thumbnail, so that the reveal animation can work
|
// Position the placeholder on top of the thumbnail, so that the reveal animation can work
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|
|
@ -38,6 +38,7 @@ import UIStore from '../../../stores/UIStore';
|
||||||
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
import { ViewRoomPayload } from "../../../dispatcher/payloads/ViewRoomPayload";
|
||||||
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
import { KeyBindingAction } from "../../../accessibility/KeyboardShortcuts";
|
||||||
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
import { getKeyBindingsManager } from "../../../KeyBindingsManager";
|
||||||
|
import { presentableTextForFile } from "../../../utils/FileUtils";
|
||||||
|
|
||||||
// Max scale to keep gaps around the image
|
// Max scale to keep gaps around the image
|
||||||
const MAX_SCALE = 0.95;
|
const MAX_SCALE = 0.95;
|
||||||
|
@ -546,6 +547,9 @@ export default class ImageView extends React.Component<IProps, IState> {
|
||||||
>
|
>
|
||||||
<div className="mx_ImageView_panel">
|
<div className="mx_ImageView_panel">
|
||||||
{ info }
|
{ info }
|
||||||
|
<div className="mx_ImageView_title">
|
||||||
|
{ presentableTextForFile(this.props.mxEvent.getContent(), _t("Image"), true) }
|
||||||
|
</div>
|
||||||
<div className="mx_ImageView_toolbar">
|
<div className="mx_ImageView_toolbar">
|
||||||
{ zoomOutButton }
|
{ zoomOutButton }
|
||||||
{ zoomInButton }
|
{ zoomInButton }
|
||||||
|
|
|
@ -344,7 +344,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
|
||||||
</a>
|
</a>
|
||||||
{ this.context.timelineRenderingType === TimelineRenderingType.File && (
|
{ this.context.timelineRenderingType === TimelineRenderingType.File && (
|
||||||
<div className="mx_MImageBody_size">
|
<div className="mx_MImageBody_size">
|
||||||
{ this.content.info && this.content.info.size ? filesize(this.content.info.size) : "" }
|
{ this.content.info?.size ? filesize(this.content.info.size) : "" }
|
||||||
</div>
|
</div>
|
||||||
) }
|
) }
|
||||||
</div> }
|
</div> }
|
||||||
|
|
|
@ -37,6 +37,7 @@ import { ImageSize, suggestedSize as suggestedImageSize } from "../../../setting
|
||||||
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
import { MatrixClientPeg } from '../../../MatrixClientPeg';
|
||||||
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||||
import { blobIsAnimated, mayBeAnimated } from '../../../utils/Image';
|
import { blobIsAnimated, mayBeAnimated } from '../../../utils/Image';
|
||||||
|
import { presentableTextForFile } from "../../../utils/FileUtils";
|
||||||
|
|
||||||
enum Placeholder {
|
enum Placeholder {
|
||||||
NoImage,
|
NoImage,
|
||||||
|
@ -446,6 +447,21 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
gifLabel = <p className="mx_MImageBody_gifLabel">GIF</p>;
|
gifLabel = <p className="mx_MImageBody_gifLabel">GIF</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let banner: JSX.Element;
|
||||||
|
const isTimeline = [
|
||||||
|
TimelineRenderingType.Room,
|
||||||
|
TimelineRenderingType.Search,
|
||||||
|
TimelineRenderingType.Thread,
|
||||||
|
TimelineRenderingType.Notification,
|
||||||
|
].includes(this.context.timelineRenderingType);
|
||||||
|
if (this.state.showImage && this.state.hover && isTimeline) {
|
||||||
|
banner = (
|
||||||
|
<span className="mx_MImageBody_banner">
|
||||||
|
{ presentableTextForFile(content, _t("Image"), true, true) }
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const classes = classNames({
|
const classes = classNames({
|
||||||
'mx_MImageBody_placeholder': true,
|
'mx_MImageBody_placeholder': true,
|
||||||
'mx_MImageBody_placeholder--blurhash': this.props.mxEvent.getContent().info?.[BLURHASH_FIELD],
|
'mx_MImageBody_placeholder--blurhash': this.props.mxEvent.getContent().info?.[BLURHASH_FIELD],
|
||||||
|
@ -473,6 +489,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
|
||||||
<div style={sizing}>
|
<div style={sizing}>
|
||||||
{ img }
|
{ img }
|
||||||
{ gifLabel }
|
{ gifLabel }
|
||||||
|
{ banner }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ /* HACK: This div fills out space while the image loads, to prevent scroll jumps */ }
|
{ /* HACK: This div fills out space while the image loads, to prevent scroll jumps */ }
|
||||||
|
|
|
@ -2134,9 +2134,9 @@
|
||||||
"Error decrypting attachment": "Error decrypting attachment",
|
"Error decrypting attachment": "Error decrypting attachment",
|
||||||
"Decrypt %(text)s": "Decrypt %(text)s",
|
"Decrypt %(text)s": "Decrypt %(text)s",
|
||||||
"Invalid file%(extra)s": "Invalid file%(extra)s",
|
"Invalid file%(extra)s": "Invalid file%(extra)s",
|
||||||
|
"Image": "Image",
|
||||||
"Error decrypting image": "Error decrypting image",
|
"Error decrypting image": "Error decrypting image",
|
||||||
"Show image": "Show image",
|
"Show image": "Show image",
|
||||||
"Image": "Image",
|
|
||||||
"Join the conference at the top of this room": "Join the conference at the top of this room",
|
"Join the conference at the top of this room": "Join the conference at the top of this room",
|
||||||
"Join the conference from the room information card on the right": "Join the conference from the room information card on the right",
|
"Join the conference from the room information card on the right": "Join the conference from the room information card on the right",
|
||||||
"Video conference ended by %(senderName)s": "Video conference ended by %(senderName)s",
|
"Video conference ended by %(senderName)s": "Video conference ended by %(senderName)s",
|
||||||
|
|
|
@ -37,7 +37,7 @@ export function presentableTextForFile(
|
||||||
shortened = false,
|
shortened = false,
|
||||||
): string {
|
): string {
|
||||||
let text = fallbackText;
|
let text = fallbackText;
|
||||||
if (content.body && content.body.length > 0) {
|
if (content.body?.length > 0) {
|
||||||
// The content body should be the name of the file including a
|
// The content body should be the name of the file including a
|
||||||
// file extension.
|
// file extension.
|
||||||
text = content.body;
|
text = content.body;
|
||||||
|
@ -58,7 +58,7 @@ export function presentableTextForFile(
|
||||||
text = `${fileName}...${extension}`;
|
text = `${fileName}...${extension}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (content.info && content.info.size && withSize) {
|
if (content.info?.size && withSize) {
|
||||||
// If we know the size of the file then add it as human readable
|
// If we know the size of the file then add it as human readable
|
||||||
// string to the end of the link text so that the user knows how
|
// string to the end of the link text so that the user knows how
|
||||||
// big a file they are downloading.
|
// big a file they are downloading.
|
||||||
|
|
Loading…
Reference in a new issue