Skip to content

Commit

Permalink
Implement showing and sending spoilers
Browse files Browse the repository at this point in the history
  • Loading branch information
cloudrac3r committed Sep 8, 2023
1 parent 6c4e34f commit 5735af5
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 10 deletions.
13 changes: 12 additions & 1 deletion src/ContentMessages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -415,14 +415,16 @@ export default class ContentMessages {
for (let i = 0; i < okFiles.length; ++i) {
const file = okFiles[i];
const loopPromiseBefore = promBefore;
let shouldContentWarning = false;

if (!uploadAll) {
const { finished } = Modal.createDialog(UploadConfirmDialog, {
file,
currentIndex: i,
totalFiles: okFiles.length,
});
const [shouldContinue, shouldUploadAll] = await finished;
const [shouldContinue, shouldUploadAll, contentWarning] = await finished;
shouldContentWarning = contentWarning;
if (!shouldContinue) break;
if (shouldUploadAll) {
uploadAll = true;
Expand All @@ -436,6 +438,7 @@ export default class ContentMessages {
relation,
matrixClient,
replyToEvent ?? undefined,
shouldContentWarning,
loopPromiseBefore,
),
);
Expand Down Expand Up @@ -481,6 +484,7 @@ export default class ContentMessages {
relation: IEventRelation | undefined,
matrixClient: MatrixClient,
replyToEvent: MatrixEvent | undefined,
contentWarning?: boolean,
promBefore?: Promise<any>,
): Promise<void> {
const fileName = file.name || _t("Attachment");
Expand All @@ -492,6 +496,13 @@ export default class ContentMessages {
msgtype: MsgType.File, // set more specifically later
};

// Attach content warning
if (contentWarning) {
content["m.content_warning"] = {
type: "m.spoiler" // Since the UI checkbox is labelled "Spoiler"
}
}

// Attach mentions, which really only applies if there's a replyToEvent.
attachMentions(matrixClient.getSafeUserId(), content, null, replyToEvent);
attachRelation(content, relation);
Expand Down
25 changes: 21 additions & 4 deletions src/components/views/dialogs/UploadConfirmDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,21 @@ import { _t } from "../../../languageHandler";
import { getBlobSafeMimeType } from "../../../utils/blobs";
import BaseDialog from "./BaseDialog";
import DialogButtons from "../elements/DialogButtons";
import StyledCheckbox from "../elements/StyledCheckbox";
import { fileSize } from "../../../utils/FileUtils";

interface IProps {
file: File;
currentIndex: number;
totalFiles: number;
onFinished: (uploadConfirmed: boolean, uploadAll?: boolean) => void;
onFinished: (uploadConfirmed: boolean, uploadAll?: boolean, contentWarning?: boolean) => void;
}

export default class UploadConfirmDialog extends React.Component<IProps> {
interface IState {
isContentWarning: boolean;
}

export default class UploadConfirmDialog extends React.Component<IProps, IState> {
private readonly objectUrl: string;
private readonly mimeType: string;

Expand All @@ -48,22 +53,30 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
this.mimeType = getBlobSafeMimeType(props.file.type);
const blob = new Blob([props.file], { type: this.mimeType });
this.objectUrl = URL.createObjectURL(blob);

this.state = {
isContentWarning: false,
}
}

public componentWillUnmount(): void {
if (this.objectUrl) URL.revokeObjectURL(this.objectUrl);
}

private toggleContentWarning = (): void => {
this.setState({ isContentWarning: !this.state.isContentWarning });
}

private onCancelClick = (): void => {
this.props.onFinished(false);
};

private onUploadClick = (): void => {
this.props.onFinished(true);
this.props.onFinished(true, false, this.state.isContentWarning);
};

private onUploadAllClick = (): void => {
this.props.onFinished(true, true);
this.props.onFinished(true, true, this.state.isContentWarning);
};

public render(): React.ReactNode {
Expand Down Expand Up @@ -122,6 +135,10 @@ export default class UploadConfirmDialog extends React.Component<IProps> {
</div>
</div>

<StyledCheckbox checked={this.state.isContentWarning} onChange={() => this.toggleContentWarning()}>
Spoiler
</StyledCheckbox>

<DialogButtons
primaryButton={_t("Upload")}
hasCancel={false}
Expand Down
10 changes: 6 additions & 4 deletions src/components/views/messages/MImageBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,12 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
imgError: false,
imgLoaded: false,
hover: false,
showImage: SettingsStore.getValue("showImages"),
showImage: SettingsStore.getValue("showImages") && !this.props.mxEvent.getContent()["m.content_warning"],
placeholder: Placeholder.NoImage,
};
}

protected showImage(): void {
localStorage.setItem("mx_ShowImage_" + this.props.mxEvent.getId(), "true");
this.setState({ showImage: true });
this.downloadImage();
}
Expand Down Expand Up @@ -338,13 +337,16 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
this.unmounted = false;

const showImage =
this.state.showImage || localStorage.getItem("mx_ShowImage_" + this.props.mxEvent.getId()) === "true";
this.state.showImage;

if (showImage) {
// noinspection JSIgnoredPromiseFromCall
this.downloadImage();
this.setState({ showImage: true });
} // else don't download anything because we don't want to display anything.
} else {
// don't download anything because we don't want to display anything.
this.setState({ contentUrl: this.getContentUrl() }); // doing this ensures wrapImage() gets called later, which adds the needed onClick handler
}

// Add a 150ms timer for blurhash to first appear.
if (this.props.mxEvent.getContent().info?.[BLURHASH_FIELD]) {
Expand Down
27 changes: 26 additions & 1 deletion src/components/views/messages/MVideoBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { BLURHASH_FIELD } from "../../../utils/image-media";
import { IMediaEventContent } from "../../../customisations/models/IMediaEventContent";
import { IBodyProps } from "./IBodyProps";
import MFileBody from "./MFileBody";
import { HiddenImagePlaceholder } from "./MImageBody";
import { ImageSize, suggestedSize as suggestedVideoSize } from "../../../settings/enums/ImageSize";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
import MediaProcessingError from "./shared/MediaProcessingError";
Expand All @@ -38,6 +39,7 @@ interface IState {
fetchingData: boolean;
posterLoading: boolean;
blurhashUrl: string | null;
showVideo: boolean;
}

export default class MVideoBody extends React.PureComponent<IBodyProps, IState> {
Expand All @@ -58,6 +60,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
error: null,
posterLoading: false,
blurhashUrl: null,
showVideo: true,
};
}

Expand Down Expand Up @@ -174,6 +177,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
decryptedUrl: `data:${mimetype},`,
decryptedThumbnailUrl: thumbnailUrl || `data:${mimetype},`,
decryptedBlob: null,
showVideo: !content?.["m.content_warning"],
});
}
} catch (err) {
Expand All @@ -183,6 +187,11 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
error: err,
});
}
} else { // not encrypted
const content = this.props.mxEvent.getContent<IMediaEventContent>();
this.setState({
showVideo: !content?.["m.content_warning"],
})
}
}

Expand Down Expand Up @@ -232,6 +241,19 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
return this.showFileBody && <MFileBody {...this.props} showGenericPlaceholder={false} />;
};

protected showVideo(): void {
this.setState({ showVideo: true });
}

protected onClick = (ev: React.MouseEvent): void => {
if (ev.button === 0 && !ev.metaKey) {
if (!this.state.showVideo) {
this.showVideo();
ev.preventDefault();
}
}
}

public render(): React.ReactNode {
const content = this.props.mxEvent.getContent();
const autoplay = SettingsStore.getValue("autoplayVideo");
Expand Down Expand Up @@ -287,7 +309,8 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
const fileBody = this.getFileBody();
return (
<span className="mx_MVideoBody">
<div className="mx_MVideoBody_container" style={{ maxWidth, maxHeight, aspectRatio }}>
<div className="mx_MVideoBody_container" onClick={ev => this.onClick(ev)} style={{ maxWidth, maxHeight, aspectRatio }}>
{this.state.showVideo ?
<video
className="mx_MVideoBody"
ref={this.videoRef}
Expand All @@ -303,6 +326,8 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
poster={poster}
onPlay={this.videoOnPlay}
/>
:
<HiddenImagePlaceholder />}
{spaceFiller}
</div>
{fileBody}
Expand Down

0 comments on commit 5735af5

Please sign in to comment.