first version

This commit is contained in:
2021-11-01 22:30:51 +08:00
parent da2a96865c
commit d964439624
143 changed files with 29617 additions and 101 deletions
+66
View File
@@ -0,0 +1,66 @@
import dayjs from 'dayjs';
import store from '../state/store';
import { Contact, Message } from '../state/reducer';
/** 构建本地消息 */
export default function composeMessage({
type,
target,
data,
filename,
fileSize,
localFileUrl,
}: {
type: 'text' | 'img' | 'emotion' | 'file';
target: string;
data: any;
filename?: string;
fileSize?: number;
localFileUrl?: string;
}): Message {
const state = store.getState();
const to = state.allContacts.get(target);
const isGroupMessage = state.groups.includes(to as Contact);
if (!to) {
throw Error('目标联系人不存在');
}
const msg = {
type,
messageID: Math.random().toString(),
fileSize: fileSize,
displayFilename: filename,
fileUrl: localFileUrl,
speakingGroupMemberID: isGroupMessage ? state.user?.id : null,
speakingGroupMemberName: isGroupMessage
? state.user?.displayName
: null,
fromID: state.user?.id,
fromName: state.user?.displayName,
fromNickName: state.user?.nickname,
toID: target,
toName: to?.displayName,
toNickName: to?.nickname,
createTimestamp: dayjs().unix(),
isGroupMessage: isGroupMessage,
isSentBySelf: true,
pending: true,
percent: 0,
} as Message;
switch (type) {
case 'text':
msg.content = data;
break;
case 'img':
msg.content = '[图片]';
break;
case 'emotion':
msg.content = '[表情]';
break;
case 'file':
msg.content = '[文件]' + filename;
break;
default:
msg.content = '不支持的消息类型';
}
return msg;
}
+21
View File
@@ -0,0 +1,21 @@
import Message from '../components/Message';
import { socket } from '../socket';
export default function fetch<T = any>(
event: string,
data = {},
{ toast = true } = {},
): Promise<[string | null, T | null]> {
return new Promise((resolve) => {
socket.emit(event, data, (res: any) => {
if (typeof res === 'string') {
if (toast) {
Message.error(res);
}
resolve([res, null]);
} else {
resolve([null, res]);
}
});
});
}
+87
View File
@@ -0,0 +1,87 @@
import HuaJi0 from '@fiora/assets/images/huaji/0.jpg';
import HuaJi1 from '@fiora/assets/images/huaji/1.gif';
import HuaJi2 from '@fiora/assets/images/huaji/2.jpeg';
import HuaJi3 from '@fiora/assets/images/huaji/3.jpeg';
import HuaJi4 from '@fiora/assets/images/huaji/4.jpeg';
import HuaJi5 from '@fiora/assets/images/huaji/5.jpg';
import HuaJi6 from '@fiora/assets/images/huaji/6.jpeg';
import HuaJi7 from '@fiora/assets/images/huaji/7.jpg';
import HuaJi8 from '@fiora/assets/images/huaji/8.jpeg';
import HuaJi9 from '@fiora/assets/images/huaji/9.jpeg';
import HuaJi10 from '@fiora/assets/images/huaji/10.jpeg';
import HuaJi11 from '@fiora/assets/images/huaji/11.jpeg';
import HuaJi12 from '@fiora/assets/images/huaji/12.jpeg';
import HuaJi13 from '@fiora/assets/images/huaji/13.jpeg';
import HuaJi14 from '@fiora/assets/images/huaji/14.jpeg';
import HuaJi15 from '@fiora/assets/images/huaji/15.jpeg';
import HuaJi16 from '@fiora/assets/images/huaji/16.jpeg';
import HuaJi17 from '@fiora/assets/images/huaji/17.jpeg';
import HuaJi18 from '@fiora/assets/images/huaji/18.gif';
import HuaJi19 from '@fiora/assets/images/huaji/19.jpeg';
import HuaJi20 from '@fiora/assets/images/huaji/20.jpeg';
import HuaJi21 from '@fiora/assets/images/huaji/21.jpeg';
import HuaJi22 from '@fiora/assets/images/huaji/22.jpeg';
import HuaJi23 from '@fiora/assets/images/huaji/23.jpeg';
import HuaJi24 from '@fiora/assets/images/huaji/24.jpeg';
import HuaJi25 from '@fiora/assets/images/huaji/25.png';
import HuaJi26 from '@fiora/assets/images/huaji/26.jpeg';
import HuaJi27 from '@fiora/assets/images/huaji/27.jpeg';
import HuaJi28 from '@fiora/assets/images/huaji/28.jpeg';
import HuaJi29 from '@fiora/assets/images/huaji/29.jpeg';
import HuaJi30 from '@fiora/assets/images/huaji/30.jpeg';
import HuaJi31 from '@fiora/assets/images/huaji/31.jpeg';
import HuaJi32 from '@fiora/assets/images/huaji/32.jpg';
import HuaJi33 from '@fiora/assets/images/huaji/33.gif';
import HuaJi34 from '@fiora/assets/images/huaji/34.gif';
import HuaJi35 from '@fiora/assets/images/huaji/35.gif';
import HuaJi36 from '@fiora/assets/images/huaji/36.gif';
type Huaji = {
[key: number]: string;
};
const huaji: Huaji = {
0: `${HuaJi0}?width=250&height=250&huaji=true`,
1: `${HuaJi1}?width=300&height=300&huaji=true`,
2: `${HuaJi2}?width=245&height=206&huaji=true`,
3: `${HuaJi3}?width=225&height=225&huaji=true`,
4: `${HuaJi4}?width=224&height=225&huaji=true`,
5: `${HuaJi5}?width=200&height=200&huaji=true`,
6: `${HuaJi6}?width=284&height=177&huaji=true7`,
7: `${HuaJi7}?width=300&height=300&huaji=true`,
8: `${HuaJi8}?width=225&height=225&huaji=true`,
9: `${HuaJi9}?width=204&height=247&huaji=true`,
10: `${HuaJi10}?width=223&height=226&huaji=true`,
11: `${HuaJi11}?width=198&height=255&huaji=true`,
12: `${HuaJi12}?width=212&height=237&huaji=true`,
13: `${HuaJi13}?width=290&height=174&huaji=true`,
14: `${HuaJi14}?width=224&height=224&huaji=true`,
15: `${HuaJi15}?width=224&height=224&huaji=true`,
16: `${HuaJi16}?width=225&height=225&huaji=true`,
17: `${HuaJi17}?width=225&height=225&huaji=true`,
18: `${HuaJi18}?width=100&height=110&huaji=true`,
19: `${HuaJi19}?width=225&height=225&huaji=true`,
20: `${HuaJi20}?width=225&height=225&huaji=true`,
21: `${HuaJi21}?width=225&height=225&huaji=true`,
22: `${HuaJi22}?width=245&height=206&huaji=true`,
23: `${HuaJi23}?width=225&height=225&huaji=true`,
24: `${HuaJi24}?width=225&height=225&huaji=true`,
25: `${HuaJi25}?width=225&height=225&huaji=true`,
26: `${HuaJi26}?width=225&height=225&huaji=true`,
27: `${HuaJi27}?width=180&height=180&huaji=true`,
28: `${HuaJi28}?width=235&height=215&huaji=true`,
29: `${HuaJi29}?width=278&height=182&huaji=true`,
30: `${HuaJi30}?width=228&height=221&huaji=true`,
31: `${HuaJi31}?width=239&height=211&huaji=true`,
32: `${HuaJi32}?width=220&height=220&huaji=true`,
33: `${HuaJi33}?width=220&height=220&huaji=true`,
34: `${HuaJi34}?width=164&height=192&huaji=true`,
35: `${HuaJi35}?width=130&height=62&huaji=true`,
36: `${HuaJi36}?width=187&height=144&huaji=true`,
};
const HuajiaCount = Object.keys(huaji).length;
export default function getRandomHuaji() {
const number = Math.floor(Math.random() * HuajiaCount);
return huaji[number];
}
+78
View File
@@ -0,0 +1,78 @@
/**
* 阻止目标元素下不必要的滚动事件. 解决ios橡皮筋效果问题
* @param {HTMLElement} targetElement 目标元素
*/
export default function inobounce(targetElement: HTMLElement) {
let startX = 0;
let startY = 0;
function handleTouchStart(e: any) {
startY = e.touches ? e.touches[0].screenY : e.screenY;
startX = e.touches ? e.touches[0].screenX : e.screenX;
}
function handleTouchMove(e: any) {
let el = e.target;
while (el !== e.currentTarget) {
const style = window.getComputedStyle(el);
if (!style) {
break;
}
if (
el.nodeName === 'INPUT' &&
el.getAttribute('type') === 'range'
) {
return;
}
const overflowY = style.getPropertyValue('overflow-y');
const height = +parseFloat(
style.getPropertyValue('height'),
).toFixed(0);
const isScrollableY =
overflowY === 'auto' || overflowY === 'scroll';
const canScrollY = el.scrollHeight > el.offsetHeight;
if (isScrollableY && canScrollY) {
const curY = e.touches ? e.touches[0].screenY : e.screenY;
const isAtTop = startY <= curY && el.scrollTop === 0;
const isAtBottom =
startY >= curY && el.scrollHeight - el.scrollTop === height;
if (isAtTop || isAtBottom) {
e.preventDefault();
}
return;
}
const overflowX = style.getPropertyValue('overflow-x');
const width = +parseFloat(style.getPropertyValue('width')).toFixed(
0,
);
const isScrollableX =
overflowX === 'auto' || overflowX === 'scroll';
const canScrollX = el.scrollWidth > el.offsetWidth;
if (isScrollableX && canScrollX) {
const curX = e.touches ? e.touches[0].screenX : e.screenX;
const isAtLeft = startX <= curX && el.scrollLeft === 0;
const isAtRight =
startX >= curX && el.scrollWidth - el.scrollLeft === width;
if (isAtLeft || isAtRight) {
e.preventDefault();
}
return;
}
el = el.parentNode;
}
e.preventDefault();
}
if (targetElement) {
targetElement.addEventListener('touchstart', handleTouchStart);
targetElement.addEventListener('touchmove', handleTouchMove);
}
}
+20
View File
@@ -0,0 +1,20 @@
export default function notification(
title: string,
icon: string,
body: string,
tag = 'tag',
duration = 3000,
) {
if (window.Notification && window.Notification.permission === 'granted') {
const n = new window.Notification(title, {
icon,
body,
tag,
});
n.onclick = function handleClick() {
window.focus();
this.close();
};
setTimeout(n.close.bind(n), duration);
}
}
+53
View File
@@ -0,0 +1,53 @@
import DefaultSound from '@fiora/assets/audios/default.mp3';
import AppleSound from '@fiora/assets/audios/apple.mp3';
import PcQQSound from '@fiora/assets/audios/pcqq.mp3';
import MobileQQSound from '@fiora/assets/audios/mobileqq.mp3';
import MoMoSound from '@fiora/assets/audios/momo.mp3';
import HuaJiSound from '@fiora/assets/audios/huaji.mp3';
type Sounds = {
[key: string]: string;
};
const sounds: Sounds = {
default: DefaultSound,
apple: AppleSound,
pcqq: PcQQSound,
mobileqq: MobileQQSound,
momo: MoMoSound,
huaji: HuaJiSound,
};
let prevType = 'default';
const $audio = document.createElement('audio');
const $source = document.createElement('source');
$audio.volume = 0.6;
$source.setAttribute('type', 'audio/mp3');
$source.setAttribute('src', sounds[prevType]);
$audio.appendChild($source);
document.body.appendChild($audio);
let isPlaying = false;
async function play() {
if (!isPlaying) {
isPlaying = true;
try {
await $audio.play();
} catch (err) {
console.warn('播放新消息提示音失败', err.message);
} finally {
isPlaying = false;
}
}
}
export default function playSound(type = 'default') {
if (type !== prevType) {
$source.setAttribute('src', sounds[type]);
$audio.load();
prevType = type;
}
play();
}
+98
View File
@@ -0,0 +1,98 @@
export interface ReadFileResult {
/** 文件名 */
filename: string;
/** 文件拓展名 */
ext: string;
/** 文件类型 */
type: string;
/** 文件内容 */
result: Blob | ArrayBuffer | string;
/** 文件长度 */
length: number;
}
/**
* 读取本地文件
* @param {string} resultType 数据类型, {blob|base64}, 默认blob
* @param {string} accept 可选文件类型, 默认 * / *
*/
export default async function readDiskFIle(
resultType = 'blob',
accept = '*/*',
) {
const result: ReadFileResult | null = await new Promise((resolve) => {
const $input = document.createElement('input');
$input.style.display = 'none';
$input.setAttribute('type', 'file');
$input.setAttribute('accept', accept);
// 判断用户是否点击取消, 原生没有提供专门事件, 用hack的方法实现
$input.onclick = () => {
// @ts-ignore
$input.value = null;
document.body.onfocus = () => {
// onfocus事件会比$input.onchange事件先触发, 因此需要延迟一段时间
setTimeout(() => {
if ($input.value.length === 0) {
resolve(null);
}
document.body.onfocus = null;
}, 500);
};
};
$input.onchange = (e: Event) => {
// @ts-ignore
const file = e.target.files[0];
if (!file) {
return;
}
const reader = new FileReader();
reader.onloadend = function handleLoad() {
if (!this.result) {
resolve(null);
return;
}
// @ts-ignore
resolve({
filename: file.name,
ext: file.name
.split('.')
.pop()
.toLowerCase(),
type: file.type,
// @ts-ignore
result: this.result,
length:
resultType === 'blob'
? (this.result as ArrayBuffer).byteLength
: (this.result as string).length,
});
};
switch (resultType) {
case 'blob': {
reader.readAsArrayBuffer(file);
break;
}
case 'base64': {
reader.readAsDataURL(file);
break;
}
default: {
reader.readAsArrayBuffer(file);
}
}
};
$input.click();
});
if (result && resultType === 'blob') {
result.result = new Blob(
[new Uint8Array(result.result as ArrayBuffer)],
{
type: result.type,
},
);
}
return result;
}
+14
View File
@@ -0,0 +1,14 @@
/**
* set global css variable
* @param color primary color, three numbers split with comma, like 255,255,255
* @param textColor text colore, format like color
*/
export default function setCssVariable(color: string, textColor: string) {
let cssText = '';
for (let i = 0; i <= 10; i++) {
cssText += `--primary-color-${i}:rgba(${color}, ${i /
10});--primary-color-${i}_5:rgba(${color}, ${(i + 0.5) /
10});--primary-text-color-${i}:rgba(${textColor}, ${i / 10});`;
}
document.documentElement.style.cssText += cssText;
}
+21
View File
@@ -0,0 +1,21 @@
import config from '@fiora/config/client';
import { sessionID } from '../socket';
/**
* 上传文件
* @param blob 文件blob数据
* @param fileName 文件名
*/
export default async function uploadFile(blob: Blob): Promise<string> {
const formData = new FormData();
formData.append('file', blob);
const resp = await fetch(config.server + 'upload_file', {
headers: {
session_id: sessionID,
},
method: 'POST',
body: formData,
});
const result = await resp.json();
return result.tempFileName;
}