diff --git a/packages/desktop/CHANGELOG.md b/packages/desktop/CHANGELOG.md
index a6cdb53c..2c076192 100644
--- a/packages/desktop/CHANGELOG.md
+++ b/packages/desktop/CHANGELOG.md
@@ -1,5 +1,9 @@
# @pear-rec/desktop
+## 1.3.1
+
+feat: 增加录全屏
+
## 1.3.0
feat: 增加钉图功能
diff --git a/packages/desktop/electron-builder.json5 b/packages/desktop/electron-builder.json5
index e7e9fb8e..f5f9f08e 100644
--- a/packages/desktop/electron-builder.json5
+++ b/packages/desktop/electron-builder.json5
@@ -3,17 +3,22 @@
*/
{
appId: 'com.electron.pear',
- productName: 'pear-rec',
asar: false,
+ productName: 'pear-rec',
+ copyright: 'Copyright © 2023 ${author}',
// extraResources: ['node_modules/sql.js', 'node_modules/typeorm'],
directories: {
output: 'release/${version}',
},
files: ['dist-electron', 'dist'],
mac: {
- artifactName: 'pear-rec.${ext}',
+ artifactName: '${productName}-Mac-${version}-Installer.${ext}',
target: ['dmg', 'zip'],
},
+ linux: {
+ target: ['AppImage'],
+ artifactName: '${productName}-Linux-${version}.${ext}',
+ },
win: {
icon: 'build/icons/win/icon.ico',
target: [
@@ -22,7 +27,7 @@
arch: ['x64'],
},
],
- artifactName: 'pear-rec.${ext}',
+ artifactName: '${productName}-Windows-${version}-Setup.${ext}',
requestedExecutionLevel: 'requireAdministrator',
},
nsis: {
diff --git a/packages/desktop/electron/main/ipcMain.ts b/packages/desktop/electron/main/ipcMain.ts
index 129f9696..c86edbcd 100644
--- a/packages/desktop/electron/main/ipcMain.ts
+++ b/packages/desktop/electron/main/ipcMain.ts
@@ -12,6 +12,7 @@ import * as viewAudioWin from '../win/viewAudioWin';
import * as settingWin from '../win/settingWin';
import * as recordsWin from '../win/recordsWin';
import * as pinImageWin from '../win/pinImageWin';
+import * as recorderFullScreenWin from '../win/recorderFullScreenWin';
import * as utils from './utils';
import { editConfig } from './config';
@@ -121,7 +122,6 @@ function initIpcMain() {
// 录屏截图
ipcMain.on('cs:open-win', () => {
clipScreenWin.closeClipScreenWin();
- mainWin.hideMainWin();
clipScreenWin.openClipScreenWin();
});
ipcMain.on('cs:close-win', () => {
@@ -234,50 +234,6 @@ function initIpcMain() {
viewVideoWin.setAlwaysOnTopViewVideoWin(isAlwaysOnTop);
});
- // 打开图片;
- ipcMain.handle('vi:get-images', async (event, title) => {
- let res = await dialog.showOpenDialog({
- title: title,
- buttonLabel: '按此打开文件',
- // defaultPath: app.getAppPath("aaa"),
- properties: ['multiSelections'],
- filters: [
- { name: '图片', extensions: ['jpg', 'jpeg', 'png', 'webp', 'svg'] },
- // { name: "视频", extensions: ["mkv", "avi", "mp4"] },
- ],
- });
- const images = res.filePaths.map((filePath, index) => {
- return { src: `pearrec:///${filePath}`, key: index };
- });
- return images;
- });
- ipcMain.handle('vi:get-img', async (event, title) => {
- // let res = await dialog.showOpenDialog({
- // title: title,
- // buttonLabel: '按此打开文件',
- // properties: ['openFile'],
- // filters: [{ name: '图片', extensions: ['jpg', 'jpeg', 'png', 'webp', 'svg'] }],
- // });
- // res.filePaths.map((filePath, index) => {
- // store.setHistoryImg(filePath);
- // });
- // const img = await store.getHistoryImg();
- // return img;
- });
- // 打开视频
- ipcMain.handle('vv:get-video', async (e) => {
- // let res = await dialog.showOpenDialog({
- // title: '选择视频',
- // buttonLabel: '按此打开文件',
- // properties: ['openFile'],
- // filters: [{ name: '视频', extensions: ['mkv', 'avi', 'mp4', 'webm'] }],
- // });
- // res.filePaths.map((filePath, index) => {
- // store.setHistoryVideo(filePath);
- // });
- // const video = await store.getHistoryVideo();
- // return video;
- });
// 录音;
ipcMain.on('ra:open-win', () => {
recorderAudioWin.closeRecorderAudioWin();
@@ -371,7 +327,6 @@ function initIpcMain() {
// 钉图
ipcMain.on('pi:open-win', (e, search) => {
- console.log(111, search);
pinImageWin.openPinImageWin(search);
});
ipcMain.on('pi:close-win', () => {
@@ -383,6 +338,15 @@ function initIpcMain() {
ipcMain.on('pi:maximize-win', () => {
pinImageWin.maximizePinImageWin();
});
+
+ // 录全屏
+ ipcMain.on('rfs:open-win', () => {
+ recorderFullScreenWin.closeRecorderFullScreenWin();
+ recorderFullScreenWin.openRecorderFullScreenWin();
+ });
+ ipcMain.on('rfs:close-win', () => {
+ recorderFullScreenWin.closeRecorderFullScreenWin();
+ });
}
initIpcMain();
diff --git a/packages/desktop/electron/preload/electronAPI.ts b/packages/desktop/electron/preload/electronAPI.ts
index f7de2e8d..5a0e14c2 100644
--- a/packages/desktop/electron/preload/electronAPI.ts
+++ b/packages/desktop/electron/preload/electronAPI.ts
@@ -79,7 +79,6 @@ contextBridge.exposeInMainWorld('electronAPI', {
//vvWin
sendVvOpenWin: (search?: string) => ipcRenderer.send('vv:open-win', search),
invokeVvGetHistoryVideo: () => ipcRenderer.invoke('vv:get-historyVideo'),
- invokeVvGetVideo: () => ipcRenderer.invoke('vv:get-video'),
sendVvSetHistoryVideo: (img: string) => ipcRenderer.send('vv:set-historyVideo', img),
//vaWin
@@ -103,4 +102,8 @@ contextBridge.exposeInMainWorld('electronAPI', {
sendPiCloseWin: () => ipcRenderer.send('pi:close-win'),
sendPiMinimizeWin: () => ipcRenderer.send('pi:minimize-win'),
sendPiMaximizeWin: () => ipcRenderer.send('pi:maximize-win'),
+
+ // rfs
+ sendRfsOpenWin: () => ipcRenderer.send('rfs:open-win'),
+ sendRfsCloseWin: () => ipcRenderer.send('rfs:close-win'),
});
diff --git a/packages/desktop/electron/win/recorderFullScreenWin.ts b/packages/desktop/electron/win/recorderFullScreenWin.ts
new file mode 100644
index 00000000..761c0ede
--- /dev/null
+++ b/packages/desktop/electron/win/recorderFullScreenWin.ts
@@ -0,0 +1,79 @@
+import { app, BrowserWindow, dialog, shell, screen, Rectangle } from 'electron';
+import { join, basename, dirname } from 'node:path';
+import { preload, url, DIST, ICON, WEB_URL, DIST_ELECTRON } from '../main/contract';
+
+const recorderFullScreenHtml = join(DIST, './recordeFullScreen.html');
+let recorderFullScreenWin: BrowserWindow | null = null;
+
+function createRecorderFullScreenWin(): BrowserWindow {
+ recorderFullScreenWin = new BrowserWindow({
+ title: 'pear-rec 录屏',
+ icon: ICON,
+ height: 40,
+ width: 365,
+ center: true,
+ transparent: true, // 使窗口透明
+ autoHideMenuBar: true, // 自动隐藏菜单栏
+ frame: false, // 无边框窗口
+ hasShadow: false, // 窗口是否有阴影
+ fullscreenable: false, // 窗口是否可以进入全屏状态
+ alwaysOnTop: true, // 窗口是否永远在别的窗口的上面
+ skipTaskbar: true,
+ webPreferences: {
+ preload,
+ },
+ });
+ recorderFullScreenWin?.setBounds({ y: 0 });
+ recorderFullScreenWin?.setResizable(false);
+ if (url) {
+ recorderFullScreenWin.loadURL(WEB_URL + `recorderFullScreen.html`);
+ // recorderFullScreenWin.webContents.openDevTools();
+ } else {
+ recorderFullScreenWin.loadFile(recorderFullScreenHtml);
+ }
+
+ return recorderFullScreenWin;
+}
+
+function closeRecorderFullScreenWin() {
+ recorderFullScreenWin?.isDestroyed() || recorderFullScreenWin?.close();
+ recorderFullScreenWin = null;
+}
+
+function openRecorderFullScreenWin() {
+ if (!recorderFullScreenWin || recorderFullScreenWin?.isDestroyed()) {
+ recorderFullScreenWin = createRecorderFullScreenWin();
+ }
+ recorderFullScreenWin?.show();
+}
+
+function hideRecorderFullScreenWin() {
+ recorderFullScreenWin?.hide();
+}
+
+function showRecorderFullScreenWin() {
+ recorderFullScreenWin?.show();
+}
+
+function minimizeRecorderFullScreenWin() {
+ recorderFullScreenWin?.minimize();
+}
+
+function setMovableRecorderFullScreenWin(movable: boolean) {
+ recorderFullScreenWin?.setMovable(movable);
+}
+
+function setAlwaysOnTopRecorderFullScreenWin(isAlwaysOnTop: boolean) {
+ recorderFullScreenWin?.setAlwaysOnTop(isAlwaysOnTop);
+}
+
+export {
+ createRecorderFullScreenWin,
+ closeRecorderFullScreenWin,
+ openRecorderFullScreenWin,
+ hideRecorderFullScreenWin,
+ showRecorderFullScreenWin,
+ minimizeRecorderFullScreenWin,
+ setMovableRecorderFullScreenWin,
+ setAlwaysOnTopRecorderFullScreenWin,
+};
diff --git a/packages/desktop/package.json b/packages/desktop/package.json
index 7d2400b3..b86bb5ab 100644
--- a/packages/desktop/package.json
+++ b/packages/desktop/package.json
@@ -41,8 +41,7 @@
"typescript": "^5.2.2",
"uuid": "^9.0.1",
"vite": "^4.4.9",
- "vite-plugin-electron": "^0.13.0-beta.3",
- "vite-plugin-electron-renderer": "^0.14.5"
+ "vite-plugin-electron": "^0.14.0"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
diff --git a/packages/desktop/vite.config.ts b/packages/desktop/vite.config.ts
index f3223c23..f3b391e7 100644
--- a/packages/desktop/vite.config.ts
+++ b/packages/desktop/vite.config.ts
@@ -1,8 +1,6 @@
import { rmSync } from 'node:fs';
import { defineConfig } from 'vite';
-import react from '@vitejs/plugin-react';
-import electron from 'vite-plugin-electron';
-import renderer from 'vite-plugin-electron-renderer';
+import electron from 'vite-plugin-electron/simple';
import pkg from './package.json';
// https://vitejs.dev/config/
@@ -15,21 +13,12 @@ export default defineConfig(({ command }) => {
return {
plugins: [
- react(),
- electron([
- {
- // Main-Process entry file of the Electron App.
+ electron({
+ main: {
entry: 'electron/main/index.ts',
- onstart(options) {
- if (process.env.VSCODE_DEBUG) {
- console.log(/* For `.vscode/.debug.script.mjs` */ '[startup] Electron App');
- } else {
- options.startup();
- }
- },
vite: {
build: {
- sourcemap,
+ sourcemap: sourcemap ? 'inline' : undefined, // #332
minify: isBuild,
outDir: 'dist-electron/main',
rollupOptions: {
@@ -38,13 +27,8 @@ export default defineConfig(({ command }) => {
},
},
},
- {
- entry: 'electron/preload/index.ts',
- onstart(options) {
- // Notify the Renderer-Process to reload the page when the Preload-Scripts build is complete,
- // instead of restarting the entire Electron App.
- options.reload();
- },
+ preload: {
+ input: 'electron/preload/index.ts',
vite: {
build: {
sourcemap: sourcemap ? 'inline' : undefined, // #332
@@ -56,9 +40,7 @@ export default defineConfig(({ command }) => {
},
},
},
- ]),
- // Use Node.js API in the Renderer-process
- renderer(),
+ }),
],
server:
process.env.VSCODE_DEBUG &&
diff --git a/packages/web/CHANGELOG.md b/packages/web/CHANGELOG.md
index b2b86183..314f2e1e 100644
--- a/packages/web/CHANGELOG.md
+++ b/packages/web/CHANGELOG.md
@@ -1,5 +1,9 @@
# @pear-rec/web
+## 1.3.1
+
+feat: 增加录全屏
+
## 1.3.0
feat: 增加钉图功能
diff --git a/packages/web/package.json b/packages/web/package.json
index 25879626..68abb70e 100644
--- a/packages/web/package.json
+++ b/packages/web/package.json
@@ -7,8 +7,7 @@
"build": "rimraf dist && tsc && vite build",
"watch": "tsc && vite build --mode lib -w",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
- "preview": "rimraf dist && tsc && vite build && vite preview",
- "deploy": "deploy.sh"
+ "preview": "vite preview"
},
"dependencies": {
"@pear-rec/recorder": "workspace:^",
diff --git a/packages/web/src/components/card/recordScreenCard.tsx b/packages/web/src/components/card/recordScreenCard.tsx
index 24948b97..722e83ec 100644
--- a/packages/web/src/components/card/recordScreenCard.tsx
+++ b/packages/web/src/components/card/recordScreenCard.tsx
@@ -1,53 +1,39 @@
import React, { useImperativeHandle, forwardRef } from 'react';
import { useTranslation } from 'react-i18next';
import { CameraOutlined, DownOutlined } from '@ant-design/icons';
-import { Card, Space, Dropdown } from 'antd';
-import type { MenuProps } from 'antd';
+import { Card, Space, Button } from 'antd';
const RecordScreenCard = forwardRef((props: any, ref: any) => {
useImperativeHandle(ref, () => ({}));
const { t } = useTranslation();
function handleClipScreen() {
- window.electronAPI
- ? window.electronAPI.sendCsOpenWin()
- : (location.href = '/recorderScreen.html');
- }
-
- function handleRecordScreen() {
- window.electronAPI
- ? window.electronAPI.sendRsOpenWin({ isFullScreen: true })
- : (location.href = '/recorderScreen.html');
+ if (window.isElectron) {
+ window.electronAPI.sendCsOpenWin();
+ window.electronAPI.sendMaCloseWin();
+ } else {
+ location.href = '/recorderFullScreen.html';
+ }
}
- const onClick: MenuProps['onClick'] = ({ key }) => {
- if (key == 'full') {
- handleRecordScreen();
+ function handleFullScreenClick() {
+ if (window.isElectron) {
+ window.electronAPI.sendRfsOpenWin();
+ window.electronAPI.sendMaCloseWin();
} else {
- handleClipScreen();
+ location.href = '/recorderFullScreen.html';
}
- };
-
- const items: MenuProps['items'] = [
- {
- label: '全屏录制',
- key: 'full',
- },
- {
- label: '局部录制',
- key: 'clip',
- },
- ];
+ }
return (
+
+ {t('home.fullScreen')}
+
- {/*
*/}
- {/* */}
- {/* */}
{t('home.screenRecording')}
diff --git a/packages/web/src/pages/home/index.module.scss b/packages/web/src/pages/home/index.module.scss
index 0d67d67b..cfd349b8 100644
--- a/packages/web/src/pages/home/index.module.scss
+++ b/packages/web/src/pages/home/index.module.scss
@@ -11,6 +11,12 @@
text-align: center;
}
}
+ .extra {
+ position: absolute;
+ right: 15px;
+ top: 10px;
+ color: #1677ff;
+ }
.cardContent {
text-align: center;
font-size: 30px;
diff --git a/packages/web/src/pages/index.scss b/packages/web/src/pages/index.scss
index 0887de32..0f69df29 100644
--- a/packages/web/src/pages/index.scss
+++ b/packages/web/src/pages/index.scss
@@ -23,15 +23,15 @@ input,
textarea,
th,
td {
- margin: 0;
- padding: 0;
+ margin: 0;
+ padding: 0;
}
body,
button,
input,
select,
textarea {
- font: 12px/1.5tahoma, arial, \5b8b\4f53;
+ font: 12px/1.5tahoma, arial, \5b8b\4f53;
}
h1,
h2,
@@ -39,180 +39,179 @@ h3,
h4,
h5,
h6 {
- font-size: 100%;
+ font-size: 100%;
}
address,
cite,
dfn,
em,
var {
- font-style: normal;
+ font-style: normal;
}
code,
kbd,
pre,
samp {
- font-family: couriernew, courier, monospace;
+ font-family: couriernew, courier, monospace;
}
small {
- font-size: 12px;
+ font-size: 12px;
}
ul,
ol {
- list-style: none;
+ list-style: none;
}
a {
- text-decoration: none;
+ text-decoration: none;
}
a:hover {
- text-decoration: underline;
+ text-decoration: underline;
}
sup {
- vertical-align: text-top;
+ vertical-align: text-top;
}
sub {
- vertical-align: text-bottom;
+ vertical-align: text-bottom;
}
legend {
- color: #000;
+ color: #000;
}
fieldset,
img {
- border: 0;
+ border: 0;
}
button,
input,
select,
textarea {
- font-size: 100%;
+ font-size: 100%;
}
table {
- border-collapse: collapse;
- border-spacing: 0;
+ border-collapse: collapse;
+ border-spacing: 0;
}
.hide {
- display: none;
+ display: none;
}
.hidden {
- display: none;
+ display: none;
}
.show {
- display: block;
+ display: block;
}
@keyframes blink {
- 0% {
- opacity: 1;
- }
- 50% {
- opacity: 1;
- }
- 50.01% {
- opacity: 0;
- }
- 100% {
- opacity: 0;
- }
+ 0% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 1;
+ }
+ 50.01% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 0;
+ }
}
@-webkit-keyframes blink {
- 0% {
- opacity: 1;
- }
- 50% {
- opacity: 1;
- }
- 50.01% {
- opacity: 0;
- }
- 100% {
- opacity: 0;
- }
+ 0% {
+ opacity: 1;
+ }
+ 50% {
+ opacity: 1;
+ }
+ 50.01% {
+ opacity: 0;
+ }
+ 100% {
+ opacity: 0;
+ }
}
.blink {
- animation: blink 1s linear infinite;
+ color: red;
+ animation: blink 1s linear infinite;
}
#root {
- display: flex;
- justify-content: center;
- .viewImgs {
- .viewer-toolbar {
- background-color: rgba(0, 0, 0, 0.1);
- height: 45px;
- ul {
- padding: 0;
- margin: 0 auto;
- li {
- background-color: transparent;
- width: 40px;
- height: 45px;
- line-height: 45px;
- font-size: 16px;
- border-radius: unset;
- }
- li::before {
- margin: 0 auto;
- margin-top: 15px;
- }
- li:focus {
- box-shadow: unset;
- }
- .viewer-large {
- margin: 0;
- }
- .viewer-download {
- background: transparent url(/imgs/svg/download.svg) no-repeat 0px 0px;
- background-size: 20px;
- background-position: 8px 14px;
- }
- .viewer-print {
- background: transparent url(/imgs/svg/print.svg) no-repeat 0px 0px;
- background-size: 20px;
- background-position: 8px 14px;
- }
- .viewer-always-on-top-win {
- background: transparent url(/imgs/svg/pushpin.svg) no-repeat 0px 0px;
- background-size: 20px;
- background-position: 8px 14px;
- }
- .viewer-edit {
- background: transparent url(/imgs/svg/edit.svg) no-repeat 0px 0px;
- background-size: 20px;
- background-position: 8px 14px;
- }
- .viewer-file {
- background: transparent url(/imgs/svg/file.svg) no-repeat 0px 0px;
- background-size: 20px;
- background-position: 8px 14px;
- }
- .viewer-scan {
- background: transparent url(/imgs/svg/viewer-scan.svg) no-repeat 0px
- 0px;
- background-size: 20px;
- background-position: 8px 14px;
- }
- .viewer-search {
- background: transparent url(/imgs/svg/viewer-search.svg) no-repeat 0px
- 0px;
- background-size: 20px;
- background-position: 8px 14px;
- }
- .current {
- background-color: #1677ff;
- }
- }
- }
- .viewer-button.viewer-fullscreen {
- display: none;
- }
- }
+ display: flex;
+ justify-content: center;
+ .viewImgs {
+ .viewer-toolbar {
+ background-color: rgba(0, 0, 0, 0.1);
+ height: 45px;
+ ul {
+ padding: 0;
+ margin: 0 auto;
+ li {
+ background-color: transparent;
+ width: 40px;
+ height: 45px;
+ line-height: 45px;
+ font-size: 16px;
+ border-radius: unset;
+ }
+ li::before {
+ margin: 0 auto;
+ margin-top: 15px;
+ }
+ li:focus {
+ box-shadow: unset;
+ }
+ .viewer-large {
+ margin: 0;
+ }
+ .viewer-download {
+ background: transparent url(/imgs/svg/download.svg) no-repeat 0px 0px;
+ background-size: 20px;
+ background-position: 8px 14px;
+ }
+ .viewer-print {
+ background: transparent url(/imgs/svg/print.svg) no-repeat 0px 0px;
+ background-size: 20px;
+ background-position: 8px 14px;
+ }
+ .viewer-always-on-top-win {
+ background: transparent url(/imgs/svg/pushpin.svg) no-repeat 0px 0px;
+ background-size: 20px;
+ background-position: 8px 14px;
+ }
+ .viewer-edit {
+ background: transparent url(/imgs/svg/edit.svg) no-repeat 0px 0px;
+ background-size: 20px;
+ background-position: 8px 14px;
+ }
+ .viewer-file {
+ background: transparent url(/imgs/svg/file.svg) no-repeat 0px 0px;
+ background-size: 20px;
+ background-position: 8px 14px;
+ }
+ .viewer-scan {
+ background: transparent url(/imgs/svg/viewer-scan.svg) no-repeat 0px 0px;
+ background-size: 20px;
+ background-position: 8px 14px;
+ }
+ .viewer-search {
+ background: transparent url(/imgs/svg/viewer-search.svg) no-repeat 0px 0px;
+ background-size: 20px;
+ background-position: 8px 14px;
+ }
+ .current {
+ background-color: #1677ff;
+ }
+ }
+ }
+ .viewer-button.viewer-fullscreen {
+ display: none;
+ }
+ }
- .aplayer .aplayer-list {
- max-height: unset !important;
- ol {
- max-height: unset !important;
- }
- }
+ .aplayer .aplayer-list {
+ max-height: unset !important;
+ ol {
+ max-height: unset !important;
+ }
+ }
}
diff --git a/packages/web/src/pages/recorderFullScreen.html b/packages/web/src/pages/recorderFullScreen.html
new file mode 100644
index 00000000..c260ef43
--- /dev/null
+++ b/packages/web/src/pages/recorderFullScreen.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+ pear-rec | 录屏
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/web/src/pages/recorderFullScreen/index.module.scss b/packages/web/src/pages/recorderFullScreen/index.module.scss
new file mode 100644
index 00000000..62343782
--- /dev/null
+++ b/packages/web/src/pages/recorderFullScreen/index.module.scss
@@ -0,0 +1,43 @@
+.recorderFullScreen {
+ display: flex;
+ align-items: center;
+ border-radius: 5px;
+ background-color: rgba(#eee, 0.1);
+
+ :global {
+ .recordIcon {
+ font-size: 18px;
+ width: 32px;
+ padding: 4px 0;
+ }
+ .ant-btn {
+ padding-top: 5px;
+ .ant-btn-icon .anticon {
+ font-size: 18px;
+ }
+ }
+ .singleDigit {
+ background-color: transparent;
+ }
+ .recorderTools {
+ display: flex;
+ align-items: center;
+ .pauseBtn,
+ .stopBtn,
+ .playBtn {
+ padding-top: 2px;
+ color: #1677ff;
+ .ant-btn-icon {
+ font-size: 26px;
+ }
+ }
+ .stopBtn {
+ color: red;
+ }
+ }
+ }
+}
+
+.recorderFullScreen:hover {
+ background-color: rgba(#fff, 0.5);
+}
diff --git a/packages/web/src/pages/recorderFullScreen/index.tsx b/packages/web/src/pages/recorderFullScreen/index.tsx
new file mode 100644
index 00000000..93ea243c
--- /dev/null
+++ b/packages/web/src/pages/recorderFullScreen/index.tsx
@@ -0,0 +1,316 @@
+import React, { useState, useEffect, useRef } from 'react';
+import useTimer from '@pear-rec/timer/src/useTimer';
+import { useTranslation } from 'react-i18next';
+import { CameraOutlined } from '@ant-design/icons';
+import {
+ BsMic,
+ BsMicMute,
+ BsPlayFill,
+ BsPause,
+ BsFillStopFill,
+ BsRecordCircle,
+} from 'react-icons/bs';
+import { SettingOutlined, CloseOutlined } from '@ant-design/icons';
+import { Button, Modal, message } from 'antd';
+import Timer from '@pear-rec/timer';
+import ininitApp from '../../pages/main';
+import { useApi } from '../../api';
+import { useUserApi } from '../../api/user';
+import '@pear-rec/timer/src/Timer/index.module.scss';
+import styles from './index.module.scss';
+
+const RecorderScreen = () => {
+ const { t } = useTranslation();
+ const api = useApi();
+ const userApi = useUserApi();
+ const videoRef = useRef();
+ const mediaStream = useRef();
+ const mediaRecorder = useRef(); // 媒体录制器对象
+ const recordedChunks = useRef([]); // 存储录制的音频数据
+ const audioTrack = useRef(); // 音频轨道对象
+ const [isPause, setIsPause] = useState(false); // 标记是否暂停
+ const [isRecording, setIsRecording] = useState(false); // 标记是否正在录制
+ const [isMute, setIsMute] = useState(false); // 标记是否静音
+ const [isSave, setIsSave] = useState(false); // 标记是否正在保存
+ const timer = useTimer();
+ const [user, setUser] = useState({} as any);
+
+ useEffect(() => {
+ user.id || getCurrentUser();
+ }, []);
+
+ async function getCurrentUser() {
+ try {
+ const res = (await userApi.getCurrentUser()) as any;
+ if (res.code == 0) {
+ setUser(res.data);
+ }
+ } catch (err) {
+ console.log(err);
+ }
+ }
+
+ function handleStartRecording() {
+ startRecordingElectron();
+ }
+
+ function startRecordingWeb() {
+ navigator.mediaDevices
+ .getDisplayMedia({ video: true, audio: true })
+ .then((stream) => {
+ videoRef.current!.srcObject = stream;
+ mediaStream.current = stream;
+ audioTrack.current = stream.getAudioTracks()[0];
+ audioTrack.current.enabled = true; // 开启音频轨道
+ mediaRecorder.current = new MediaRecorder(stream);
+ mediaRecorder.current.addEventListener('dataavailable', (e) => {
+ if (e.data.size > 0) {
+ recordedChunks.current.push(e.data);
+ }
+ });
+ mediaRecorder.current.addEventListener('stop', () => {
+ exportRecording();
+ });
+ mediaRecorder.current.start();
+ setIsRecording(true);
+ timer.start();
+ console.log('开始录像...');
+ })
+ .catch((error) => {
+ console.error('无法获取媒体权限:', error);
+ });
+ }
+
+ // 静音录制
+ function muteRecording() {
+ if (audioTrack.current) {
+ audioTrack.current.enabled = false; // 关闭音频轨道
+ setIsMute(true);
+ console.log('录像已静音');
+ }
+ }
+
+ // 取消静音
+ function unmuteRecording() {
+ if (audioTrack.current) {
+ audioTrack.current.enabled = true; // 开启音频轨道
+ setIsMute(false);
+ console.log('取消静音');
+ }
+ }
+
+ // 暂停录制
+ function pauseRecording() {
+ if (!isPause && mediaRecorder.current.state === 'recording') {
+ mediaRecorder.current.pause();
+ setIsPause(true);
+ timer.pause();
+ window.electronAPI?.sendRsPauseRecord();
+ console.log('录像已暂停');
+ }
+ }
+
+ // 恢复录制
+ function resumeRecording() {
+ if (isPause && mediaRecorder.current.state === 'paused') {
+ mediaRecorder.current.resume();
+ setIsPause(false);
+ timer.start();
+ window.electronAPI?.sendRsStartRecord();
+ console.log('恢复录像...');
+ }
+ }
+
+ // 停止录制,并将录制的音频数据导出为 Blob 对象
+ function stopRecording() {
+ if (isRecording) {
+ mediaRecorder.current.stop();
+ mediaStream.current?.getTracks().forEach((track) => track.stop());
+ setIsRecording(false);
+ timer.reset();
+ recordedChunks.current = [];
+ window.electronAPI?.sendRsStopRecord();
+ console.log('录像完成!');
+ }
+ }
+ // 导出录制的音频文件
+ function saveRecording() {
+ setIsSave(true);
+ stopRecording();
+ }
+
+ // 导出录制的音频文件
+ function exportRecording() {
+ if (recordedChunks.current.length > 0) {
+ const blob = new Blob(recordedChunks.current, {
+ type: 'video/webm',
+ });
+ saveFile(blob);
+ }
+ }
+
+ async function saveFile(blob) {
+ try {
+ recordedChunks.current = [];
+ setIsSave(false);
+ const formData = new FormData();
+ formData.append('type', 'rs');
+ formData.append('userUuid', user.uuid);
+ formData.append('file', blob);
+ const res = (await api.saveFile(formData)) as any;
+ if (res.code == 0) {
+ window.electronAPI?.sendRfsCloseWin();
+ window.electronAPI?.sendVvOpenWin({ videoUrl: res.data.filePath });
+ }
+ } catch (err) {
+ message.error('保存失败');
+ }
+ }
+
+ function handleToggleMute() {
+ isMute ? unmuteRecording() : muteRecording();
+ }
+
+ async function startRecordingElectron() {
+ const sources = await window.electronAPI?.invokeRsGetDesktopCapturerSource();
+ const source = sources.filter((e: any) => e.id == 'screen:0:0')[0];
+ const constraints: any = {
+ audio: false,
+ video: {
+ mandatory: {
+ chromeMediaSource: 'desktop',
+ chromeMediaSourceId: source.id,
+ },
+ },
+ };
+ navigator.mediaDevices
+ .getUserMedia(constraints)
+ .then((stream) => {
+ mediaStream.current = stream;
+ mediaRecorder.current = new MediaRecorder(stream);
+ mediaRecorder.current.addEventListener('dataavailable', (e) => {
+ if (e.data.size > 0) {
+ recordedChunks.current.push(e.data);
+ }
+ });
+ mediaRecorder.current.addEventListener('stop', () => {
+ exportRecording();
+ });
+ mediaRecorder.current.start();
+ setIsRecording(true);
+ timer.start();
+ console.log('开始录像...');
+ })
+ .catch((error) => {
+ console.error('无法获取媒体权限:', error);
+ });
+ return constraints;
+ }
+
+ function handleOpenSettingWin() {
+ window.electronAPI ? window.electronAPI.sendSeOpenWin() : (location.href = '/setting.html');
+ }
+
+ function handleShotScreen() {
+ if (window.electronAPI) {
+ window.electronAPI.sendRsShotScreen();
+ } else {
+ const canvas = document.createElement('canvas');
+ canvas.width = window.innerWidth;
+ canvas.height = window.innerHeight;
+ const context = canvas.getContext('2d');
+ context.drawImage(videoRef.current, 0, 0, canvas.width, canvas.height);
+ const url = canvas.toDataURL('image/png');
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = `pear-rec_${+new Date()}.png`;
+ link.click();
+ }
+ }
+
+ function handleTogglePause() {
+ isPause ? resumeRecording() : pauseRecording();
+ }
+
+ function handleCloseWin() {
+ window.electronAPI?.sendRfsCloseWin();
+ }
+
+ return (
+
+
+
}
+ className="toolbarIcon settingBtn"
+ title={t('nav.setting')}
+ onClick={handleOpenSettingWin}
+ >
+
}
+ className="toolbarIcon closeBtn"
+ title={t('nav.close')}
+ onClick={handleCloseWin}
+ >
+ {/*
}
+ className="toolbarIcon shotScreenBtn"
+ title={t('recorderScreen.shotScreen')}
+ onClick={handleShotScreen}
+ > */}
+
+
+ {isSave ? (
+
+ ) : isRecording ? (
+ <>
+ : }
+ className="toolbarIcon pauseBtn"
+ title={isPause ? t('recorderScreen.resume') : t('recorderScreen.pause')}
+ onClick={handleTogglePause}
+ />
+ {/* : }
+ title={isMute ? t('recorderScreen.unmute') : t('recorderScreen.mute')}
+ /> */}
+ }
+ className="toolbarIcon stopBtn"
+ title={t('recorderScreen.save')}
+ onClick={saveRecording}
+ />
+ >
+ ) : (
+ <>
+ {t('recorderScreen.play')}
+ }
+ className="toolbarIcon playBtn"
+ title={t('recorderScreen.play')}
+ onClick={handleStartRecording}
+ >
+ >
+ )}
+
+
+ );
+};
+
+ininitApp(RecorderScreen);
+export default RecorderScreen;
diff --git a/packages/web/src/pages/recorderScreen/index.module.scss b/packages/web/src/pages/recorderScreen/index.module.scss
index ac771d1f..0051d8f3 100644
--- a/packages/web/src/pages/recorderScreen/index.module.scss
+++ b/packages/web/src/pages/recorderScreen/index.module.scss
@@ -1,94 +1,91 @@
.recorderScreen {
- width: 100vw;
- height: 100vh;
- overflow: hidden;
- :global {
- .selectMedia {
- position: absolute;
- left: 50%;
- transform: translateX(-50%);
- margin-top: 50px;
- z-index: 100;
- }
- .cropArea {
- position: absolute;
- z-index: 10;
- outline: 2px solid #ccc;
- #innerCropArea {
- height: 100%;
- width: 100%;
- }
- .react-resizable-handle {
- background-image: none;
- }
- }
- .screenRecorder {
- position: absolute;
- z-index: 100;
- outline: 2px solid #ccc;
- display: flex;
- cursor: pointer;
- background-color: #ccc;
- .toolbarIcon {
- font-size: 30px;
- line-height: 30px;
- }
- .blink {
- color: red;
- }
- .recordBtn {
- font-size: 18px;
- }
- .settingBtn {
- line-height: 15px;
- }
- .shotScreenBtn {
- line-height: 15px;
- }
- .drgan {
- flex: auto;
- }
- .playBtn {
- color: #1677ff;
- }
- .stopBtn {
- color: red;
- }
- .pauseBtn {
- color: #000;
- }
- .toggleMuteBtn {
- font-size: 18px;
- color: #000;
- }
- .singleDigit {
- background: transparent;
- }
- .playRecorder {
- line-height: 40px;
- display: flex;
- .toolbarTitle {
- height: 30px;
- line-height: 35px;
- }
- }
- }
- .iframeRef {
- position: absolute;
- left: 0;
- top: 0;
- }
- .videoRef {
- width: 100%;
- height: 100%;
- position: absolute;
- left: 0;
- top: 0;
- }
- .control {
- position: absolute;
- right: 0;
- bottom: 0;
- }
- }
+ width: 100vw;
+ height: 100vh;
+ overflow: hidden;
+ :global {
+ .selectMedia {
+ position: absolute;
+ left: 50%;
+ transform: translateX(-50%);
+ margin-top: 50px;
+ z-index: 100;
+ }
+ .cropArea {
+ position: absolute;
+ z-index: 10;
+ outline: 2px solid #ccc;
+ #innerCropArea {
+ height: 100%;
+ width: 100%;
+ }
+ .react-resizable-handle {
+ background-image: none;
+ }
+ }
+ .screenRecorder {
+ position: absolute;
+ z-index: 100;
+ outline: 2px solid #ccc;
+ display: flex;
+ cursor: pointer;
+ background-color: #ccc;
+ .toolbarIcon {
+ font-size: 30px;
+ line-height: 30px;
+ }
+ .recordBtn {
+ font-size: 18px;
+ }
+ .settingBtn {
+ line-height: 15px;
+ }
+ .shotScreenBtn {
+ line-height: 15px;
+ }
+ .drgan {
+ flex: auto;
+ }
+ .playBtn {
+ color: #1677ff;
+ }
+ .stopBtn {
+ color: red;
+ }
+ .pauseBtn {
+ color: #000;
+ }
+ .toggleMuteBtn {
+ font-size: 18px;
+ color: #000;
+ }
+ .singleDigit {
+ background: transparent;
+ }
+ .playRecorder {
+ line-height: 40px;
+ display: flex;
+ .toolbarTitle {
+ height: 30px;
+ line-height: 35px;
+ }
+ }
+ }
+ .iframeRef {
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
+ .videoRef {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ left: 0;
+ top: 0;
+ }
+ .control {
+ position: absolute;
+ right: 0;
+ bottom: 0;
+ }
+ }
}
diff --git a/packages/web/vite.config.ts b/packages/web/vite.config.ts
index c9433d6b..2561159b 100644
--- a/packages/web/vite.config.ts
+++ b/packages/web/vite.config.ts
@@ -10,6 +10,7 @@ const buildOptionsProject = {
index: resolve(__dirname, 'src/pages/index.html'),
shotScreen: resolve(__dirname, 'src/pages/shotScreen.html'),
recorderScreen: resolve(__dirname, 'src/pages/recorderScreen.html'),
+ recorderFullScreen: resolve(__dirname, 'src/pages/recorderFullScreen.html'),
recorderVideo: resolve(__dirname, 'src/pages/recorderVideo.html'),
recorderAudio: resolve(__dirname, 'src/pages/recorderAudio.html'),
viewImage: resolve(__dirname, 'src/pages/viewImage.html'),