Skip to content

Commit 351d708

Browse files
committed
feat: add file icons
1 parent d5ebcf4 commit 351d708

File tree

9 files changed

+347
-3
lines changed

9 files changed

+347
-3
lines changed

src/components/input-message/index.module.css

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@
4141
}
4242

4343
#answer_block_cancel_icon{
44-
color: rgb(0, 152, 234);
44+
/*color: rgb(0, 152, 234);*/
4545
font-size: 24px;
46+
opacity: 0.5;
47+
stroke-width: 1px;
4648
}
4749

4850
#inputs{
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
.background{
2+
display: grid;
3+
grid-template-columns: auto 1fr;
4+
gap: 8px;
5+
padding: 4px;
6+
cursor: pointer;
7+
transition: all .3s ease;
8+
}
9+
10+
.background:hover{
11+
background-color: #ccedff;
12+
border-radius: 8px;
13+
14+
.file_logo{
15+
display: none;
16+
}
17+
18+
.download_logo{
19+
display: inline;
20+
animation: show_slowly .3s linear forwards;
21+
}
22+
}
23+
24+
.name{
25+
display: grid;
26+
overflow: hidden;
27+
text-overflow: ellipsis;
28+
display: -moz-box;
29+
-moz-box-orient: vertical;
30+
display: -webkit-box;
31+
-webkit-line-clamp: 1;
32+
-webkit-box-orient: vertical;
33+
}
34+
35+
.file_background{
36+
width: 40px;
37+
height: 40px;
38+
border-radius: 30px;
39+
background-color: #0098ea;
40+
display: grid;
41+
justify-content: center;
42+
align-items: center;
43+
cursor: pointer;
44+
}
45+
46+
.file_logo{
47+
font-size: 25px;
48+
color: white;
49+
}
50+
51+
.download_logo{
52+
display: none;
53+
font-size: 25px;
54+
color: white;
55+
}
56+
57+
.file_inf{
58+
display: grid;
59+
overflow: hidden;
60+
}
61+
62+
.size{
63+
font-weight: 500;
64+
opacity: 0.4;
65+
}
66+
67+
68+
@keyframes show_slowly{
69+
from {
70+
visibility: hidden;
71+
opacity: 0;
72+
}
73+
to {
74+
visibility: visible;
75+
opacity: 1;
76+
}
77+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import { FC, memo, useCallback } from 'react';
2+
import { PropsType } from './props.type.ts';
3+
import { useFileSize } from '../../common/hooks/use-file-size.ts';
4+
import styles from './index.module.css';
5+
import { IoMusicalNotesSharp } from 'react-icons/io5';
6+
import { DownloadFile } from '../../root/api/files';
7+
import { FileMap, MimetypeEnum } from '../../root/types/files/file.type.ts';
8+
import { CiFileOn } from 'react-icons/ci';
9+
import { TbBrandOpenvpn } from 'react-icons/tb';
10+
import {
11+
BsCode,
12+
BsDownload,
13+
BsFiletypeCss,
14+
BsFiletypeCsv,
15+
BsFiletypeDoc,
16+
BsFiletypeDocx,
17+
BsFiletypeHtml,
18+
BsFiletypeJs,
19+
BsFiletypeJsx,
20+
BsFiletypePdf,
21+
BsFiletypePpt,
22+
BsFiletypeTsx,
23+
BsFiletypeTxt,
24+
BsFiletypeXls,
25+
BsFiletypeXlsx,
26+
BsFiletypeYml,
27+
BsFileZip,
28+
} from 'react-icons/bs';
29+
30+
export const MessageFile: FC<PropsType> = memo(({ file }) => {
31+
const size = useFileSize(file.size);
32+
33+
const download = useCallback(() => {
34+
DownloadFile(file);
35+
}, [file]);
36+
37+
console.log([file.originalName, file.mimeType]);
38+
39+
return (
40+
<div className={styles.background} onClick={download}>
41+
<div className={styles.file_background}>
42+
{FileMap.get('MP3')?.includes(file.mimeType) && <IoMusicalNotesSharp className={styles.file_logo} />}
43+
{FileMap.get('ZIP')?.includes(file.mimeType) && <BsFileZip className={styles.file_logo} />}
44+
{FileMap.get('SH')?.includes(file.mimeType) && <BsCode className={styles.file_logo} />}
45+
{FileMap.get('PPT')?.includes(file.mimeType) && <BsFiletypePpt className={styles.file_logo} />}
46+
{!Object.values(MimetypeEnum).includes(file.mimeType) && <CiFileOn className={styles.file_logo} />}
47+
{file.mimeType === MimetypeEnum.HTML && <BsFiletypeHtml className={styles.file_logo} />}
48+
{file.mimeType === MimetypeEnum.DOCX && <BsFiletypeDocx className={styles.file_logo} />}
49+
{file.mimeType === MimetypeEnum.OVPN && <TbBrandOpenvpn className={styles.file_logo} />}
50+
{file.mimeType === MimetypeEnum.PDF && <BsFiletypePdf className={styles.file_logo} />}
51+
{file.mimeType === MimetypeEnum.CSS && <BsFiletypeCss className={styles.file_logo} />}
52+
{file.mimeType === MimetypeEnum.JS && <BsFiletypeJs className={styles.file_logo} />}
53+
{file.mimeType === MimetypeEnum.TS && <BsFiletypeJs className={styles.file_logo} />}
54+
{file.mimeType === MimetypeEnum.TSX && <BsFiletypeTsx className={styles.file_logo} />}
55+
{file.mimeType === MimetypeEnum.JSX && <BsFiletypeJsx className={styles.file_logo} />}
56+
{file.mimeType === MimetypeEnum.TXT && <BsFiletypeTxt className={styles.file_logo} />}
57+
{file.mimeType === MimetypeEnum.CSV && <BsFiletypeCsv className={styles.file_logo} />}
58+
{file.mimeType === MimetypeEnum.YML && <BsFiletypeYml className={styles.file_logo} />}
59+
{file.mimeType === MimetypeEnum.PPT && <BsFiletypePpt className={styles.file_logo} />}
60+
{file.mimeType === MimetypeEnum.XLSX && <BsFiletypeXlsx className={styles.file_logo} />}
61+
{file.mimeType === MimetypeEnum.XLS && <BsFiletypeXls className={styles.file_logo} />}
62+
{file.mimeType === MimetypeEnum.DOC && <BsFiletypeDoc className={styles.file_logo} />}
63+
{file.mimeType === MimetypeEnum.BINARY && file.originalName.includes('.ovpn') && (
64+
<TbBrandOpenvpn className={styles.file_logo} />
65+
)}
66+
<BsDownload className={styles.download_logo} />
67+
</div>
68+
<div className={styles.file_inf}>
69+
<div className={styles.name}>{file.originalName}</div>
70+
<div className={styles.size}>{size}</div>
71+
</div>
72+
</div>
73+
);
74+
});
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { FileType } from '../../root/types/files/file.type.ts';
2+
3+
export type PropsType = {
4+
file: FileType;
5+
};

src/components/message/index.module.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,7 @@
4949
padding-left: 5px;
5050
font-size: 13px;
5151
}
52+
53+
.file_list {
54+
display: grid;
55+
}

src/components/message/index.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { ParentMessage } from '../parent-message';
88
import styles2 from '../menu-message/index.module.css';
99
import { useAppSelector } from '../../root/store';
1010
import { ContextChat } from '../../pages/chat/context/chat-context.tsx';
11+
import { MessageFile } from '../message-file';
1112

1213
const Message: FC<PropsType> = memo((props) => {
1314
const { number, type, findMessage } = props;
@@ -73,7 +74,14 @@ const Message: FC<PropsType> = memo((props) => {
7374
return (
7475
<>
7576
<div ref={observerTarget} id={elementId} className={`${styles.background}`}>
76-
{props.parentMessage && <ParentMessage {...{ ...props.parentMessage, findMessage }} />}
77+
{!!props.parentMessage && <ParentMessage {...{ ...props.parentMessage, findMessage }} />}
78+
{!!props.files?.length && (
79+
<div className={styles.file_list}>
80+
{props.files.map((file, number) => (
81+
<MessageFile key={number} file={file} />
82+
))}
83+
</div>
84+
)}
7785
<RenderMessage message={visibleMessage} type={type} files={props.files} />
7886
<div className={`${styles.left_div2} text_translate`}>{time}</div>
7987
</div>

src/components/preview-file/index.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,66 @@ enum FileTypeEnum {
1515
VPN = 'vpn',
1616
}
1717

18+
// async function getAudioLevels(file: File, segments = 60): Promise<void> {
19+
// if (file.type !== FileTypeEnum.AUDIO) return;
20+
// const arrayBuffer = await file.arrayBuffer();
21+
//
22+
// const audioCtx = new AudioContext();
23+
// const audioBuffer = await audioCtx.decodeAudioData(arrayBuffer);
24+
//
25+
// const channelData = audioBuffer.getChannelData(0); // берём первый канал
26+
// const samplesPerSegment = Math.floor(channelData.length / segments);
27+
//
28+
// const levels: number[] = [];
29+
//
30+
// for (let i = 0; i < segments; i++) {
31+
// const start = i * samplesPerSegment;
32+
// const end = start + samplesPerSegment;
33+
// const slice = channelData.subarray(start, end);
34+
//
35+
// // RMS для сегмента
36+
// let sumSquares = 0;
37+
// for (let j = 0; j < slice.length; j++) {
38+
// sumSquares += slice[j] * slice[j];
39+
// }
40+
// const rms = Math.sqrt(sumSquares / slice.length);
41+
//
42+
// // Переводим в дБFS (отрицательное число, ближе к 0 = громче)
43+
// const db = 20 * Math.log10(rms);
44+
//
45+
// // Для удобства нормализуем в 0..1
46+
// const normalized = Math.max(0, (db + 100) / 100);
47+
// levels.push(normalized);
48+
// }
49+
// console.log(levels);
50+
// }
51+
1852
export const PreviewFile: FC<PropsType> = ({ file, number }) => {
1953
const size = useFileSize(file.size);
2054
const [url, setUrl] = useState<string>('');
2155
const [type, setType] = useState<FileTypeEnum>();
2256
const { setFiles, files } = useContext(ContextMedia)!;
57+
//
58+
// useEffect(() => {
59+
// if (!file) return;
60+
//
61+
// const audio = new Audio();
62+
// audio.src = URL.createObjectURL(file);
63+
//
64+
// const handler = () => {
65+
// console.log('Длительность:', audio.duration, 'секунд');
66+
// URL.revokeObjectURL(audio.src); // очищаем, чтобы не было утечек памяти
67+
// };
68+
//
69+
// audio.addEventListener('loadedmetadata', handler);
70+
// audio.play();
71+
//
72+
// getAudioLevels(file);
73+
//
74+
// return () => {
75+
// audio.removeEventListener('loadedmetadata', handler);
76+
// };
77+
// }, [file]);
2378

2479
useEffect(() => {
2580
const url = URL.createObjectURL(file);

src/root/api/files/index.ts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { IData } from '../index.ts';
22
import { Envs } from '../../../common/config/envs/envs.ts';
3+
import { FileType } from '../../types/files/file.type.ts';
34

45
export const uploadFile = async (body: FormData): Promise<IData<string[]>> => {
56
const response = await fetch(`${Envs.chatsServiceUrl}/files/upload`, { method: 'POST', body }).then((response) =>
@@ -8,3 +9,24 @@ export const uploadFile = async (body: FormData): Promise<IData<string[]>> => {
89

910
return response as IData<string[]>;
1011
};
12+
13+
export const DownloadFile = async (file: FileType): Promise<void> => {
14+
const response = await fetch(`${Envs.chatsServiceUrl}/files/${file.id}`);
15+
16+
if (!response.ok) {
17+
throw new Error(`Ошибка загрузки файла: ${response.statusText}`);
18+
}
19+
20+
const blob = await response.blob();
21+
const filename = file.originalName;
22+
const url = window.URL.createObjectURL(blob);
23+
24+
const a = document.createElement('a');
25+
a.href = url;
26+
a.download = filename;
27+
document.body.appendChild(a);
28+
a.click();
29+
30+
a.remove();
31+
window.URL.revokeObjectURL(url);
32+
};

0 commit comments

Comments
 (0)