mirror of
https://github.com/stoatchat/for-legacy-web.git
synced 2026-03-07 09:25:27 +00:00
Merge branch 'master' into 'cleanup'
# Conflicts: # src/components/common/LocaleSelector.tsx
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import { ulid } from "ulid";
|
||||
import { Text } from "preact-i18n";
|
||||
import Tooltip, { PermissionTooltip } from "../Tooltip";
|
||||
import { Channel } from "revolt.js";
|
||||
import styled from "styled-components";
|
||||
import { dispatch } from "../../../redux";
|
||||
import { defer } from "../../../lib/defer";
|
||||
import IconButton from "../../ui/IconButton";
|
||||
import { X } from '@styled-icons/boxicons-regular';
|
||||
import { PermissionTooltip } from "../Tooltip";
|
||||
import { Send } from '@styled-icons/boxicons-solid';
|
||||
import { debounce } from "../../../lib/debounce";
|
||||
import Axios, { CancelTokenSource } from "axios";
|
||||
@@ -13,7 +13,6 @@ import { useTranslation } from "../../../lib/i18n";
|
||||
import { Reply } from "../../../redux/reducers/queue";
|
||||
import { connectState } from "../../../redux/connector";
|
||||
import { SoundContext } from "../../../context/Settings";
|
||||
import { WithDispatcher } from "../../../redux/reducers";
|
||||
import { takeError } from "../../../context/revoltjs/util";
|
||||
import TextAreaAutoSize from "../../../lib/TextAreaAutoSize";
|
||||
import AutoComplete, { useAutoComplete } from "../AutoComplete";
|
||||
@@ -30,9 +29,8 @@ import { ShieldX } from "@styled-icons/boxicons-regular";
|
||||
|
||||
import ReplyBar from "./bars/ReplyBar";
|
||||
import FilePreview from './bars/FilePreview';
|
||||
import { Styleshare } from "@styled-icons/simple-icons";
|
||||
|
||||
type Props = WithDispatcher & {
|
||||
type Props = {
|
||||
channel: Channel;
|
||||
draft?: string;
|
||||
};
|
||||
@@ -77,7 +75,7 @@ const Action = styled.div`
|
||||
// ! FIXME: add to app config and load from app config
|
||||
export const CAN_UPLOAD_AT_ONCE = 5;
|
||||
|
||||
function MessageBox({ channel, draft, dispatcher }: Props) {
|
||||
function MessageBox({ channel, draft }: Props) {
|
||||
const [ uploadState, setUploadState ] = useState<UploadState>({ type: 'none' });
|
||||
const [ typing, setTyping ] = useState<boolean | number>(false);
|
||||
const [ replies, setReplies ] = useState<Reply[]>([]);
|
||||
@@ -102,13 +100,13 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
|
||||
|
||||
function setMessage(content?: string) {
|
||||
if (content) {
|
||||
dispatcher({
|
||||
dispatch({
|
||||
type: "SET_DRAFT",
|
||||
channel: channel._id,
|
||||
content
|
||||
});
|
||||
} else {
|
||||
dispatcher({
|
||||
dispatch({
|
||||
type: "CLEAR_DRAFT",
|
||||
channel: channel._id
|
||||
});
|
||||
@@ -148,7 +146,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
|
||||
playSound('outbound');
|
||||
|
||||
const nonce = ulid();
|
||||
dispatcher({
|
||||
dispatch({
|
||||
type: "QUEUE_ADD",
|
||||
nonce,
|
||||
channel: channel._id,
|
||||
@@ -171,7 +169,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
|
||||
replies
|
||||
});
|
||||
} catch (error) {
|
||||
dispatcher({
|
||||
dispatch({
|
||||
type: "QUEUE_FAIL",
|
||||
error: takeError(error),
|
||||
nonce
|
||||
@@ -383,7 +381,7 @@ function MessageBox({ channel, draft, dispatcher }: Props) {
|
||||
)
|
||||
}
|
||||
|
||||
export default connectState<Omit<Props, "dispatcher" | "draft">>(MessageBox, (state, { channel }) => {
|
||||
export default connectState<Omit<Props, "dispatch" | "draft">>(MessageBox, (state, { channel }) => {
|
||||
return {
|
||||
draft: state.drafts[channel._id]
|
||||
}
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
.attachment {
|
||||
display: grid;
|
||||
grid-auto-columns: min(100%, 480px);
|
||||
grid-auto-flow: row dense;
|
||||
|
||||
width: max-content;
|
||||
|
||||
border-radius: 6px;
|
||||
margin: .125rem 0 .125rem;
|
||||
|
||||
height: auto;
|
||||
|
||||
max-height: 640px;
|
||||
max-width: min(480px, 100%);
|
||||
|
||||
object-fit: contain;
|
||||
|
||||
&[data-spoiler="true"] {
|
||||
filter: blur(30px);
|
||||
pointer-events: none;
|
||||
@@ -20,6 +19,16 @@
|
||||
|
||||
&.image {
|
||||
cursor: pointer;
|
||||
|
||||
max-height: 640px;
|
||||
max-width: min(480px, 100%);
|
||||
|
||||
object-fit: contain;
|
||||
|
||||
&.loaded {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
&.video {
|
||||
@@ -29,8 +38,15 @@
|
||||
}
|
||||
|
||||
video {
|
||||
width: 100%;
|
||||
border-radius: 0 0 6px 6px;
|
||||
|
||||
max-height: 640px;
|
||||
max-width: min(480px, 100%);
|
||||
}
|
||||
|
||||
video.loaded {
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,11 +75,12 @@
|
||||
}
|
||||
|
||||
&.text {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
overflow: hidden;
|
||||
grid-auto-columns: unset;
|
||||
|
||||
border-radius: 6px;
|
||||
flex-direction: column;
|
||||
|
||||
.textContent {
|
||||
height: 140px;
|
||||
@@ -92,35 +109,48 @@
|
||||
}
|
||||
}
|
||||
|
||||
.actions.imageAction {
|
||||
grid-template:
|
||||
"name icon download" auto
|
||||
"size icon download" auto
|
||||
/ minmax(20px, 1fr) min-content min-content;
|
||||
}
|
||||
|
||||
.actions {
|
||||
gap: 8px;
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
overflow: none;
|
||||
max-width: 100%;
|
||||
display: grid;
|
||||
grid-template:
|
||||
"icon name download" auto
|
||||
"icon size download" auto
|
||||
/ min-content minmax(20px, 1fr) min-content;
|
||||
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
column-gap: 8px;
|
||||
|
||||
width: 100%;
|
||||
padding: 8px;
|
||||
overflow: none;
|
||||
|
||||
color: var(--foreground);
|
||||
background: var(--secondary-background);
|
||||
|
||||
> svg {
|
||||
flex-shrink: 0;
|
||||
span {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
.filesize {
|
||||
grid-area: size;
|
||||
|
||||
> span {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
font-size: 10px;
|
||||
color: var(--secondary-foreground);
|
||||
}
|
||||
|
||||
.filesize {
|
||||
font-size: 10px;
|
||||
color: var(--secondary-foreground);
|
||||
}
|
||||
.downloadIcon {
|
||||
grid-area: download;
|
||||
}
|
||||
|
||||
.iconType {
|
||||
grid-area: icon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ export default function Attachment({ attachment, hasContent }: Props) {
|
||||
const { openScreen } = useIntermediate();
|
||||
const { filename, metadata } = attachment;
|
||||
const [ spoiler, setSpoiler ] = useState(filename.startsWith("SPOILER_"));
|
||||
const [ loaded, setLoaded ] = useState(false)
|
||||
|
||||
const url = client.generateFileURL(attachment, { width: MAX_ATTACHMENT_WIDTH * 1.5 }, true);
|
||||
|
||||
@@ -44,7 +45,7 @@ export default function Attachment({ attachment, hasContent }: Props) {
|
||||
height={metadata.height}
|
||||
data-spoiler={spoiler}
|
||||
data-has-content={hasContent}
|
||||
className={classNames(styles.attachment, styles.image)}
|
||||
className={classNames(styles.attachment, styles.image, loaded && styles.loaded)}
|
||||
onClick={() =>
|
||||
openScreen({ id: "image_viewer", attachment })
|
||||
}
|
||||
@@ -52,6 +53,7 @@ export default function Attachment({ attachment, hasContent }: Props) {
|
||||
ev.button === 1 &&
|
||||
window.open(url, "_blank")
|
||||
}
|
||||
onLoad={() => setLoaded(true)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -85,11 +87,15 @@ export default function Attachment({ attachment, hasContent }: Props) {
|
||||
<AttachmentActions attachment={attachment} />
|
||||
<video
|
||||
src={url}
|
||||
width={metadata.width}
|
||||
height={metadata.height}
|
||||
className={classNames(loaded && styles.loaded)}
|
||||
controls
|
||||
onMouseDown={ev =>
|
||||
ev.button === 1 &&
|
||||
window.open(url, "_blank")
|
||||
}
|
||||
onLoadedMetadata={() => setLoaded(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Attachment } from "revolt.js/dist/api/objects";
|
||||
import { determineFileSize } from '../../../../lib/fileSize';
|
||||
import { AppContext } from '../../../../context/revoltjs/RevoltClient';
|
||||
import { Download, LinkExternal, File, Headphone, Video } from '@styled-icons/boxicons-regular';
|
||||
import classNames from 'classnames';
|
||||
|
||||
interface Props {
|
||||
attachment: Attachment;
|
||||
@@ -24,17 +25,15 @@ export default function AttachmentActions({ attachment }: Props) {
|
||||
switch (metadata.type) {
|
||||
case 'Image':
|
||||
return (
|
||||
<div className={styles.actions}>
|
||||
<div className={styles.info}>
|
||||
<span className={styles.filename}>{filename}</span>
|
||||
<span className={styles.filesize}>{metadata.width + 'x' + metadata.height} ({filesize})</span>
|
||||
</div>
|
||||
<a href={open_url} target="_blank">
|
||||
<div className={classNames(styles.actions, styles.imageAction)}>
|
||||
<span className={styles.filename}>{filename}</span>
|
||||
<span className={styles.filesize}>{metadata.width + 'x' + metadata.height} ({filesize})</span>
|
||||
<a href={open_url} target="_blank" className={styles.iconType} >
|
||||
<IconButton>
|
||||
<LinkExternal size={24} />
|
||||
</IconButton>
|
||||
</a>
|
||||
<a href={download_url} download target="_blank">
|
||||
<a href={download_url} className={styles.downloadIcon} download target="_blank">
|
||||
<IconButton>
|
||||
<Download size={24} />
|
||||
</IconButton>
|
||||
@@ -43,13 +42,11 @@ export default function AttachmentActions({ attachment }: Props) {
|
||||
)
|
||||
case 'Audio':
|
||||
return (
|
||||
<div className={styles.actions}>
|
||||
<Headphone size={24} />
|
||||
<div className={styles.info}>
|
||||
<span className={styles.filename}>{filename}</span>
|
||||
<span className={styles.filesize}>{filesize}</span>
|
||||
</div>
|
||||
<a href={download_url} download target="_blank">
|
||||
<div className={classNames(styles.actions, styles.audioAction)}>
|
||||
<Headphone size={24} className={styles.iconType} />
|
||||
<span className={styles.filename}>{filename}</span>
|
||||
<span className={styles.filesize}>{filesize}</span>
|
||||
<a href={download_url} className={styles.downloadIcon} download target="_blank">
|
||||
<IconButton>
|
||||
<Download size={24} />
|
||||
</IconButton>
|
||||
@@ -58,13 +55,11 @@ export default function AttachmentActions({ attachment }: Props) {
|
||||
)
|
||||
case 'Video':
|
||||
return (
|
||||
<div className={styles.actions}>
|
||||
<Video size={24} />
|
||||
<div className={styles.info}>
|
||||
<span className={styles.filename}>{filename}</span>
|
||||
<span className={styles.filesize}>{metadata.width + 'x' + metadata.height} ({filesize})</span>
|
||||
</div>
|
||||
<a href={download_url} download target="_blank">
|
||||
<div className={classNames(styles.actions, styles.videoAction)}>
|
||||
<Video size={24} className={styles.iconType} />
|
||||
<span className={styles.filename}>{filename}</span>
|
||||
<span className={styles.filesize}>{metadata.width + 'x' + metadata.height} ({filesize})</span>
|
||||
<a href={download_url} className={styles.downloadIcon} download target="_blank">
|
||||
<IconButton>
|
||||
<Download size={24} />
|
||||
</IconButton>
|
||||
@@ -74,12 +69,10 @@ export default function AttachmentActions({ attachment }: Props) {
|
||||
default:
|
||||
return (
|
||||
<div className={styles.actions}>
|
||||
<File size={24} />
|
||||
<div className={styles.info}>
|
||||
<span className={styles.filename}>{filename}</span>
|
||||
<span className={styles.filesize}>{filesize}</span>
|
||||
</div>
|
||||
<a href={download_url} download target="_blank">
|
||||
<File size={24} className={styles.iconType} />
|
||||
<span className={styles.filename}>{filename}</span>
|
||||
<span className={styles.filesize}>{filesize}</span>
|
||||
<a href={download_url} className={styles.downloadIcon} download target="_blank">
|
||||
<IconButton>
|
||||
<Download size={24} />
|
||||
</IconButton>
|
||||
|
||||
Reference in New Issue
Block a user