Merge pull request #24 from TangSengDaoDao/dev-electron

新增功能
This commit is contained in:
budou 2024-03-09 18:01:51 +08:00 committed by GitHub
commit 0b27f8f6a8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 4294 additions and 1394 deletions

6
.gitignore vendored
View File

@ -33,4 +33,8 @@ yarn-error.log*
lib/ lib/
out/ out/
build build
out-election/
dist-ele/
icon.icns
icons.iconset

3
.yarnrc Normal file
View File

@ -0,0 +1,3 @@
registry "https://registry.npmmirror.com/"
electron_mirror "https://registry.npmmirror.com/-/binary/electron/"

View File

@ -0,0 +1,55 @@
module.exports = {
productName: "tangsengdaodao", //项目名
appId: "com.tsdaodao.im",
copyright: "Copyright © tsdaodao", //版权
directories: {
output: "dist-ele", // 输出文件夹
},
npmRebuild: false,
asar: false,
buildDependenciesFromSource: true,
electronDownload: {
mirror: "https://registry.npmmirror.com/-/binary/electron/",
},
files: ["resources/**/*","out-election/**/*", "build/**/*"], // 需要打包的文件
extraMetadata: {
main: "out-election/main/index.js",
},
mac: {
category: "public.app-category.instant-messaging",
artifactName: "${productName}-${version}-${arch}.${ext}",
icon: "resources/icons/icon.icns",
target: ["dmg", "zip"]
},
dmg: {
// background: 'build/appdmg.png', // dmg安装窗口背景图
icon: "resources/icons/icon.icns", // 客户端图标
iconSize: 100, // 安装图标大小
// 安装窗口中包含的项目和配置
contents: [
{ x: 380, y: 280, type: "link", path: "/Applications" },
{ x: 110, y: 280, type: "file" },
],
window: { width: 500, height: 500 }, // 安装窗口大小
},
win: {
icon: "resources/icons/icon.ico",
verifyUpdateCodeSignature: false,
target: ["nsis", "zip"],
artifactName: "${productName}-Setup-${version}.${ext}"
},
nsis: {
oneClick: false, // 是否一键安装
allowElevation: true, // 允许请求提升。 如果为false则用户必须使用提升的权限重新启动安装程序。
allowToChangeInstallationDirectory: true, // 允许修改安装目录
// installerIcon: "./build/icon.ico",// 安装图标
// uninstallerIcon: "./build/icons/bbb.ico",//卸载图标
// installerHeaderIcon: "./build/icon.ico", // 安装时头部图标
createDesktopShortcut: true, // 创建桌面图标
createStartMenuShortcut: true, // 创建开始菜单图标
},
linux: {
target: ["AppImage", "deb"],
icon: "resources/icons/icon.icns",
},
};

View File

@ -2,7 +2,23 @@
"name": "@tsdaodao/web", "name": "@tsdaodao/web",
"version": "1.0.0", "version": "1.0.0",
"private": true, "private": true,
"homepage": "./", "main": "out-election/main/index.js",
"scripts": {
"start": "cross-env BROWSER=none REACT_APP_VERSION=$npm_package_version react-app-rewired start",
"dev": "cross-env MODE=dev BROWSER=none REACT_APP_VERSION=$npm_package_version react-app-rewired start",
"dev-ele": "kill-port 3000 && concurrently -k -n=web,ele -c=green,blue \"yarn dev\" \"wait-on tcp:3000 && npm-run-all watch\"",
"watch": "tsc-watch -p tsconfig.e.json --onSuccess \"npm-run-all start:electron\"",
"start:electron": "cross-env NODE_ENV=development electron .",
"build": "cross-env REACT_APP_VERSION=$npm_package_version react-app-rewired build",
"build:analyzer": "cross-env ANALYZER=true REACT_APP_VERSION=$npm_package_version react-app-rewired build",
"build-ele:mac": "tsc -p tsconfig.e.json && electron-builder --mac -c",
"build-ele:win": "tsc -p tsconfig.e.json && electron-builder --win -c",
"build-ele": "tsc -p tsconfig.e.json && electron-builder -mw -c",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject",
"clean": "rimraf node_modules out-election build dist-ele .turbo",
"tauri": "tauri"
},
"dependencies": { "dependencies": {
"@tauri-apps/api": "^1.1.0", "@tauri-apps/api": "^1.1.0",
"@tsdaodao/base": "*", "@tsdaodao/base": "*",
@ -13,6 +29,9 @@
"@types/react-mentions": "^4.1.5", "@types/react-mentions": "^4.1.5",
"@types/react-virtualized": "^9.21.22", "@types/react-virtualized": "^9.21.22",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"electron-log": "^5.1.1",
"electron-screenshots": "^0.5.26",
"howler": "^2.2.4",
"react": "^17.0.2", "react": "^17.0.2",
"react-app-rewired": "^2.1.8", "react-app-rewired": "^2.1.8",
"react-lazyload": "^3.2.0", "react-lazyload": "^3.2.0",
@ -26,28 +45,28 @@
"@testing-library/jest-dom": "^4.2.4", "@testing-library/jest-dom": "^4.2.4",
"@testing-library/react": "^9.3.2", "@testing-library/react": "^9.3.2",
"@testing-library/user-event": "^7.1.2", "@testing-library/user-event": "^7.1.2",
"@types/howler": "^2.2.11",
"@types/jest": "^24.0.0", "@types/jest": "^24.0.0",
"@types/node": "^12.0.0", "@types/node": "^20.10.5",
"@types/react": "^16.9.0", "@types/react": "^16.9.0",
"concurrently": "^8.2.2",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"customize-cra": "^1.0.0", "customize-cra": "^1.0.0",
"electron": "26.0.0",
"electron-builder": "^24.9.1",
"electron-log": "^5.0.1",
"eslint-config-react-app": "^7.0.1", "eslint-config-react-app": "^7.0.1",
"kill-port": "^2.0.1",
"npm-run-all": "^4.1.5",
"postcss-normalize": "^10.0.1", "postcss-normalize": "^10.0.1",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-scripts": "5.0.0", "react-scripts": "5.0.0",
"terser-webpack-plugin": "^5.3.9", "terser-webpack-plugin": "^5.3.9",
"typescript": "~3.7.2", "tsc-watch": "^6.0.4",
"typescript": "^5.3.3",
"wait-on": "^7.2.0",
"webpack-bundle-analyzer": "^4.5.0" "webpack-bundle-analyzer": "^4.5.0"
}, },
"scripts": {
"start": "cross-env BROWSER=none REACT_APP_VERSION=$npm_package_version react-app-rewired start",
"dev": "cross-env MODE=dev BROWSER=none REACT_APP_VERSION=$npm_package_version react-app-rewired start",
"build": "cross-env REACT_APP_VERSION=$npm_package_version react-app-rewired build",
"build:analyzer": "cross-env ANALYZER=true REACT_APP_VERSION=$npm_package_version react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-app-rewired eject",
"tauri": "tauri"
},
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"
}, },

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

BIN
apps/web/resources/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1 @@
declare module 'tmp';

View File

@ -0,0 +1,6 @@
const TSDD_FONFIG = {
appId: "com.tsdaodao.im",
name: "唐僧叨叨",
};
export default TSDD_FONFIG;

View File

@ -0,0 +1,441 @@
import {
app,
BrowserWindow,
screen,
globalShortcut,
ipcMain,
nativeImage as NativeImage,
Menu,
Tray,
} from "electron";
import fs from "fs";
import tmp from 'tmp';
import Screenshots from "electron-screenshots";
import { join } from "path";
import logo, { getNoMessageTrayIcon } from "./logo";
import TSDD_FONFIG from "./confing";
let forceQuit = false;
let mainWindow: any;
let isMainWindowFocusedWhenStartScreenshot = false;
let screenshots: any;
let tray: any;
let trayIcon: any;
let settings: any = {};
let screenShotWindowId = 0;
let isFullScreen = false;
let isOsx = process.platform === "darwin";
let isWin = !isOsx;
const isDevelopment = process.env.NODE_ENV === "development";
let mainMenu: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[] = [
{
label: "唐僧叨叨",
submenu: [
{
label: `关于唐僧叨叨`,
},
{ label: "服务", role: "services" },
{ type: "separator" },
{
label: "退出",
accelerator: "Command+Q",
click() {
forceQuit = true;
mainWindow = null;
setTimeout(() => {
app.exit(0);
}, 1000);
},
},
],
},
{
label: "编辑",
submenu: [
{
role: "undo",
label: "撤销",
},
{
role: "redo",
label: "重做",
},
{
type: "separator",
},
{
role: "cut",
label: "剪切",
},
{
role: "copy",
label: "复制",
},
{
role: "paste",
label: "粘贴",
},
{
role: "pasteAndMatchStyle",
label: "粘贴并匹配样式",
},
{
role: "delete",
label: "删除",
},
{
role: "selectAll",
label: "全选",
},
],
},
{
label: "显示",
submenu: [
{
label: isFullScreen ? "全屏" : "退出全屏",
accelerator: "Shift+Cmd+F",
click() {
isFullScreen = !isFullScreen;
mainWindow.show();
mainWindow.setFullScreen(isFullScreen);
},
},
{
label: "切换会话",
accelerator: "Shift+Cmd+M",
click() {
mainWindow.show();
mainWindow.webContents.send("show-conversations");
},
},
{
type: "separator",
},
{
type: "separator",
},
{
role: "toggleDevTools",
label: "切换开发者工具",
},
{
role: "togglefullscreen",
label: "切换全屏",
},
],
},
{
label: "窗口",
role: "window",
submenu: [
{
label: "最小化",
role: "minimize",
},
{
label: "关闭窗口",
role: "close",
},
],
},
{
label: "帮助",
role: "help",
submenu: [
{
type: "separator",
},
{
role: "reload",
label: "刷新",
},
{
role: "forceReload",
label: "强制刷新",
},
],
},
];
let trayMenu: Electron.MenuItemConstructorOptions[] = [
{
label: "显示窗口",
click() {
let isVisible = mainWindow.isVisible();
isVisible ? mainWindow.hide() : mainWindow.show();
},
},
{
type: "separator",
},
{
label: "退出",
accelerator: "Command+Q",
click() {
forceQuit = true;
mainWindow = null;
setTimeout(() => {
app.exit(0);
}, 1000);
},
},
];
function updateTray(unread = 0): any {
settings.showOnTray = true;
// linux 系统不支持 tray
if (process.platform === "linux") {
return;
}
if (settings.showOnTray) {
let contextmenu = Menu.buildFromTemplate(trayMenu);
if (!trayIcon) {
trayIcon = getNoMessageTrayIcon();
}
setTimeout(() => {
if (!tray) {
// Init tray icon
tray = new Tray(trayIcon);
if (process.platform === "linux") {
tray.setContextMenu(contextmenu);
}
tray.on("right-click", () => {
tray.popUpContextMenu(contextmenu);
});
tray.on("click", () => {
mainWindow.show();
});
}
if (isOsx) {
tray.setTitle(unread > 0 ? " " + unread : "");
}
tray.setImage(trayIcon);
});
} else {
if (!tray) return;
tray.destroy();
tray = null;
}
}
function createMenu() {
var menu = Menu.buildFromTemplate(mainMenu);
if (isOsx) {
Menu.setApplicationMenu(menu);
} else {
mainWindow.setMenu(null);
}
}
function regShortcut() {
globalShortcut.register("CommandOrControl+shift+a", () => {
isMainWindowFocusedWhenStartScreenshot = mainWindow.isFocused();
console.log(
"isMainWindowFocusedWhenStartScreenshot",
mainWindow.isFocused()
);
screenshots.startCapture();
});
globalShortcut.register("esc", () => {
if (screenshots.$win?.isFocused()) {
screenshots.endCapture();
}
});
// 打开所有窗口控制台
globalShortcut.register("ctrl+shift+i", () => {
let windows = BrowserWindow.getAllWindows();
windows.forEach((win: any) => win.openDevTools());
});
}
const createMainWindow = async () => {
const NODE_ENV = process.env.NODE_ENV;
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
mainWindow = new BrowserWindow({
width: 960,
height: 600,
minWidth: 960,
minHeight: 600,
// frame: true, // * app边框(包括关闭,全屏,最小化按钮的导航栏) @false: 隐藏
// titleBarStyle: "hidden",
// transparent: true, // * app 背景透明
hasShadow: false, // * app 边框阴影
show: false, // 启动窗口时隐藏,直到渲染进程加载完成「ready-to-show 监听事件」 再显示窗口,防止加载时闪烁
resizable: true, // 禁止手动修改窗口尺寸
webPreferences: {
// 加载脚本
preload: join(__dirname, "..", "preload/index"),
nodeIntegration: true,
},
// frame: !isWin,
});
mainWindow.center();
mainWindow.once("ready-to-show", () => {
mainWindow.show(); // 显示窗口
mainWindow.focus();
});
mainWindow.on("close", (e: any) => {
if (forceQuit || !tray) {
mainWindow = null;
} else {
e.preventDefault();
if (mainWindow.isFullScreen()) {
mainWindow.setFullScreen(false);
mainWindow.once("leave-full-screen", () => mainWindow.hide());
} else {
mainWindow.hide();
}
}
});
if (NODE_ENV === "development") mainWindow.loadURL("http://localhost:3000");
if (NODE_ENV !== "development") {
process.env.DIST_ELECTRON = join(__dirname, "../");
const WEB_URL = join(process.env.DIST_ELECTRON, "../build/index.html");
mainWindow.loadFile(WEB_URL);
}
ipcMain.on("screenshots-start", (event, args) => {
console.log("main voip-message event", args);
screenShotWindowId = event.sender.id;
screenshots.startCapture();
});
createMenu();
};
function onDeepLink(url: string) {
console.log("onOpenDeepLink", url);
mainWindow.webContents.send("deep-link", url);
}
app.setName(TSDD_FONFIG.name);
isDevelopment && app.dock && app.dock.setIcon(logo);
app.on("open-url", (event, url) => {
onDeepLink(url);
});
// 单例模式启动
const gotTheLock = app.requestSingleInstanceLock();
if (!gotTheLock) {
app.quit();
} else {
app.on("second-instance", (event, argv) => {
if (mainWindow) {
mainWindow.show();
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.focus();
}
});
}
app.on("ready", () => {
regShortcut();
createMainWindow(); // 创建窗口
screenshots = new Screenshots({
singleWindow: true,
});
const onScreenShotEnd = (result?: any) => {
console.log(
"onScreenShotEnd",
isMainWindowFocusedWhenStartScreenshot,
screenShotWindowId
);
if (isMainWindowFocusedWhenStartScreenshot) {
if (result) {
mainWindow.webContents.send("screenshots-ok", result);
}
mainWindow.show();
isMainWindowFocusedWhenStartScreenshot = false;
} else if (screenShotWindowId) {
let windows = BrowserWindow.getAllWindows();
let tms = windows.filter(
(win) => win.webContents.id === screenShotWindowId
);
if (tms.length > 0) {
if (result) {
tms[0].webContents.send("screenshots-ok", result);
}
tms[0].show();
}
screenShotWindowId = 0;
}
};
// 点击确定按钮回调事件
screenshots.on("ok", (e, buffer, bounds) => {
let filename = tmp.tmpNameSync() + '.png';
let image = NativeImage.createFromBuffer(buffer);
fs.writeFileSync(filename, image.toPNG());
console.log("screenshots ok", e);
onScreenShotEnd({ filePath: filename });
});
// 点击取消按钮回调事件
screenshots.on("cancel", (e: any) => {
// 执行了preventDefault
// 点击取消不会关闭截图窗口
// e.preventDefault()
// console.log('capture', 'cancel2')
console.log("screenshots cancel", e);
onScreenShotEnd();
});
// 点击保存按钮回调事件
screenshots.on("save", (e, { viewer }) => {
console.log("screenshots save", e);
onScreenShotEnd();
});
try {
updateTray();
} catch (e) {
// do nothing
console.log("==updateTray==", e);
}
});
app.on("activate", () => {
if (!mainWindow) {
return createMainWindow();
}
if (!mainWindow.isVisible()) {
mainWindow.show();
}
});
app.on("before-quit", () => {
forceQuit = true;
if (!tray) return;
tray.destroy();
tray = null;
});
// 除了 macOS 外,当所有窗口都被关闭的时候退出程序。 macOS窗口全部关闭时,dock中程序不会退出
app.on("window-all-closed", () => {
process.platform !== "darwin" && app.quit();
});

View File

@ -0,0 +1,16 @@
import path from "path";
import { app, screen } from "electron";
export default path.join(app.getAppPath(), "./resources/logo.png");
export function getNoMessageTrayIcon () {
if (process.platform === 'darwin') {
return path.join(app.getAppPath(), './resources/tray/30x30.png')
} else if (process.platform === 'win32') {
return path.join(app.getAppPath(), './resources/tray/128x128.png')
} else if (screen.getPrimaryDisplay().scaleFactor > 1) {
return path.join(app.getAppPath(), './resources/tray/128x128.png')
} else {
return path.join(app.getAppPath(), './resources/tray/128x128.png')
}
}

View File

@ -0,0 +1,35 @@
import { BrowserWindow, screen } from "electron";
import { join } from "path";
export function createWindow() {
const NODE_ENV = process.env.NODE_ENV;
const { width, height } = screen.getPrimaryDisplay().workAreaSize;
// 生成窗口实例
const mainWin = new BrowserWindow({
width, // * 指定启动app时的默认窗口尺寸
height, // * 指定启动app时的默认窗口尺寸
frame: true, // * app边框(包括关闭,全屏,最小化按钮的导航栏) @false: 隐藏
titleBarStyle: "hidden",
transparent: true, // * app 背景透明
hasShadow: false, // * app 边框阴影
show: false, // 启动窗口时隐藏,直到渲染进程加载完成「ready-to-show 监听事件」 再显示窗口,防止加载时闪烁
resizable: true, // 禁止手动修改窗口尺寸
webPreferences: {
// 加载脚本
preload: join(__dirname, "../..", "preload/index"),
nodeIntegration: true,
},
});
// 启动窗口时隐藏,直到渲染进程加载完成「ready-to-show 监听事件」 再显示窗口,防止加载时闪烁
mainWin.once("ready-to-show", () => {
mainWin.show(); // 显示窗口
});
if (NODE_ENV === "development") mainWin.loadURL("http://localhost:3000");
if (NODE_ENV !== "development") {
process.env.DIST_ELECTRON = join(__dirname, '../');
const WEB_URL = join(process.env.DIST_ELECTRON, "../../build/index.html");
mainWin.loadFile(WEB_URL);
}
}

View File

@ -0,0 +1,21 @@
import { contextBridge, ipcRenderer } from "electron";
contextBridge.exposeInMainWorld("__POWERED_ELECTRON__", true);
contextBridge.exposeInMainWorld("ipc", {
send: (channel: string, ...args: any[]) => ipcRenderer.send(channel, ...args),
invoke: (channel: string, ...args: any[]): Promise<any> =>
ipcRenderer.invoke(channel, ...args),
on: (
channel: string,
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
) => {
ipcRenderer.on(channel, listener);
},
once: (
channel: string,
listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void
) => {
ipcRenderer.once(channel, listener);
},
});

View File

@ -1,60 +1,100 @@
import { ChatPage, EndpointCategory, WKApp, Menus } from '@tsdaodao/base'; import { ChatPage, EndpointCategory, WKApp, Menus } from "@tsdaodao/base";
import { ContactsList } from '@tsdaodao/contacts'; import { ContactsList } from "@tsdaodao/contacts";
import React from 'react'; import React from "react";
import './index.css'; import "./index.css";
import AppLayout from '../Layout'; import AppLayout from "../Layout";
import { WKSDK } from 'wukongimjssdk'; import { WKSDK } from "wukongimjssdk";
function App() { function App() {
registerMenus() registerMenus();
return ( return <AppLayout />;
<AppLayout />
);
} }
function registerMenus() { function registerMenus() {
WKSDK.shared().conversationManager.addConversationListener(() => { WKSDK.shared().conversationManager.addConversationListener(() => {
WKApp.menus.refresh() WKApp.menus.refresh();
}) });
WKApp.endpointManager.setMethod("menus.friendapply.change", () => { WKApp.endpointManager.setMethod(
WKApp.menus.refresh() "menus.friendapply.change",
}, { () => {
category: EndpointCategory.friendApplyDataChange, WKApp.menus.refresh();
}) },
{
WKApp.menus.register("chat", (context) => { category: EndpointCategory.friendApplyDataChange,
const m = new Menus("chat", "/", "会话",
<img alt='会话' src={require("./assets/HomeTab.svg").default}></img>,
<img alt='会话' src={require("./assets/HomeTabSelected.svg").default}></img>)
let badge = 0
for (const conversation of WKSDK.shared().conversationManager.conversations) {
if (!conversation.channelInfo?.mute) {
badge += conversation.unread
}
} }
m.badge = badge );
return m
},1000)
WKApp.menus.register("contacts", (param) => { WKApp.menus.register(
const m = new Menus("contacts", "/contacts", "通讯录", "chat",
<img alt='通讯录' src={require("./assets/ContactsTab.svg").default}></img>, (context) => {
<img alt='通讯录' src={require("./assets/ContactsTabSelected.svg").default}></img>) const m = new Menus(
"chat",
"/",
"会话",
<img alt="会话" src={require("./assets/HomeTab.svg").default}></img>,
(
<img
alt="会话"
src={require("./assets/HomeTabSelected.svg").default}
></img>
)
);
let badge = 0;
for (const conversation of WKSDK.shared().conversationManager
.conversations) {
if (!conversation.channelInfo?.mute) {
badge += conversation.unread;
}
}
m.badge = badge;
return m;
},
1000
);
m.badge = WKApp.shared.getFriendApplysUnreadCount() // 获取好友未申请添加数量
return m let unreadCount = 0;
},2000) if (WKApp.loginInfo.isLogined()) {
WKApp.apiClient.get(`/user/reddot/friendApply`).then((res) => {
unreadCount = res.count;
WKApp.menus.refresh();
});
}
WKApp.menus.register(
"contacts",
(param) => {
const m = new Menus(
"contacts",
"/contacts",
"通讯录",
(
<img
alt="通讯录"
src={require("./assets/ContactsTab.svg").default}
></img>
),
(
<img
alt="通讯录"
src={require("./assets/ContactsTabSelected.svg").default}
></img>
)
);
m.badge = unreadCount;
return m;
},
2000
);
WKApp.route.register("/", () => { WKApp.route.register("/", () => {
return <ChatPage></ChatPage> return <ChatPage></ChatPage>;
}) });
WKApp.route.register("/contacts", () => { WKApp.route.register("/contacts", () => {
return <ContactsList></ContactsList> return <ContactsList></ContactsList>;
}) });
} }
export default App; export default App;

View File

@ -1,49 +1,48 @@
import React from 'react'; import React from "react";
import ReactDOM from 'react-dom'; import ReactDOM from "react-dom";
import './index.css'; import "./index.css";
import App from './App'; import App from "./App";
import reportWebVitals from './reportWebVitals'; import reportWebVitals from "./reportWebVitals";
import { BaseModule, WKApp } from '@tsdaodao/base'; import { BaseModule, WKApp } from "@tsdaodao/base";
import { LoginModule } from '@tsdaodao/login'; import { LoginModule } from "@tsdaodao/login";
import { DataSourceModule } from '@tsdaodao/datasource'; import { DataSourceModule } from "@tsdaodao/datasource";
import {ContactsModule} from '@tsdaodao/contacts'; import { ContactsModule } from "@tsdaodao/contacts";
const apiURL = "https://api.botgate.cn/v1/" const apiURL = "https://api.botgate.cn/v1/";
if(!(window as any).__TAURI_IPC__) { // tauri环境 if ((window as any).__TAURI_IPC__) {
if(process.env.NODE_ENV === "development") { // tauri环境
WKApp.apiClient.config.apiURL = apiURL console.log("tauri环境");
}else { WKApp.apiClient.config.apiURL = apiURL;
WKApp.apiClient.config.apiURL = "/api/v1/" // 正式环境地址 (通用打包镜像,用此相对地址),打包出来的镜像可以通过API_URL环境变量来修改API地址 } else if ((window as any)?.__POWERED_ELECTRON__) {
console.log("__POWERED_ELECTRON__环境");
WKApp.apiClient.config.apiURL = apiURL;
} else {
if (process.env.NODE_ENV === "development") {
WKApp.apiClient.config.apiURL = apiURL;
} else {
WKApp.apiClient.config.apiURL = "/api/v1/"; // 正式环境地址 (通用打包镜像,用此相对地址),打包出来的镜像可以通过API_URL环境变量来修改API地址
} }
}else {
console.log("tauri环境")
WKApp.apiClient.config.apiURL = apiURL
} }
WKApp.apiClient.config.tokenCallback = () => {
return WKApp.loginInfo.token;
};
WKApp.config.appVersion = `${process.env.REACT_APP_VERSION || "0.0.0"}`;
WKApp.loginInfo.load(); // 加载登录信息
WKApp.apiClient.config.tokenCallback = ()=> {
return WKApp.loginInfo.token
}
WKApp.config.appVersion = `${process.env.REACT_APP_VERSION || "0.0.0"}`
WKApp.loginInfo.load() // 加载登录信息
WKApp.shared.registerModule(new BaseModule()); // 基础模块 WKApp.shared.registerModule(new BaseModule()); // 基础模块
WKApp.shared.registerModule(new DataSourceModule()) // 数据源模块 WKApp.shared.registerModule(new DataSourceModule()); // 数据源模块
WKApp.shared.registerModule(new LoginModule()); // 登录模块 WKApp.shared.registerModule(new LoginModule()); // 登录模块
WKApp.shared.registerModule(new ContactsModule()); // 联系模块 WKApp.shared.registerModule(new ContactsModule()); // 联系模块
WKApp.shared.startup() // app启动 WKApp.shared.startup(); // app启动
ReactDOM.render( ReactDOM.render(
<React.StrictMode> <React.StrictMode>
<App /> <App />
</React.StrictMode>, </React.StrictMode>,
document.getElementById('root') document.getElementById("root")
); );
reportWebVitals(); reportWebVitals();

15
apps/web/tsconfig.e.json Normal file
View File

@ -0,0 +1,15 @@
{
"exclude": ["node_modules", "src"],
"include": ["src-election/**/*"],
"compilerOptions": {
"outDir": "./out-election",
"module": "commonjs",
"moduleResolution": "node",
"resolveJsonModule": true,
"esModuleInterop": true,
"noEmit": false,
"baseUrl": "./",
"skipLibCheck":true,
"sourceMap": false
}
}

View File

@ -6,11 +6,15 @@
], ],
"resolutions": { "resolutions": {
"//": "See https://github.com/facebook/create-react-app/issues/11773", "//": "See https://github.com/facebook/create-react-app/issues/11773",
"react-error-overlay": "6.0.9" "react-error-overlay": "6.0.11"
}, },
"scripts": { "scripts": {
"build": "turbo run build", "bootstrap": "yarn install",
"dev": "turbo run dev --parallel", "dev": "turbo run dev --parallel",
"dev-ele": "turbo run dev-ele --parallel",
"build": "turbo run build",
"build-ele": "turbo run build-ele",
"clean": "turbo run clean && rimraf node_modules",
"lint": "turbo run lint", "lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,md}\"" "format": "prettier --write \"**/*.{ts,tsx,md}\""
}, },
@ -18,8 +22,8 @@
"eslint": "^7.32.0", "eslint": "^7.32.0",
"eslint-config-custom": "*", "eslint-config-custom": "*",
"prettier": "^2.5.1", "prettier": "^2.5.1",
"rimraf": "^5.0.5",
"turbo": "latest" "turbo": "latest"
}, },
"dependencies": {},
"version": "0.0.0" "version": "0.0.0"
} }

View File

@ -1,7 +1,9 @@
module.exports = { module.exports = {
extends: ["next", "turbo", "prettier"], extends: ["next", "turbo", "prettier", "@typescript-eslint/parser"],
rules: { rules: {
"@next/next/no-html-link-for-pages": "off", "@next/next/no-html-link-for-pages": "off",
"react/jsx-key": "off", "react/jsx-key": "off",
"@next/next/no-img-element": "off",
"no-template-curly-in-string": "off"
}, },
}; };

View File

@ -3,7 +3,11 @@
"version": "0.0.0", "version": "0.0.0",
"main": "index.js", "main": "index.js",
"license": "MIT", "license": "MIT",
"scripts": {
"clean": "rimraf node_modules out-election"
},
"dependencies": { "dependencies": {
"@typescript-eslint/parser": "^6.16.0",
"eslint-config-next": "^12.0.8", "eslint-config-next": "^12.0.8",
"eslint-config-prettier": "^8.3.0", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-react": "7.28.0", "eslint-plugin-react": "7.28.0",

View File

@ -13,8 +13,9 @@
"axios": "^0.25.0", "axios": "^0.25.0",
"benz-amr-recorder": "^1.1.3", "benz-amr-recorder": "^1.1.3",
"classnames": "^2.3.1", "classnames": "^2.3.1",
"electron-log": "^5.1.1",
"electron-screenshots": "^0.5.26",
"hotkeys-js": "^3.8.7", "hotkeys-js": "^3.8.7",
"wukongimjssdk": "^1.1.2",
"moment": "^2.29.3", "moment": "^2.29.3",
"qrcode.react": "^1.0.1", "qrcode.react": "^1.0.1",
"react": "^17.0.2", "react": "^17.0.2",
@ -22,7 +23,8 @@
"react-mentions": "^4.3.1", "react-mentions": "^4.3.1",
"react-scroll": "^1.8.4", "react-scroll": "^1.8.4",
"react-spinners": "^0.11.0", "react-spinners": "^0.11.0",
"react-viewer": "^3.2.2" "react-viewer": "^3.2.2",
"wukongimjssdk": "^1.2.7"
}, },
"devDependencies": { "devDependencies": {
"@types/react-avatar-editor": "^13.0.0", "@types/react-avatar-editor": "^13.0.0",

View File

@ -457,9 +457,9 @@ export default class WKApp extends ProviderListener {
for (const friendApplyObj of friendApplyObjs) { for (const friendApplyObj of friendApplyObjs) {
const f = new FriendApply() const f = new FriendApply()
f.uid = friendApplyObj.uid f.uid = friendApplyObj.uid
f.name = friendApplyObj.name f.to_name = friendApplyObj.to_name
f.remark = friendApplyObj.remark f.remark = friendApplyObj.remark
f.state = friendApplyObj.state f.status = friendApplyObj.status
f.token = friendApplyObj.token f.token = friendApplyObj.token
f.unread = friendApplyObj.unread f.unread = friendApplyObj.unread
f.createdAt = friendApplyObj.createdAt f.createdAt = friendApplyObj.createdAt
@ -472,35 +472,41 @@ export default class WKApp extends ProviderListener {
return friendApplys return friendApplys
} }
public getFriendApplysUnreadCount() { public async getFriendApplysUnreadCount() {
const friendApplys = this.getFriendApplys() // const friendApplys = this.getFriendApplys()
let unreadCount = 0 let unreadCount = 0
if (friendApplys && friendApplys.length > 0) { // if (friendApplys && friendApplys.length > 0) {
for (const friendApply of friendApplys) { // for (const friendApply of friendApplys) {
if (friendApply.unread) { // if (friendApply.unread) {
unreadCount++ // unreadCount++
} // }
} // }
} // }
if (!WKApp.loginInfo.isLogined()) {
return unreadCount;
}
const res = await WKApp.apiClient.get(`/user/reddot/friendApply`);
unreadCount = res.count;
return unreadCount return unreadCount
} }
public friendApplyMarkAllReaded() { public async friendApplyMarkAllReaded(): Promise<void> {
let friendApplys = this.getFriendApplys() // let friendApplys = this.getFriendApplys()
if (!friendApplys) { // if (!friendApplys) {
friendApplys = new Array<FriendApply>() // friendApplys = new Array<FriendApply>()
} // }
var change = false // var change = false
for (const friendApply of friendApplys) { // for (const friendApply of friendApplys) {
if (friendApply.unread) { // if (friendApply.unread) {
friendApply.unread = false // friendApply.unread = false
change = true // change = true
} // }
} // }
if (change) { // if (change) {
WKApp.loginInfo.setStorageItem(this.getFriendApplysKey(), JSON.stringify(friendApplys)) // WKApp.loginInfo.setStorageItem(this.getFriendApplysKey(), JSON.stringify(friendApplys))
WKApp.endpointManager.invokes(EndpointCategory.friendApplyDataChange) // WKApp.endpointManager.invokes(EndpointCategory.friendApplyDataChange)
} // }
await WKApp.apiClient.delete(`/user/reddot/friendApply`);
} }
public addFriendApply(friendApply: FriendApply) { public addFriendApply(friendApply: FriendApply) {
@ -572,13 +578,14 @@ export enum FriendApplyState {
} }
// 好友申请 // 好友申请
export class FriendApply { export class FriendApply {
uid!: string uid!: string;
name!: string to_uid!: string;
remark?: string to_name!: string;
token?: string remark?: string;
state!: FriendApplyState token?: string;
unread: boolean = false // 是否未读 status!: FriendApplyState;
createdAt!: number // 创建时间 unread: boolean = false; // 是否未读
createdAt!: number; // 创建时间
} }

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +1,92 @@
import { FriendApplyState, WKApp, WKViewQueueHeader, Provider } from "@tsdaodao/base"; import {
FriendApplyState,
WKApp,
WKViewQueueHeader,
Provider,
} from "@tsdaodao/base";
import React from "react"; import React from "react";
import { Component, ReactNode } from "react"; import { Component, ReactNode } from "react";
import { Button } from '@douyinfe/semi-ui'; import { Button } from "@douyinfe/semi-ui";
import "./index.css" import "./index.css";
import { NewFriendVM } from "./vm"; import { NewFriendVM } from "./vm";
import "./index.css" import "./index.css";
import { FriendAdd } from "../FriendAdd"; import { FriendAdd } from "../FriendAdd";
export class NewFriend extends Component { export class NewFriend extends Component {
render(): ReactNode {
render(): ReactNode { return (
return <Provider create={() => { <Provider
return new NewFriendVM() create={() => {
}} render={(vm: NewFriendVM) => { return new NewFriendVM();
}}
return <div className="wk-newfriend"> render={(vm: NewFriendVM) => {
<WKViewQueueHeader title="新朋友" onBack={() => { return (
WKApp.routeLeft.pop() <div className="wk-newfriend">
}} action={<div className="wk-viewqueueheader-content-action"> <WKViewQueueHeader
<Button size="small" onClick={()=>{ title="新朋友"
WKApp.routeLeft.push(<FriendAdd onBack={()=>{ onBack={() => {
WKApp.routeLeft.pop() WKApp.routeLeft.pop();
}}></FriendAdd>) }}
}} ></Button> action={
</div>}></WKViewQueueHeader> <div className="wk-viewqueueheader-content-action">
<div className="wk-newfriend-content"> <Button
<ul> size="small"
{ onClick={() => {
vm.friendApplys.map((f) => { WKApp.routeLeft.push(
return <li key={f.uid} > <FriendAdd
<div className="wk-newfriend-content-avatar"> onBack={() => {
<img src={WKApp.shared.avatarUser(f.uid)}></img> WKApp.routeLeft.pop();
</div> }}
<div className="wk-newfriend-content-title"> ></FriendAdd>
<div className="wk-newfriend-content-title-name"> );
{f.name} }}
</div> >
<div className="wk-newfriend-content-title-remark">
{f.remark} </Button>
</div> </div>
</div> }
<div className="wk-newfriend-content-action"> ></WKViewQueueHeader>
<Button loading={vm.currentFriendApply?.uid === f.uid && vm.sureLoading } disabled={f.state==FriendApplyState.accepted} onClick={()=>{ <div className="wk-newfriend-content">
vm.friendSure(f) <ul>
}}>{f.state==FriendApplyState.accepted?"已添加":"确认"}</Button> {vm.friendApplys.map((f) => {
</div> return (
</li> <li key={f.to_uid}>
}) <div className="wk-newfriend-content-avatar">
} <img src={WKApp.shared.avatarUser(f.to_uid)}></img>
</ul> </div>
</div> <div className="wk-newfriend-content-title">
<div className="wk-newfriend-content-title-name">
{f.to_name}
</div>
<div className="wk-newfriend-content-title-remark">
{f.remark}
</div>
</div>
<div className="wk-newfriend-content-action">
<Button
loading={
vm.currentFriendApply?.to_uid === f.to_uid &&
vm.sureLoading
}
disabled={f.status == FriendApplyState.accepted}
onClick={() => {
vm.friendSure(f);
}}
>
{f.status == FriendApplyState.accepted
? "已添加"
: "确认"}
</Button>
</div>
</li>
);
})}
</ul>
</div>
</div> </div>
}}> );
}}
</Provider> ></Provider>
} );
} }
}

View File

@ -1,33 +1,83 @@
import { WKSDK, Message, CMDContent } from "wukongimjssdk";
import { FriendApplyState, WKApp, ProviderListener } from "@tsdaodao/base"; import { FriendApplyState, WKApp, ProviderListener } from "@tsdaodao/base";
import { FriendApply } from "@tsdaodao/base"; import { FriendApply } from "@tsdaodao/base";
export class NewFriendVM extends ProviderListener { export class NewFriendVM extends ProviderListener {
friendApplys: FriendApply[] = [] friendApplys: FriendApply[] = [];
sureLoading:boolean = false sureLoading: boolean = false;
currentFriendApply?:FriendApply currentFriendApply?: FriendApply;
didMount(): void { async didMount(): Promise<void> {
WKApp.shared.friendApplyMarkAllReaded();
WKApp.shared.friendApplyMarkAllReaded() this.friendApplys = await this.getFriendApply();
if (this.friendApplys.length === 0) {
this.friendApplys = WKApp.shared.getFriendApplys() this.clearFriendApply();
this.notifyListener()
} }
this.notifyListener();
// 监听好友申请
WKSDK.shared().chatManager.addCMDListener(this.friendRequestCMDListener);
}
friendSure(apply: FriendApply) { didUnMount(): void {
this.sureLoading = true // 监听好友申请
this.currentFriendApply = apply WKSDK.shared().chatManager.removeCMDListener(this.friendRequestCMDListener);
this.notifyListener() }
WKApp.dataSource.commonDataSource.friendSure(apply.token || "").then(() => { friendSure(apply: FriendApply) {
apply.state = FriendApplyState.accepted this.sureLoading = true;
WKApp.shared.updateFriendApply(apply) this.currentFriendApply = apply;
this.sureLoading = false this.notifyListener();
this.notifyListener()
}).catch(() => { WKApp.dataSource.commonDataSource
this.sureLoading = false .friendSure(apply.token || "")
this.notifyListener() .then(() => {
}) apply.status = FriendApplyState.accepted;
WKApp.shared.updateFriendApply(apply);
this.sureLoading = false;
this.notifyListener();
})
.catch(() => {
this.sureLoading = false;
this.notifyListener();
});
}
async getFriendApply(): Promise<FriendApply[]> {
const fromData = {
page_index: 1,
page_size: 999,
};
const res = await WKApp.apiClient.get("/friend/apply", {
param: fromData,
});
return res;
}
async delFriendApply(apply: FriendApply): Promise<void> {
WKApp.apiClient
.delete(`/friend/apply/${apply.to_uid}`)
.then(async () => {
this.friendApplys = await this.getFriendApply();
this.sureLoading = false;
this.notifyListener();
})
.catch(() => {
this.sureLoading = false;
this.notifyListener();
});
}
async clearFriendApply(): Promise<void> {
await WKApp.apiClient.delete(`/user/reddot/friendApply`);
}
public async friendRequestCMDListener(message: Message) {
console.log("收到CMD->", message);
const cmdContent = message.content as CMDContent;
if (cmdContent.cmd === "friendRequest") {
this.friendApplys = await this.getFriendApply();
} }
} }
}

View File

@ -1,57 +1,105 @@
import { EndpointCategory, IconListItem, IModule, WKApp, ThemeMode } from "@tsdaodao/base" import {
import React from "react" EndpointCategory,
import Blacklist from "./Blacklist" IconListItem,
import { FriendAdd } from "./FriendAdd" IModule,
import GroupSave from "./GroupSave" WKApp,
import { NewFriend } from "./NewFriend" ThemeMode,
import { ContactsListManager } from "./Service/ContactsListManager" } from "@tsdaodao/base";
import React from "react";
import Blacklist from "./Blacklist";
import { FriendAdd } from "./FriendAdd";
import GroupSave from "./GroupSave";
import { NewFriend } from "./NewFriend";
import { ContactsListManager } from "./Service/ContactsListManager";
export default class ContactsModule implements IModule { export default class ContactsModule implements IModule {
id(): string {
return "ContactsModule";
}
init(): void {
console.log("【ContactsModule】初始化");
id(): string { WKApp.endpointManager.setMethod(
return "ContactsModule" "contacts.friendapply.change",
() => {
ContactsListManager.shared.refreshList();
},
{
category: EndpointCategory.friendApplyDataChange,
}
);
// 获取好友未申请添加数量
let unreadCount = 0;
if (WKApp.loginInfo.isLogined()) {
WKApp.apiClient.get(`/user/reddot/friendApply`).then((res) => {
unreadCount = res.count;
WKApp.menus.refresh();
});
} }
init(): void {
console.log("【ContactsModule】初始化")
WKApp.endpoints.registerContactsHeader("friends.new", (param: any) => {
return (
<IconListItem
badge={unreadCount}
title="新朋友"
icon={require("./assets/friend_new.png")}
backgroudColor={"var(--wk-color-secondary)"}
onClick={() => {
WKApp.routeLeft.push(<NewFriend></NewFriend>);
}}
></IconListItem>
);
});
WKApp.endpointManager.setMethod("contacts.friendapply.change", () => { WKApp.endpoints.registerContactsHeader("groups.save", (param: any) => {
ContactsListManager.shared.refreshList() return (
}, { <IconListItem
category: EndpointCategory.friendApplyDataChange, title="保存的群"
}) icon={require("./assets/icon_group_save.png")}
backgroudColor={"var(--wk-color-secondary)"}
onClick={() => {
WKApp.routeLeft.push(<GroupSave></GroupSave>);
}}
></IconListItem>
);
});
WKApp.endpoints.registerContactsHeader("friends.new", (param: any) => { WKApp.endpoints.registerContactsHeader(
return <IconListItem badge={WKApp.shared.getFriendApplysUnreadCount()} title="新朋友" icon={require("./assets/friend_new.png")} backgroudColor={"var(--wk-color-secondary)"} onClick={() => { "contacts.blacklist",
WKApp.routeLeft.push(<NewFriend></NewFriend>) (param: any) => {
}} ></IconListItem> return (
}) <IconListItem
title="黑名单"
icon={require("./assets/blacklist.png")}
backgroudColor={"var(--wk-color-secondary)"}
onClick={() => {
WKApp.routeLeft.push(<Blacklist></Blacklist>);
}}
></IconListItem>
);
}
);
WKApp.endpoints.registerContactsHeader("groups.save", (param: any) => { WKApp.shared.chatMenusRegister("chatmenus.addfriend", (param) => {
return <IconListItem title="保存的群" icon={require("./assets/icon_group_save.png")} backgroudColor={"var(--wk-color-secondary)"} onClick={() => { const isDark = WKApp.config.themeMode === ThemeMode.dark;
WKApp.routeLeft.push(<GroupSave></GroupSave>) return {
}}></IconListItem> title: "添加朋友",
}) icon: require(`${
isDark
WKApp.endpoints.registerContactsHeader("contacts.blacklist", (param: any) => { ? "./assets/popmenus_friendadd_dark.png"
return <IconListItem title="黑名单" icon={require("./assets/blacklist.png")} backgroudColor={"var(--wk-color-secondary)"} onClick={() => { : "./assets/popmenus_friendadd.png"
WKApp.routeLeft.push(<Blacklist></Blacklist>) }`),
}}></IconListItem> onClick: () => {
}) WKApp.routeLeft.push(
<FriendAdd
WKApp.shared.chatMenusRegister("chatmenus.addfriend",(param)=>{ onBack={() => {
const isDark = WKApp.config.themeMode === ThemeMode.dark WKApp.routeLeft.pop();
return { }}
title: "添加朋友", ></FriendAdd>
icon: require(`${isDark?"./assets/popmenus_friendadd_dark.png":"./assets/popmenus_friendadd.png"}`), );
onClick:()=>{ },
WKApp.routeLeft.push(<FriendAdd onBack={()=>{ };
WKApp.routeLeft.pop() });
}}></FriendAdd>) }
} }
}
})
}
}

View File

@ -1,14 +1,25 @@
{ {
"$schema": "https://turborepo.org/schema.json", "$schema": "https://turborepo.org/schema.json",
"pipeline": { "pipeline": {
"dev": {
"cache": false
},
"dev-ele": {
"dependsOn": ["^dev"],
"cache": false
},
"build": { "build": {
"outputs": [".next/**"] "outputs": [".next/**"]
}, },
"build-ele": {
"outputs": [".next/**"],
"dependsOn": ["^build"]
},
"lint": { "lint": {
"outputs": [] "outputs": []
}, },
"dev": { "clean": {
"cache": false "dependsOn": ["^clean"]
} }
} }
} }

1694
yarn.lock

File diff suppressed because it is too large Load Diff