@ -0,0 +1,8 @@ |
|||||||
|
# Changesets |
||||||
|
|
||||||
|
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works |
||||||
|
with multi-package repos, or single-package repos to help you version and publish your code. You can |
||||||
|
find the full documentation for it [in our repository](https://github.com/changesets/changesets) |
||||||
|
|
||||||
|
We have a quick list of common questions to get you started engaging with this project in |
||||||
|
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
{ |
||||||
|
"$schema": "https://unpkg.com/@changesets/config@2.3.1/schema.json", |
||||||
|
"changelog": [ |
||||||
|
"@changesets/changelog-github", |
||||||
|
{ |
||||||
|
"repo": "027xiguapi/pear-rec" |
||||||
|
} |
||||||
|
], |
||||||
|
"commit": false, |
||||||
|
"fixed": [], |
||||||
|
"linked": [], |
||||||
|
"access": "public", |
||||||
|
"baseBranch": "main", |
||||||
|
"updateInternalDependencies": "patch", |
||||||
|
"ignore": [] |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
root = true |
||||||
|
|
||||||
|
[*] |
||||||
|
charset = utf-8 |
||||||
|
end_of_line = lf |
||||||
|
|
||||||
|
[*.{html,css,ts,json,tsx,js,scss}] |
||||||
|
indent_style = space |
||||||
|
indent_size = 2 |
||||||
|
insert_final_newline = true |
||||||
|
trim_trailing_whitespace = true |
||||||
@ -0,0 +1,14 @@ |
|||||||
|
module.exports = { |
||||||
|
extends: [require.resolve("@umijs/fabric/dist/eslint")], |
||||||
|
|
||||||
|
// in antd-design-pro
|
||||||
|
globals: { |
||||||
|
ANT_DESIGN_PRO_ONLY_DO_NOT_USE_IN_YOUR_PRODUCTION: true, |
||||||
|
page: true, |
||||||
|
}, |
||||||
|
|
||||||
|
rules: { |
||||||
|
// your rules
|
||||||
|
"@typescript-eslint/no-explicit-any": ["off"], |
||||||
|
}, |
||||||
|
}; |
||||||
@ -0,0 +1,51 @@ |
|||||||
|
name: Build |
||||||
|
'on': |
||||||
|
push: |
||||||
|
tags: |
||||||
|
- v* |
||||||
|
jobs: |
||||||
|
build: |
||||||
|
name: build and release electron app |
||||||
|
runs-on: '${{ matrix.os }}' |
||||||
|
strategy: |
||||||
|
matrix: |
||||||
|
os: [macos-latest, windows-latest, ubuntu-latest] |
||||||
|
steps: |
||||||
|
- name: Checkout |
||||||
|
uses: actions/checkout@v4 |
||||||
|
|
||||||
|
- name: Install Node.js |
||||||
|
uses: actions/setup-node@v4 |
||||||
|
with: |
||||||
|
node-version: 20 |
||||||
|
|
||||||
|
- name: Install pnpm |
||||||
|
uses: pnpm/action-setup@v3 |
||||||
|
with: |
||||||
|
version: 8 |
||||||
|
|
||||||
|
- name: Install Dependencies |
||||||
|
run: pnpm install |
||||||
|
|
||||||
|
- name: Build Electron App |
||||||
|
run: 'pnpm run build:desktop' |
||||||
|
env: |
||||||
|
GITHUB_TOKEN: '${{ secrets.ACCESS_TOKEN }}' |
||||||
|
|
||||||
|
- name: Upload artifacts |
||||||
|
uses: actions/upload-artifact@v4 |
||||||
|
with: |
||||||
|
name: '${{ matrix.os }}' |
||||||
|
path: packages/desktop/release |
||||||
|
|
||||||
|
- name: Release |
||||||
|
uses: softprops/action-gh-release@v2 |
||||||
|
if: startsWith(github.ref, 'refs/tags/') |
||||||
|
with: |
||||||
|
files: | |
||||||
|
packages/desktop/release/*.AppImage |
||||||
|
packages/desktop/release/*.exe |
||||||
|
packages/desktop/release/*.dmg |
||||||
|
packages/desktop/release/*.yml |
||||||
|
env: |
||||||
|
GITHUB_TOKEN: '${{ secrets.ACCESS_TOKEN }}' |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
name: syncToGitee |
||||||
|
'on': |
||||||
|
push: |
||||||
|
branches: |
||||||
|
- main |
||||||
|
jobs: |
||||||
|
repo-sync: |
||||||
|
runs-on: ubuntu-latest |
||||||
|
steps: |
||||||
|
- name: Mirror with force push (git push -f) |
||||||
|
uses: Yikun/hub-mirror-action@master |
||||||
|
with: |
||||||
|
src: github/027xiguapi |
||||||
|
dst: gitee/xiguapi027 |
||||||
|
dst_key: '${{ secrets.GITEE_PRIVATE_KEY }}' |
||||||
|
dst_token: '${{ secrets.GITEE_TOKEN }}' |
||||||
|
static_list: pear-rec |
||||||
|
force_update: true |
||||||
|
debug: true |
||||||
|
- name: Build Gitee Pages |
||||||
|
uses: yanglbme/gitee-pages-action@main |
||||||
|
with: |
||||||
|
# 注意替换为你的 Gitee 用户名 |
||||||
|
gitee-username: xiguapi027 |
||||||
|
# 注意在 Settings->Secrets 配置 GITEE_PASSWORD |
||||||
|
gitee-password: '${{ secrets.GITEE_PASSWORD }}' |
||||||
|
# 注意替换为你的 Gitee 仓库,仓库名严格区分大小写,请准确填写,否则会出错 |
||||||
|
gitee-repo: xiguapi027/pear-rec |
||||||
|
# 要部署的分支,默认是 master,若是其他分支,则需要指定(指定的分支必须存在) |
||||||
|
branch: gh-pages |
||||||
@ -0,0 +1,41 @@ |
|||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
pnpm-debug.log* |
||||||
|
lerna-debug.log* |
||||||
|
|
||||||
|
node_modules |
||||||
|
dist |
||||||
|
dist-ssr |
||||||
|
lib |
||||||
|
dist-electron |
||||||
|
release |
||||||
|
*.local |
||||||
|
dev-dist |
||||||
|
|
||||||
|
stats.html |
||||||
|
|
||||||
|
# Editor directories and files |
||||||
|
.vscode |
||||||
|
.idea |
||||||
|
.DS_Store |
||||||
|
*.suo |
||||||
|
*.ntvs* |
||||||
|
*.njsproj |
||||||
|
*.sln |
||||||
|
*.sw? |
||||||
|
|
||||||
|
#lockfile |
||||||
|
package-lock.json |
||||||
|
pnpm-lock.yaml |
||||||
|
yarn.lock |
||||||
|
/test-results/ |
||||||
|
/playwright-report/ |
||||||
|
/playwright/.cache/ |
||||||
|
Pear Files/ |
||||||
|
|
||||||
|
# docs |
||||||
|
cache/ |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
shamefully-hoist=true |
||||||
|
|
||||||
|
# 在国内使用pnpm安装electron需要配置一下electron的下载路径 |
||||||
|
registry="https://registry.npmmirror.com/" |
||||||
|
electron_mirror="https://registry.npmmirror.com/-/binary/electron/" |
||||||
|
electron_builder_binaries_mirror="https://registry.npmmirror.com/-/binary/electron-builder-binaries/" |
||||||
@ -0,0 +1,34 @@ |
|||||||
|
# Logs |
||||||
|
logs |
||||||
|
*.log |
||||||
|
npm-debug.log* |
||||||
|
yarn-debug.log* |
||||||
|
yarn-error.log* |
||||||
|
pnpm-debug.log* |
||||||
|
lerna-debug.log* |
||||||
|
|
||||||
|
node_modules |
||||||
|
packages/*/node_modules |
||||||
|
dist |
||||||
|
dist-ssr |
||||||
|
dist-electron |
||||||
|
release |
||||||
|
*.local |
||||||
|
|
||||||
|
# Editor directories and files |
||||||
|
.vscode/.debug.env |
||||||
|
.idea |
||||||
|
.DS_Store |
||||||
|
*.suo |
||||||
|
*.ntvs* |
||||||
|
*.njsproj |
||||||
|
*.sln |
||||||
|
*.sw? |
||||||
|
|
||||||
|
#lockfile |
||||||
|
package-lock.json |
||||||
|
pnpm-lock.yaml |
||||||
|
yarn.lock |
||||||
|
/test-results/ |
||||||
|
/playwright-report/ |
||||||
|
/playwright/.cache/ |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
const fabric = require('@umijs/fabric'); |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
...fabric.prettier, |
||||||
|
}; |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
module.exports = { |
||||||
|
extends: [require.resolve("@umijs/fabric/dist/stylelint")], |
||||||
|
rules: { |
||||||
|
// your rules
|
||||||
|
}, |
||||||
|
}; |
||||||
@ -0,0 +1,201 @@ |
|||||||
|
Apache License |
||||||
|
Version 2.0, January 2004 |
||||||
|
http://www.apache.org/licenses/ |
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||||
|
|
||||||
|
1. Definitions. |
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, |
||||||
|
and distribution as defined by Sections 1 through 9 of this document. |
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by |
||||||
|
the copyright owner that is granting the License. |
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all |
||||||
|
other entities that control, are controlled by, or are under common |
||||||
|
control with that entity. For the purposes of this definition, |
||||||
|
"control" means (i) the power, direct or indirect, to cause the |
||||||
|
direction or management of such entity, whether by contract or |
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity. |
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity |
||||||
|
exercising permissions granted by this License. |
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, |
||||||
|
including but not limited to software source code, documentation |
||||||
|
source, and configuration files. |
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical |
||||||
|
transformation or translation of a Source form, including but |
||||||
|
not limited to compiled object code, generated documentation, |
||||||
|
and conversions to other media types. |
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or |
||||||
|
Object form, made available under the License, as indicated by a |
||||||
|
copyright notice that is included in or attached to the work |
||||||
|
(an example is provided in the Appendix below). |
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object |
||||||
|
form, that is based on (or derived from) the Work and for which the |
||||||
|
editorial revisions, annotations, elaborations, or other modifications |
||||||
|
represent, as a whole, an original work of authorship. For the purposes |
||||||
|
of this License, Derivative Works shall not include works that remain |
||||||
|
separable from, or merely link (or bind by name) to the interfaces of, |
||||||
|
the Work and Derivative Works thereof. |
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including |
||||||
|
the original version of the Work and any modifications or additions |
||||||
|
to that Work or Derivative Works thereof, that is intentionally |
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner |
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of |
||||||
|
the copyright owner. For the purposes of this definition, "submitted" |
||||||
|
means any form of electronic, verbal, or written communication sent |
||||||
|
to the Licensor or its representatives, including but not limited to |
||||||
|
communication on electronic mailing lists, source code control systems, |
||||||
|
and issue tracking systems that are managed by, or on behalf of, the |
||||||
|
Licensor for the purpose of discussing and improving the Work, but |
||||||
|
excluding communication that is conspicuously marked or otherwise |
||||||
|
designated in writing by the copyright owner as "Not a Contribution." |
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||||
|
on behalf of whom a Contribution has been received by Licensor and |
||||||
|
subsequently incorporated within the Work. |
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
copyright license to reproduce, prepare Derivative Works of, |
||||||
|
publicly display, publicly perform, sublicense, and distribute the |
||||||
|
Work and such Derivative Works in Source or Object form. |
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
(except as stated in this section) patent license to make, have made, |
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||||
|
where such license applies only to those patent claims licensable |
||||||
|
by such Contributor that are necessarily infringed by their |
||||||
|
Contribution(s) alone or by combination of their Contribution(s) |
||||||
|
with the Work to which such Contribution(s) was submitted. If You |
||||||
|
institute patent litigation against any entity (including a |
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||||
|
or a Contribution incorporated within the Work constitutes direct |
||||||
|
or contributory patent infringement, then any patent licenses |
||||||
|
granted to You under this License for that Work shall terminate |
||||||
|
as of the date such litigation is filed. |
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the |
||||||
|
Work or Derivative Works thereof in any medium, with or without |
||||||
|
modifications, and in Source or Object form, provided that You |
||||||
|
meet the following conditions: |
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or |
||||||
|
Derivative Works a copy of this License; and |
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices |
||||||
|
stating that You changed the files; and |
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works |
||||||
|
that You distribute, all copyright, patent, trademark, and |
||||||
|
attribution notices from the Source form of the Work, |
||||||
|
excluding those notices that do not pertain to any part of |
||||||
|
the Derivative Works; and |
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its |
||||||
|
distribution, then any Derivative Works that You distribute must |
||||||
|
include a readable copy of the attribution notices contained |
||||||
|
within such NOTICE file, excluding those notices that do not |
||||||
|
pertain to any part of the Derivative Works, in at least one |
||||||
|
of the following places: within a NOTICE text file distributed |
||||||
|
as part of the Derivative Works; within the Source form or |
||||||
|
documentation, if provided along with the Derivative Works; or, |
||||||
|
within a display generated by the Derivative Works, if and |
||||||
|
wherever such third-party notices normally appear. The contents |
||||||
|
of the NOTICE file are for informational purposes only and |
||||||
|
do not modify the License. You may add Your own attribution |
||||||
|
notices within Derivative Works that You distribute, alongside |
||||||
|
or as an addendum to the NOTICE text from the Work, provided |
||||||
|
that such additional attribution notices cannot be construed |
||||||
|
as modifying the License. |
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and |
||||||
|
may provide additional or different license terms and conditions |
||||||
|
for use, reproduction, or distribution of Your modifications, or |
||||||
|
for any such Derivative Works as a whole, provided Your use, |
||||||
|
reproduction, and distribution of the Work otherwise complies with |
||||||
|
the conditions stated in this License. |
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||||
|
any Contribution intentionally submitted for inclusion in the Work |
||||||
|
by You to the Licensor shall be under the terms and conditions of |
||||||
|
this License, without any additional terms or conditions. |
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify |
||||||
|
the terms of any separate license agreement you may have executed |
||||||
|
with Licensor regarding such Contributions. |
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade |
||||||
|
names, trademarks, service marks, or product names of the Licensor, |
||||||
|
except as required for reasonable and customary use in describing the |
||||||
|
origin of the Work and reproducing the content of the NOTICE file. |
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or |
||||||
|
agreed to in writing, Licensor provides the Work (and each |
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||||
|
implied, including, without limitation, any warranties or conditions |
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||||
|
appropriateness of using or redistributing the Work and assume any |
||||||
|
risks associated with Your exercise of permissions under this License. |
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory, |
||||||
|
whether in tort (including negligence), contract, or otherwise, |
||||||
|
unless required by applicable law (such as deliberate and grossly |
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be |
||||||
|
liable to You for damages, including any direct, indirect, special, |
||||||
|
incidental, or consequential damages of any character arising as a |
||||||
|
result of this License or out of the use or inability to use the |
||||||
|
Work (including but not limited to damages for loss of goodwill, |
||||||
|
work stoppage, computer failure or malfunction, or any and all |
||||||
|
other commercial damages or losses), even if such Contributor |
||||||
|
has been advised of the possibility of such damages. |
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing |
||||||
|
the Work or Derivative Works thereof, You may choose to offer, |
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity, |
||||||
|
or other liability obligations and/or rights consistent with this |
||||||
|
License. However, in accepting such obligations, You may act only |
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf |
||||||
|
of any other Contributor, and only if You agree to indemnify, |
||||||
|
defend, and hold each Contributor harmless for any liability |
||||||
|
incurred by, or claims asserted against, such Contributor by reason |
||||||
|
of your accepting any such warranty or additional liability. |
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS |
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work. |
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following |
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]" |
||||||
|
replaced with your own identifying information. (Don't include |
||||||
|
the brackets!) The text should be enclosed in the appropriate |
||||||
|
comment syntax for the file format. We also recommend that a |
||||||
|
file or class name and description of purpose be included on the |
||||||
|
same "printed page" as the copyright notice for easier |
||||||
|
identification within third-party archives. |
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner] |
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
|
you may not use this file except in compliance with the License. |
||||||
|
You may obtain a copy of the License at |
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0 |
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software |
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
|
See the License for the specific language governing permissions and |
||||||
|
limitations under the License. |
||||||
@ -0,0 +1,94 @@ |
|||||||
|
<p align="center"> |
||||||
|
<img src="https://027xiguapi.github.io/pear-rec/logo.png" height="120" /> |
||||||
|
<h1>pear-rec</h1> |
||||||
|
<p> |
||||||
|
<img src="https://img.shields.io/github/stars/027xiguapi/pear-rec" alt="stars"> |
||||||
|
<img src="https://img.shields.io/badge/react-v18-blue" alt="react"> |
||||||
|
<img src="https://img.shields.io/badge/electron-v26-blue" alt="electron"> |
||||||
|
<img src="https://img.shields.io/badge/nestjs-v3-blue" alt="nestjs"> |
||||||
|
<img src="https://img.shields.io/badge/-TypeScript-blue?logo=typescript&logoColor=white" alt="typescript"> |
||||||
|
<img src="https://img.shields.io/badge/-Vite-blue?logo=vite&logoColor=white" alt="vite"> |
||||||
|
</p> |
||||||
|
</p> |
||||||
|
|
||||||
|
--- |
||||||
|
|
||||||
|
## README |
||||||
|
|
||||||
|
[中文](README.zh-CN.md) | [English](README.md) | [Deutsch](README.de-DE.md) |
||||||
|
|
||||||
|
## Dokumentation |
||||||
|
|
||||||
|
> pear-rec (pear rec) ist eine plattformübergreifende Software für Bildschirmfotos, Bildschirmaufnahmen, Audio- und Videoaufnahmen. |
||||||
|
> |
||||||
|
> pear-rec(pear rec) ist ein Projekt basierend auf react + electron + vite + viewerjs + plyr + aplayer + react-screenshots. |
||||||
|
> |
||||||
|
> Weitere Funktionen und APIs finden Sie auf der offiziellen [Website](https://027xiguapi.github.io/pear-rec). |
||||||
|
|
||||||
|
## Installation |
||||||
|
|
||||||
|
``` |
||||||
|
gitee: https://gitee.com/xiguapi027/pear-rec |
||||||
|
github: https://github.com/027xiguapi/pear-rec |
||||||
|
``` |
||||||
|
|
||||||
|
## Verwendung |
||||||
|
|
||||||
|
### Erste Schritte |
||||||
|
|
||||||
|
Bevor Sie dieses Repository klonen und ausführen, müssen Sie [Git](https://git-scm.com), [Node.js](https://nodejs.org/en/download/) (mit [npm](https://www.npmjs.com/)) und [pnpm](https://pnpm.io/) auf Ihrem Computer installiert haben. Kommandozeilenaufrufe: |
||||||
|
|
||||||
|
```shell |
||||||
|
# Clone this repository |
||||||
|
git clone https://github.com/027xiguapi/pear-rec.git |
||||||
|
# Go into the repository |
||||||
|
cd pear-rec |
||||||
|
# Install dependencies |
||||||
|
pnpm install |
||||||
|
# Run the web |
||||||
|
pnpm run dev:web |
||||||
|
# Run the server |
||||||
|
pnpm run dev:server |
||||||
|
# Run the desktop |
||||||
|
pnpm run dev:desktop |
||||||
|
# Run the web |
||||||
|
pnpm run start:web |
||||||
|
# Run the desktop |
||||||
|
pnpm run start:desktop |
||||||
|
# Build the web |
||||||
|
pnpm run build:web |
||||||
|
# Build the desktop |
||||||
|
pnpm run build:desktop |
||||||
|
# Clear node_modules |
||||||
|
pnpm run clear |
||||||
|
``` |
||||||
|
|
||||||
|
## Internationalization(I18n) |
||||||
|
|
||||||
|
- [x] Chinesisch |
||||||
|
- [x] Englisch |
||||||
|
- [x] Deutsch |
||||||
|
|
||||||
|
## Test |
||||||
|
|
||||||
|
| OS | Windows | Linux | Macos | |
||||||
|
| ---- | ------- | ----- | ----- | |
||||||
|
| Test | 🟢 | ◯ | ◯ | |
||||||
|
|
||||||
|
## Download |
||||||
|
|
||||||
|
| OS | Windows | Linux | Macos | |
||||||
|
| ---- | ----------------------------------------------------------- | ----- | ----- | |
||||||
|
| link | [Download](https://github.com/027xiguapi/pear-rec/releases) | ◯ | ◯ | |
||||||
|
|
||||||
|
## Feedback |
||||||
|
|
||||||
|
- QQ group |
||||||
|
|
||||||
|
<p align="center"> |
||||||
|
<img src="https://027xiguapi.github.io/pear-rec/imgs/pear-rec_qq_qrcode.png" /> |
||||||
|
</p> |
||||||
|
|
||||||
|
## Lizenz |
||||||
|
|
||||||
|
[pear-rec wird unter der Apache License V2 bereitgestellt.](LICENSE) |
||||||
@ -0,0 +1,70 @@ |
|||||||
|
{ |
||||||
|
"name": "pear-rec", |
||||||
|
"version": "1.4.0", |
||||||
|
"description": " pear-rec is a cross platform software with screenshot, screen recording, audio recording and video recording.", |
||||||
|
"scripts": { |
||||||
|
"start:desktop": "concurrently --names \"WEB,DESKTOP\" -c \"red,blue\" \"npm run dev:web\" \"wait-on tcp:0.0.0.0:9191 && pnpm run dev:desktop\"", |
||||||
|
"dev:desktop": "pnpm run -C packages/desktop dev", |
||||||
|
"build:desktop": "pnpm run -C packages/desktop build && pnpm run copy:web && pnpm run -C packages/desktop build:win", |
||||||
|
"copy:web": "pnpm run -C packages/web build && node tools/copy-files-web2desktop.js", |
||||||
|
"copy:server": "pnpm run -C packages/server build && node tools/copy-files-server2desktop.js", |
||||||
|
"dev:web": "pnpm run -C packages/web dev", |
||||||
|
"build:web": "pnpm run -C packages/web build", |
||||||
|
"only-build:web": "pnpm run -C packages/web build", |
||||||
|
"watch:web": "pnpm run -C packages/web watch", |
||||||
|
"project:web": "pnpm run -C packages/web build && node tools/copy-files-web2server.js", |
||||||
|
"dev:server": "pnpm run -C packages/server dev", |
||||||
|
"build:server": "pnpm run -C packages/server build", |
||||||
|
"dev:docs": "pnpm run -C packages/docs dev", |
||||||
|
"build:docs": "pnpm run -C packages/docs build", |
||||||
|
"preview:docs": "pnpm run -C packages/docs preview", |
||||||
|
"deploy:docs": "pnpm run -C packages/docs deploy", |
||||||
|
"dev:timer": "pnpm run -C packages/timer dev", |
||||||
|
"build:timer": "pnpm run -C packages/timer build", |
||||||
|
"watch:timer": "pnpm run -C packages/timer watch", |
||||||
|
"dev:recorder": "pnpm run -C packages/recorder dev", |
||||||
|
"build:recorder": "pnpm run -C packages/recorder build", |
||||||
|
"watch:recorder": "pnpm run -C packages/recorder watch", |
||||||
|
"dev:screenshot": "pnpm run -C packages/screenshot dev", |
||||||
|
"build:screenshot": "pnpm run -C packages/screenshot build", |
||||||
|
"watch:screenshot": "pnpm run -C packages/screenshot watch", |
||||||
|
"clear": "concurrently --names \"SERVER,WEB,DESKTOP\" \"pnpm run -C packages/web clear\" \"pnpm run -C packages/server clear\" \"pnpm run -C packages/desktop clear\"", |
||||||
|
"copy": "node tools/copy-web-files.js", |
||||||
|
"changeset": "changeset", |
||||||
|
"vp": "changeset version", |
||||||
|
"commit": "cz" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@changesets/changelog-github": "^0.4.8", |
||||||
|
"@changesets/cli": "^2.26.2", |
||||||
|
"@umijs/fabric": "^4.0.1", |
||||||
|
"commitizen": "^4.3.0", |
||||||
|
"concurrently": "^8.2.1", |
||||||
|
"cz-conventional-changelog": "^3.3.0", |
||||||
|
"typescript": "^5.2.2", |
||||||
|
"wait-on": "^7.2.0" |
||||||
|
}, |
||||||
|
"keywords": [ |
||||||
|
"electron", |
||||||
|
"react", |
||||||
|
"antd", |
||||||
|
"aplayer", |
||||||
|
"plyr", |
||||||
|
"viewerjs", |
||||||
|
"tui-image-editor", |
||||||
|
"react-screenshots", |
||||||
|
"react-timer-hook", |
||||||
|
"webav", |
||||||
|
"gif.js" |
||||||
|
], |
||||||
|
"author": "027xiguapi", |
||||||
|
"homepage": "https://027xiguapi.github.io/pear-rec", |
||||||
|
"repository": { |
||||||
|
"type": "git", |
||||||
|
"url": "git@github.com:027xiguapi/pear-rec.git" |
||||||
|
}, |
||||||
|
"bugs": { |
||||||
|
"url": "https://github.com/027xiguapi/pear-rec/issues" |
||||||
|
}, |
||||||
|
"license": "Apache-2.0" |
||||||
|
} |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
# port 端口号 |
||||||
|
VITE_API_URL = http://localhost:9190/ |
||||||
|
VITE_WEB_URL = http://localhost:9191/ |
||||||
|
|
||||||
|
PORT=9190 |
||||||
@ -0,0 +1,101 @@ |
|||||||
|
# @pear-rec/desktop |
||||||
|
|
||||||
|
## 1.3.14 |
||||||
|
|
||||||
|
feat: 增加视频转换 |
||||||
|
|
||||||
|
## 1.3.13 |
||||||
|
|
||||||
|
perf: 升级 electron |
||||||
|
|
||||||
|
## 1.3.12 |
||||||
|
|
||||||
|
fix: 配置修改 |
||||||
|
|
||||||
|
## 1.3.11 |
||||||
|
|
||||||
|
feat: 增加画布 |
||||||
|
|
||||||
|
## 1.3.9 |
||||||
|
|
||||||
|
feat: 首页增加编辑动图 |
||||||
|
|
||||||
|
## 1.3.8 |
||||||
|
|
||||||
|
perf: 录制动图优化 |
||||||
|
|
||||||
|
## 1.3.7 |
||||||
|
|
||||||
|
feat: 增加日志 |
||||||
|
|
||||||
|
## 1.3.6 |
||||||
|
|
||||||
|
feat: 修改 GIF |
||||||
|
|
||||||
|
## 1.3.5 |
||||||
|
|
||||||
|
feat: 增加服务子进程 |
||||||
|
|
||||||
|
## 1.3.4 |
||||||
|
|
||||||
|
feat: 重置设置、显示快捷键 |
||||||
|
|
||||||
|
## 1.3.3 |
||||||
|
|
||||||
|
fix: 录屏 bug |
||||||
|
|
||||||
|
## 1.3.2 |
||||||
|
|
||||||
|
feat: 自动更新软件 |
||||||
|
|
||||||
|
## 1.3.1 |
||||||
|
|
||||||
|
feat: 增加录全屏 |
||||||
|
|
||||||
|
## 1.3.0 |
||||||
|
|
||||||
|
feat: 增加钉图功能 |
||||||
|
|
||||||
|
## 1.2.11 |
||||||
|
|
||||||
|
feat: 打包发布到 git |
||||||
|
|
||||||
|
## 1.2.10 |
||||||
|
|
||||||
|
perf: 设置保存地址 |
||||||
|
|
||||||
|
## 1.2.9 |
||||||
|
|
||||||
|
feat: 设置快捷键 |
||||||
|
|
||||||
|
## 1.2.8 |
||||||
|
|
||||||
|
perf: 优化桌面录屏 |
||||||
|
|
||||||
|
## 1.2.7 |
||||||
|
|
||||||
|
feat: 增加记录 |
||||||
|
|
||||||
|
## 1.2.6 |
||||||
|
|
||||||
|
fix: electron 修改图片加载 bug, perf: 修改端口 |
||||||
|
|
||||||
|
## 1.2.5 |
||||||
|
|
||||||
|
fix: 以管理运行、点击运行软件 |
||||||
|
|
||||||
|
## 1.2.4 |
||||||
|
|
||||||
|
fix: 打包服务无法启动 bug |
||||||
|
|
||||||
|
## 1.2.3 |
||||||
|
|
||||||
|
fix: 录屏下载 bug |
||||||
|
|
||||||
|
## 1.2.2 |
||||||
|
|
||||||
|
feat: 引入@pear-rec/server 服务 |
||||||
|
|
||||||
|
## 1.2.1 |
||||||
|
|
||||||
|
fix: fluent-ffmpeg 引入 bug |
||||||
|
After Width: | Height: | Size: 415 KiB |
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 2.3 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 6.0 KiB |
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 8.7 KiB |
|
After Width: | Height: | Size: 261 KiB |
@ -0,0 +1,8 @@ |
|||||||
|
import { test, expect, _electron as electron } from "@playwright/test"; |
||||||
|
|
||||||
|
test("homepage has title and links to intro page", async () => { |
||||||
|
const app = await electron.launch({ args: [".", "--no-sandbox"] }); |
||||||
|
const page = await app.firstWindow(); |
||||||
|
expect(await page.title()).toBe("Electron + Vite + React"); |
||||||
|
await page.screenshot({ path: "e2e/screenshots/example.png" }); |
||||||
|
}); |
||||||
|
After Width: | Height: | Size: 102 KiB |
@ -0,0 +1,44 @@ |
|||||||
|
/** |
||||||
|
* @see https://www.electron.build/configuration/configuration |
||||||
|
*/ |
||||||
|
{ |
||||||
|
appId: 'com.electron.pear-rec', |
||||||
|
productName: 'pear-rec', |
||||||
|
copyright: 'Copyright © 2024 ${author}', |
||||||
|
asar: true, |
||||||
|
directories: { |
||||||
|
output: 'release', |
||||||
|
}, |
||||||
|
files: ['dist-electron', 'dist', '.env'], |
||||||
|
mac: { |
||||||
|
icon: 'build/icons/mac/icon.icns', |
||||||
|
target: { target: 'dmg' }, |
||||||
|
artifactName: '${productName}-Mac-${version}-Installer.${ext}', |
||||||
|
}, |
||||||
|
linux: { |
||||||
|
icon: 'build/icons/png', |
||||||
|
target: ['AppImage'], |
||||||
|
artifactName: '${productName}-Linux-${version}.${ext}', |
||||||
|
}, |
||||||
|
win: { |
||||||
|
icon: 'build/icons/win/icon.ico', |
||||||
|
target: [ |
||||||
|
{ |
||||||
|
target: 'nsis', |
||||||
|
}, |
||||||
|
], |
||||||
|
artifactName: '${productName}-Windows-${version}-Setup.${ext}', |
||||||
|
requestedExecutionLevel: 'requireAdministrator', |
||||||
|
}, |
||||||
|
nsis: { |
||||||
|
oneClick: false, |
||||||
|
perMachine: false, |
||||||
|
allowToChangeInstallationDirectory: true, |
||||||
|
deleteAppDataOnUninstall: false, |
||||||
|
}, |
||||||
|
publish: { |
||||||
|
provider: 'github', |
||||||
|
repo: 'pear-rec', |
||||||
|
owner: '027xiguapi', |
||||||
|
}, |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
/// <reference types="vite-electron-plugin/electron-env" />
|
||||||
|
|
||||||
|
declare namespace NodeJS { |
||||||
|
interface ProcessEnv { |
||||||
|
VSCODE_DEBUG?: "true"; |
||||||
|
DIST_ELECTRON: string; |
||||||
|
DIST: string; |
||||||
|
/** /dist/ or /public/ */ |
||||||
|
VITE_PUBLIC: string; |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,106 @@ |
|||||||
|
{ |
||||||
|
"tray": { |
||||||
|
"screenshot": "Bildschirmfoto", |
||||||
|
"audioRecording": "Audioaufnahme", |
||||||
|
"screenRecording": "Bildschirmaufnahme", |
||||||
|
"videoRecording": "Videoaufnahme", |
||||||
|
"viewImage": "Bild anzeigen", |
||||||
|
"playAudio": "Audio abspielen", |
||||||
|
"watchVideo": "Video abspielen", |
||||||
|
"home": "Startseite", |
||||||
|
"setting": "Einstellung", |
||||||
|
"help": "Hilfe", |
||||||
|
"relaunch": "Neustart", |
||||||
|
"quit": "Beenden" |
||||||
|
}, |
||||||
|
"nav": { |
||||||
|
"setting": "Einstellung", |
||||||
|
"minimize": "minimieren", |
||||||
|
"maximize": "maximieren", |
||||||
|
"close": "schließen", |
||||||
|
"zoomIn": "zoomIn", |
||||||
|
"zoomOut": "zoomOut", |
||||||
|
"oneToOne": "oneToOne", |
||||||
|
"download": "download", |
||||||
|
"alwaysOnTopWin": "alwaysOnTopWin", |
||||||
|
"openFile": "openFile", |
||||||
|
"uploadFile": "uploadFile", |
||||||
|
"scan": "scan", |
||||||
|
"search": "search", |
||||||
|
"prev": "prev", |
||||||
|
"next": "next", |
||||||
|
"rotateLeft": "rotateLeft", |
||||||
|
"rotateRight": "rotateRight", |
||||||
|
"flipHorizontal": "flipHorizontal", |
||||||
|
"flipVertical": "flipVertical" |
||||||
|
}, |
||||||
|
"home": { |
||||||
|
"audioRecording": "Audioaufnahme", |
||||||
|
"screenRecording": "Bildschirmaufnahme", |
||||||
|
"videoRecording": "Videoaufnahme", |
||||||
|
"screenshot": "Bildschirmfoto", |
||||||
|
"fullScreen": "Vollbild", |
||||||
|
"viewImage": "Bild anzeigen", |
||||||
|
"playAudio": "Audio abspielen", |
||||||
|
"watchVideo": "Video abspielen", |
||||||
|
"history": "Verlauf" |
||||||
|
}, |
||||||
|
"editImage": { |
||||||
|
"save": "speichern" |
||||||
|
}, |
||||||
|
"recorderAudio": { |
||||||
|
"mute": "stummschalten", |
||||||
|
"unmute": "stummschalten aus", |
||||||
|
"delete": "löschen", |
||||||
|
"save": "speichern", |
||||||
|
"play": "abspielen", |
||||||
|
"resume": "fortsetzen", |
||||||
|
"pause": "pausieren" |
||||||
|
}, |
||||||
|
"recorderScreen": { |
||||||
|
"tip": "Zum Starten der Aufnahme auf 'Start' klicken", |
||||||
|
"saving": "speichern", |
||||||
|
"mute": "stummschalten", |
||||||
|
"unmute": "stummschalten aus", |
||||||
|
"resume": "fortsetzen", |
||||||
|
"pause": "pausieren", |
||||||
|
"save": "speichern", |
||||||
|
"play": "abspielen", |
||||||
|
"width": "Breite", |
||||||
|
"height": "Höhe", |
||||||
|
"shotScreen": "shotScreen" |
||||||
|
}, |
||||||
|
"recorderVideo": { |
||||||
|
"tip": "Zum Starten der Aufnahme auf 'Start' klicken", |
||||||
|
"saving": "speichern", |
||||||
|
"mute": "stummschalten", |
||||||
|
"unmute": "stummschalten aus", |
||||||
|
"resume": "fortsetzen", |
||||||
|
"pause": "pausieren", |
||||||
|
"save": "speichern", |
||||||
|
"play": "abspielen", |
||||||
|
"shotScreen": "shotScreen" |
||||||
|
}, |
||||||
|
"viewImage": { |
||||||
|
"uploadText": "Bild auswählen oder reinziehen", |
||||||
|
"uploadHint": "Formate: .jpg, .jpeg, .jfif, .pjpeg, .pjp, .png, .apng, .webp, .avif, .bmp, .gif, .webp, .ico" |
||||||
|
}, |
||||||
|
"viewVideo": { |
||||||
|
"emptyDescription": "Kein Video", |
||||||
|
"emptyBtn": "Video öffnen" |
||||||
|
}, |
||||||
|
"setting": { |
||||||
|
"userSetting": "Nutzereinstellungen", |
||||||
|
"basicSetting": "Basiseinstellungen", |
||||||
|
"serverSetting": "server", |
||||||
|
"shortcutSetting": "shortcut", |
||||||
|
"address": "web address", |
||||||
|
"openFilePath": "open folder", |
||||||
|
"language": "Sprache", |
||||||
|
"filePath": "Dateipfad", |
||||||
|
"openAtLogin": "Bei Anmeldung öffnen", |
||||||
|
"open": "öffnen", |
||||||
|
"close": "schließen", |
||||||
|
"download": "Download" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,109 @@ |
|||||||
|
{ |
||||||
|
"tray": { |
||||||
|
"screenshot": "screenshot", |
||||||
|
"audioRecording": "sound recording", |
||||||
|
"screenRecording": "screen recording", |
||||||
|
"videoRecording": "video recording", |
||||||
|
"viewImage": "view image", |
||||||
|
"playAudio": "play audio", |
||||||
|
"watchVideo": "watch video", |
||||||
|
"home": "home", |
||||||
|
"setting": "setting", |
||||||
|
"help": "help", |
||||||
|
"relaunch": "restart", |
||||||
|
"quit": "quit" |
||||||
|
}, |
||||||
|
"nav": { |
||||||
|
"setting": "setting", |
||||||
|
"minimize": "minimize", |
||||||
|
"maximize": "maximize", |
||||||
|
"close": "close", |
||||||
|
"zoomIn": "zoomIn", |
||||||
|
"zoomOut": "zoomOut", |
||||||
|
"oneToOne": "oneToOne", |
||||||
|
"download": "download", |
||||||
|
"alwaysOnTopWin": "alwaysOnTopWin", |
||||||
|
"openFile": "openFile", |
||||||
|
"uploadFile": "uploadFile", |
||||||
|
"scan": "scan", |
||||||
|
"search": "search", |
||||||
|
"prev": "prev", |
||||||
|
"next": "next", |
||||||
|
"rotateLeft": "rotateLeft", |
||||||
|
"rotateRight": "rotateRight", |
||||||
|
"flipHorizontal": "flipHorizontal", |
||||||
|
"flipVertical": "flipVertical" |
||||||
|
}, |
||||||
|
"home": { |
||||||
|
"audioRecording": "sound recording", |
||||||
|
"screenRecording": "screen recording", |
||||||
|
"videoRecording": "video recording", |
||||||
|
"screenshot": "screenshot", |
||||||
|
"fullScreen": "full", |
||||||
|
"viewImage": "view image", |
||||||
|
"playAudio": "play audio", |
||||||
|
"watchVideo": "watch video", |
||||||
|
"history": "history" |
||||||
|
}, |
||||||
|
"editImage": { |
||||||
|
"save": "save" |
||||||
|
}, |
||||||
|
"recorderAudio": { |
||||||
|
"mute": "mute", |
||||||
|
"unmute": "unmute", |
||||||
|
"delete": "delete", |
||||||
|
"save": "save", |
||||||
|
"play": "play", |
||||||
|
"resume": "resume", |
||||||
|
"pause": "pause" |
||||||
|
}, |
||||||
|
"recorderScreen": { |
||||||
|
"tip": "click the start button below to start recording", |
||||||
|
"saving": "saving", |
||||||
|
"mute": "mute", |
||||||
|
"unmute": "unmute", |
||||||
|
"resume": "resume", |
||||||
|
"pause": "pause", |
||||||
|
"save": "save", |
||||||
|
"play": "play", |
||||||
|
"width": "width", |
||||||
|
"height": "height", |
||||||
|
"shotScreen": "shotScreen" |
||||||
|
}, |
||||||
|
"recorderVideo": { |
||||||
|
"tip": "click the start button below to start recording", |
||||||
|
"saving": "saving", |
||||||
|
"mute": "mute", |
||||||
|
"unmute": "unmute", |
||||||
|
"resume": "resume", |
||||||
|
"pause": "pause", |
||||||
|
"save": "save", |
||||||
|
"play": "play", |
||||||
|
"shotScreen": "shotScreen" |
||||||
|
}, |
||||||
|
"viewImage": { |
||||||
|
"uploadText": "click or drag the image", |
||||||
|
"uploadHint": "allow .jpg、.jpeg、.jfif、.pjpeg、.pjp、.png、.apng、.webp、.avif、.bmp、.gif、.webp、.ico" |
||||||
|
}, |
||||||
|
"viewVideo": { |
||||||
|
"emptyDescription": "暂无视频", |
||||||
|
"emptyBtn": "open video" |
||||||
|
}, |
||||||
|
"setting": { |
||||||
|
"userSetting": "user", |
||||||
|
"basicSetting": "basic", |
||||||
|
"serverSetting": "server", |
||||||
|
"shortcutSetting": "shortcut", |
||||||
|
"address": "web address", |
||||||
|
"openFilePath": "open folder", |
||||||
|
"reset": "reset", |
||||||
|
"language": "language", |
||||||
|
"filePath": "filePath", |
||||||
|
"openAtLogin": "openAtLogin", |
||||||
|
"open": "open", |
||||||
|
"close": "close", |
||||||
|
"download": "download", |
||||||
|
"openServer": "open server", |
||||||
|
"serverPath": "server path" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,109 @@ |
|||||||
|
{ |
||||||
|
"tray": { |
||||||
|
"screenshot": "截图", |
||||||
|
"audioRecording": "录音", |
||||||
|
"screenRecording": "录屏", |
||||||
|
"videoRecording": "录像", |
||||||
|
"viewImage": "查看图片", |
||||||
|
"playAudio": "查看音频", |
||||||
|
"watchVideo": "查看视频", |
||||||
|
"home": "主页面", |
||||||
|
"setting": "设置", |
||||||
|
"help": "教程帮助", |
||||||
|
"relaunch": "重启", |
||||||
|
"quit": "退出" |
||||||
|
}, |
||||||
|
"nav": { |
||||||
|
"setting": "设置", |
||||||
|
"minimize": "最小化", |
||||||
|
"maximize": "最大化", |
||||||
|
"close": "关闭", |
||||||
|
"zoomIn": "放大", |
||||||
|
"zoomOut": "缩小", |
||||||
|
"oneToOne": "还原", |
||||||
|
"download": "下载", |
||||||
|
"alwaysOnTopWin": "置顶", |
||||||
|
"openFile": "打开图片", |
||||||
|
"uploadFile": "打开文件夹", |
||||||
|
"scan": "扫码", |
||||||
|
"search": "搜图", |
||||||
|
"prev": "上一个", |
||||||
|
"next": "下一个", |
||||||
|
"rotateLeft": "左转", |
||||||
|
"rotateRight": "右转", |
||||||
|
"flipHorizontal": "水平翻转", |
||||||
|
"flipVertical": "垂直翻转" |
||||||
|
}, |
||||||
|
"home": { |
||||||
|
"audioRecording": "录音", |
||||||
|
"screenRecording": "录屏", |
||||||
|
"videoRecording": "录像", |
||||||
|
"screenshot": "截图", |
||||||
|
"fullScreen": "全屏", |
||||||
|
"viewImage": "查看图片", |
||||||
|
"playAudio": "查看音频", |
||||||
|
"watchVideo": "查看视频", |
||||||
|
"history": "历史" |
||||||
|
}, |
||||||
|
"editImage": { |
||||||
|
"save": "保存" |
||||||
|
}, |
||||||
|
"recorderAudio": { |
||||||
|
"mute": "静音", |
||||||
|
"unmute": "打开声音", |
||||||
|
"delete": "删除", |
||||||
|
"save": "保存", |
||||||
|
"play": "开始", |
||||||
|
"resume": "继续", |
||||||
|
"pause": "暂停" |
||||||
|
}, |
||||||
|
"recorderScreen": { |
||||||
|
"tip": "点击下面开始按钮开始录制", |
||||||
|
"saving": "正在保存", |
||||||
|
"mute": "静音", |
||||||
|
"unmute": "打开声音", |
||||||
|
"resume": "继续", |
||||||
|
"pause": "暂停", |
||||||
|
"save": "保存", |
||||||
|
"play": "开始", |
||||||
|
"width": "长", |
||||||
|
"height": "高", |
||||||
|
"shotScreen": "截图" |
||||||
|
}, |
||||||
|
"recorderVideo": { |
||||||
|
"tip": "点击下面开始按钮开始录制", |
||||||
|
"saving": "正在保存", |
||||||
|
"mute": "静音", |
||||||
|
"unmute": "打开声音", |
||||||
|
"resume": "继续", |
||||||
|
"pause": "暂停", |
||||||
|
"save": "保存", |
||||||
|
"play": "开始", |
||||||
|
"shotScreen": "截图" |
||||||
|
}, |
||||||
|
"viewImage": { |
||||||
|
"uploadText": "点击或拖着图片", |
||||||
|
"uploadHint": "支持.jpg、.jpeg、.jfif、.pjpeg、.pjp、.png、.apng、.webp、.avif、.bmp、.gif、.webp、.ico" |
||||||
|
}, |
||||||
|
"viewVideo": { |
||||||
|
"emptyDescription": "暂无视频", |
||||||
|
"emptyBtn": "打开视频" |
||||||
|
}, |
||||||
|
"setting": { |
||||||
|
"userSetting": "账户设置", |
||||||
|
"basicSetting": "通用设置", |
||||||
|
"serverSetting": "服务设置", |
||||||
|
"shortcutSetting": "快捷键", |
||||||
|
"address": "软件地址", |
||||||
|
"openFilePath": "打开下载文件夹", |
||||||
|
"reset": "重置", |
||||||
|
"language": "语言", |
||||||
|
"filePath": "保存地址", |
||||||
|
"openAtLogin": "开机自启动", |
||||||
|
"open": "开启", |
||||||
|
"close": "关闭", |
||||||
|
"download": "浏览器下载", |
||||||
|
"openServer": "打开服务", |
||||||
|
"serverPath": "服务地址" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,46 @@ |
|||||||
|
import express, { Application, Request, Response } from 'express'; |
||||||
|
import cors from 'cors'; |
||||||
|
import { HttpsProxyAgent } from 'https-proxy-agent'; |
||||||
|
import { createProxyMiddleware, fixRequestBody } from 'http-proxy-middleware'; |
||||||
|
|
||||||
|
export function initApp() { |
||||||
|
const app: Application = express(); |
||||||
|
|
||||||
|
app.use(cors()); |
||||||
|
|
||||||
|
app.get('/', (req: Request, res: Response) => { |
||||||
|
res.send('Hello World!'); |
||||||
|
}); |
||||||
|
|
||||||
|
app.use( |
||||||
|
'/apiGoogle', |
||||||
|
createProxyMiddleware({ |
||||||
|
target: 'https://lens.google.com', |
||||||
|
changeOrigin: true, |
||||||
|
secure: false, |
||||||
|
logLevel: 'debug', |
||||||
|
onProxyReq: fixRequestBody, |
||||||
|
agent: new HttpsProxyAgent('http://127.0.0.1:7890'), |
||||||
|
pathRewrite: { |
||||||
|
'^/apiGoogle': '', |
||||||
|
}, |
||||||
|
}), |
||||||
|
); |
||||||
|
|
||||||
|
app.use( |
||||||
|
'/apiBaidu', |
||||||
|
createProxyMiddleware({ |
||||||
|
target: 'https://graph.baidu.com/', |
||||||
|
changeOrigin: true, |
||||||
|
secure: false, |
||||||
|
logLevel: 'debug', |
||||||
|
pathRewrite: { |
||||||
|
'^/apiBaidu': '', |
||||||
|
}, |
||||||
|
}), |
||||||
|
); |
||||||
|
|
||||||
|
app.listen(9190, () => { |
||||||
|
console.log('Express app listening on port 9190!'); |
||||||
|
}); |
||||||
|
} |
||||||
@ -0,0 +1,181 @@ |
|||||||
|
import { homedir } from 'node:os'; |
||||||
|
import path from 'node:path'; |
||||||
|
|
||||||
|
process.env.DIST_ELECTRON = path.join(__dirname, '../'); |
||||||
|
process.env.DIST = path.join(process.env.DIST_ELECTRON, '../dist'); |
||||||
|
|
||||||
|
export const isMac = process.platform === 'darwin'; |
||||||
|
export const isLinux = process.platform == 'linux'; |
||||||
|
export const isWin = process.platform == 'win32'; |
||||||
|
|
||||||
|
export const url = import.meta.env.VITE_DEV_SERVER_URL; |
||||||
|
export const WEB_URL = import.meta.env.VITE_WEB_URL; |
||||||
|
export const VITE_API_URL = import.meta.env.VITE_API_URL; |
||||||
|
|
||||||
|
export const preload = path.join(__dirname, '../preload/index.js'); |
||||||
|
export const serverPath = path.join(__dirname, '../server/main.js'); |
||||||
|
export const DIST_ELECTRON = path.join(__dirname, '../'); |
||||||
|
export const DIST = path.join(DIST_ELECTRON, '../dist'); |
||||||
|
|
||||||
|
export const PUBLIC = url ? path.join(DIST_ELECTRON, '../public') : process.env.DIST; |
||||||
|
|
||||||
|
export const ICON = path.join(PUBLIC, './imgs/icons/png/32x32.png'); |
||||||
|
|
||||||
|
export const DOCS_PATH = path.join(homedir(), 'Documents'); |
||||||
|
|
||||||
|
export const PEAR_FILES_PATH = path.join(DOCS_PATH, 'Pear Files'); |
||||||
|
|
||||||
|
export const CONFIG_FILE_PATH = path.join(PEAR_FILES_PATH, `config.json`); |
||||||
|
|
||||||
|
export const DEFAULT_CONFIG_FILE_PATH = path.join(PEAR_FILES_PATH, `default-config.json`); |
||||||
|
|
||||||
|
export const DB_PATH = path.join(PEAR_FILES_PATH, 'db/pear-rec.db'); |
||||||
|
|
||||||
|
export const LOG_PATH = path.join(PEAR_FILES_PATH, 'log'); |
||||||
|
|
||||||
|
export const WIN_CONFIG = { |
||||||
|
main: { |
||||||
|
html: path.join(process.env.DIST, 'index.html'), |
||||||
|
width: 660, |
||||||
|
height: 410, |
||||||
|
autoHideMenuBar: true, |
||||||
|
maximizable: false, |
||||||
|
resizable: false, |
||||||
|
}, |
||||||
|
canvas: { |
||||||
|
html: path.join(process.env.DIST, 'canvas.html'), |
||||||
|
height: 768, |
||||||
|
width: 1024, |
||||||
|
autoHideMenuBar: true, |
||||||
|
}, |
||||||
|
clipScreen: { |
||||||
|
html: path.join(process.env.DIST, 'clipScreen.html'), |
||||||
|
autoHideMenuBar: true, |
||||||
|
frame: false, // 无边框窗口
|
||||||
|
resizable: true, // 窗口大小是否可调整
|
||||||
|
transparent: true, // 使窗口透明
|
||||||
|
fullscreenable: false, // 窗口是否可以进入全屏状态
|
||||||
|
alwaysOnTop: true, |
||||||
|
skipTaskbar: true, |
||||||
|
}, |
||||||
|
editGif: { |
||||||
|
html: path.join(process.env.DIST, 'editGif.html'), |
||||||
|
height: 768, |
||||||
|
width: 1024, |
||||||
|
autoHideMenuBar: true, |
||||||
|
}, |
||||||
|
editImage: { |
||||||
|
html: path.join(process.env.DIST, 'editImage.html'), |
||||||
|
height: 768, |
||||||
|
width: 1024, |
||||||
|
autoHideMenuBar: true, |
||||||
|
}, |
||||||
|
videoConverter: { |
||||||
|
html: path.join(process.env.DIST, 'videoConverter.html'), |
||||||
|
height: 768, |
||||||
|
width: 1024, |
||||||
|
autoHideMenuBar: true, |
||||||
|
}, |
||||||
|
pinImage: { |
||||||
|
html: path.join(process.env.DIST, 'pinImage.html'), |
||||||
|
frame: false, // 无边框窗口
|
||||||
|
transparent: true, // 使窗口透明
|
||||||
|
fullscreenable: false, // 窗口是否可以进入全屏状态
|
||||||
|
alwaysOnTop: true, // 窗口是否永远在别的窗口的上面
|
||||||
|
autoHideMenuBar: true, |
||||||
|
}, |
||||||
|
pinVideo: { |
||||||
|
html: path.join(process.env.DIST, 'pinVideo.html'), |
||||||
|
height: 450, |
||||||
|
width: 600, |
||||||
|
frame: false, // 无边框窗口
|
||||||
|
resizable: true, // 窗口大小是否可调整
|
||||||
|
transparent: true, // 使窗口透明
|
||||||
|
fullscreenable: false, // 窗口是否可以进入全屏状态
|
||||||
|
alwaysOnTop: true, // 窗口是否永远在别的窗口的上面
|
||||||
|
autoHideMenuBar: true, // 自动隐藏菜单栏
|
||||||
|
}, |
||||||
|
recorderAudio: { |
||||||
|
html: path.join(process.env.DIST, 'recorderAudio.html'), |
||||||
|
height: 768, |
||||||
|
width: 1024, |
||||||
|
autoHideMenuBar: true, // 自动隐藏菜单栏
|
||||||
|
}, |
||||||
|
recorderFullScreen: { |
||||||
|
html: path.join(process.env.DIST, 'recorderFullScreen.html'), |
||||||
|
height: 40, |
||||||
|
width: 365, |
||||||
|
center: true, |
||||||
|
transparent: true, // 使窗口透明
|
||||||
|
autoHideMenuBar: true, // 自动隐藏菜单栏
|
||||||
|
frame: false, // 无边框窗口
|
||||||
|
hasShadow: false, // 窗口是否有阴影
|
||||||
|
fullscreenable: false, // 窗口是否可以进入全屏状态
|
||||||
|
alwaysOnTop: true, // 窗口是否永远在别的窗口的上面
|
||||||
|
skipTaskbar: true, |
||||||
|
resizable: false, |
||||||
|
}, |
||||||
|
recorderScreen: { |
||||||
|
html: path.join(process.env.DIST, 'recorderScreen.html'), |
||||||
|
width: 340, |
||||||
|
height: 130, |
||||||
|
autoHideMenuBar: true, // 自动隐藏菜单栏
|
||||||
|
maximizable: false, // 最大
|
||||||
|
hasShadow: false, // 窗口是否有阴影
|
||||||
|
fullscreenable: false, // 窗口是否可以进入全屏状态
|
||||||
|
alwaysOnTop: true, // 窗口是否永远在别的窗口的上面
|
||||||
|
skipTaskbar: true, |
||||||
|
// resizable: false,
|
||||||
|
}, |
||||||
|
recorderVideo: { |
||||||
|
html: path.join(process.env.DIST, 'recorderVideo.html'), |
||||||
|
height: 768, |
||||||
|
width: 1024, |
||||||
|
autoHideMenuBar: true, |
||||||
|
}, |
||||||
|
records: { |
||||||
|
html: path.join(process.env.DIST, 'records.html'), |
||||||
|
width: 1024, |
||||||
|
autoHideMenuBar: true, |
||||||
|
}, |
||||||
|
setting: { |
||||||
|
html: path.join(process.env.DIST, 'setting.html'), |
||||||
|
autoHideMenuBar: true, |
||||||
|
width: 600, |
||||||
|
height: 380, |
||||||
|
}, |
||||||
|
shotScreen: { |
||||||
|
html: path.join(process.env.DIST, 'shotScreen.html'), |
||||||
|
autoHideMenuBar: true, // 自动隐藏菜单栏
|
||||||
|
useContentSize: true, // width 和 height 将设置为 web 页面的尺寸
|
||||||
|
movable: false, // 是否可移动
|
||||||
|
frame: false, // 无边框窗口
|
||||||
|
resizable: false, // 窗口大小是否可调整
|
||||||
|
hasShadow: false, // 窗口是否有阴影
|
||||||
|
transparent: true, // 使窗口透明
|
||||||
|
fullscreenable: true, // 窗口是否可以进入全屏状态
|
||||||
|
fullscreen: true, // 窗口是否全屏
|
||||||
|
simpleFullscreen: true, // 在 macOS 上使用 pre-Lion 全屏
|
||||||
|
alwaysOnTop: true, |
||||||
|
skipTaskbar: true, |
||||||
|
}, |
||||||
|
spliceImage: { |
||||||
|
html: path.join(process.env.DIST, 'spliceImage.html'), |
||||||
|
height: 768, |
||||||
|
width: 1024, |
||||||
|
autoHideMenuBar: true, |
||||||
|
}, |
||||||
|
viewAudio: { |
||||||
|
html: path.join(process.env.DIST, 'viewAudio.html'), |
||||||
|
autoHideMenuBar: true, |
||||||
|
}, |
||||||
|
viewImage: { |
||||||
|
html: path.join(process.env.DIST, 'viewImage.html'), |
||||||
|
frame: false, |
||||||
|
autoHideMenuBar: true, |
||||||
|
}, |
||||||
|
viewVideo: { |
||||||
|
html: path.join(process.env.DIST, 'viewVideo.html'), |
||||||
|
autoHideMenuBar: true, |
||||||
|
}, |
||||||
|
}; |
||||||
@ -0,0 +1,77 @@ |
|||||||
|
import { globalShortcut } from 'electron'; |
||||||
|
import { hideShotScreenWin, showShotScreenWin } from '../win/shotScreenWin'; |
||||||
|
import { openRecorderAudioWin } from '../win/recorderAudioWin'; |
||||||
|
import { openClipScreenWin } from '../win/clipScreenWin'; |
||||||
|
import { openRecorderVideoWin } from '../win/recorderVideoWin'; |
||||||
|
|
||||||
|
function registerGlobalShortcut(data) { |
||||||
|
globalShortcut.register(data['screenshot'], () => { |
||||||
|
showShotScreenWin(); |
||||||
|
}); |
||||||
|
|
||||||
|
globalShortcut.register(data['screenRecording'], () => { |
||||||
|
openClipScreenWin(); |
||||||
|
}); |
||||||
|
|
||||||
|
globalShortcut.register(data['audioRecording'], () => { |
||||||
|
openRecorderAudioWin(); |
||||||
|
}); |
||||||
|
|
||||||
|
globalShortcut.register(data['videoRecording'], () => { |
||||||
|
openRecorderVideoWin(); |
||||||
|
}); |
||||||
|
|
||||||
|
globalShortcut.register('Esc', () => { |
||||||
|
hideShotScreenWin(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function registerShotScreenShortcut(data) { |
||||||
|
globalShortcut.unregister(data.oldKey); |
||||||
|
globalShortcut.register(data.key, () => { |
||||||
|
showShotScreenWin(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function registerRecorderScreenShortcut(data) { |
||||||
|
globalShortcut.unregister(data.oldKey); |
||||||
|
globalShortcut.register(data.key, () => { |
||||||
|
openClipScreenWin(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function registerRecorderAudioShortcut(data) { |
||||||
|
globalShortcut.unregister(data.oldKey); |
||||||
|
globalShortcut.register(data.key, () => { |
||||||
|
openRecorderAudioWin(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function registerRecorderVideoShortcut(data) { |
||||||
|
globalShortcut.unregister(data.oldKey); |
||||||
|
globalShortcut.register(data.key, () => { |
||||||
|
openRecorderVideoWin(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function unregisterGlobalShortcut() { |
||||||
|
globalShortcut.unregister('Alt+Shift+q'); |
||||||
|
globalShortcut.unregister('Alt+Shift+s'); |
||||||
|
globalShortcut.unregister('Alt+Shift+a'); |
||||||
|
globalShortcut.unregister('Alt+Shift+v'); |
||||||
|
globalShortcut.unregister('Esc'); |
||||||
|
} |
||||||
|
|
||||||
|
function unregisterAllGlobalShortcut() { |
||||||
|
globalShortcut.unregisterAll(); |
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
registerGlobalShortcut, |
||||||
|
unregisterGlobalShortcut, |
||||||
|
unregisterAllGlobalShortcut, |
||||||
|
registerShotScreenShortcut, |
||||||
|
registerRecorderScreenShortcut, |
||||||
|
registerRecorderAudioShortcut, |
||||||
|
registerRecorderVideoShortcut, |
||||||
|
}; |
||||||
@ -0,0 +1,83 @@ |
|||||||
|
import { getConfig, initConfig } from '@pear-rec/server/src/config'; |
||||||
|
import { BrowserWindow, app } from 'electron'; |
||||||
|
import { release } from 'node:os'; |
||||||
|
import * as mainWin from '../win/mainWin'; |
||||||
|
import * as shotScreenWin from '../win/shotScreenWin'; |
||||||
|
import { isWin } from './constant'; |
||||||
|
import { unregisterAllGlobalShortcut } from './globalShortcut'; |
||||||
|
import './ipcMain'; |
||||||
|
import './logger'; |
||||||
|
import { protocolHandle, registerSchemesAsPrivileged } from './protocol'; |
||||||
|
import { initTray } from './tray'; |
||||||
|
import { update } from './update'; |
||||||
|
import { initApp } from './app'; |
||||||
|
|
||||||
|
initConfig(); |
||||||
|
initApp(); |
||||||
|
|
||||||
|
// The built directory structure
|
||||||
|
//
|
||||||
|
// ├─┬ dist-electron
|
||||||
|
// │ ├─┬ main
|
||||||
|
// │ │ └── index.js > Electron-Main
|
||||||
|
// │ ├─┬ preload
|
||||||
|
// │ │ └── index.js > Preload-Scripts
|
||||||
|
// │ └─┬ server
|
||||||
|
// │ └── index.js > Server-Scripts
|
||||||
|
// ├─┬ dist
|
||||||
|
// │ └── index.html > Electron-Renderer
|
||||||
|
//
|
||||||
|
|
||||||
|
// Disable GPU Acceleration for Windows 7
|
||||||
|
if (release().startsWith('6.1')) app.disableHardwareAcceleration(); |
||||||
|
|
||||||
|
// Set application name for Windows 10+ notifications
|
||||||
|
if (isWin) app.setAppUserModelId(app.getName()); |
||||||
|
|
||||||
|
app.commandLine.appendSwitch('in-process-gpu'); |
||||||
|
|
||||||
|
if (!app.requestSingleInstanceLock()) { |
||||||
|
app.quit(); |
||||||
|
process.exit(0); |
||||||
|
} |
||||||
|
|
||||||
|
// Remove electron security warnings
|
||||||
|
// This warning only shows in development mode
|
||||||
|
// Read more on https://www.electronjs.org/docs/latest/tutorial/security
|
||||||
|
// process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'
|
||||||
|
|
||||||
|
async function createWindow() { |
||||||
|
mainWin.openMainWin(); |
||||||
|
shotScreenWin.openShotScreenWin(); |
||||||
|
} |
||||||
|
|
||||||
|
registerSchemesAsPrivileged(); |
||||||
|
|
||||||
|
app.whenReady().then(() => { |
||||||
|
const config = getConfig(); |
||||||
|
protocolHandle(); |
||||||
|
createWindow(); |
||||||
|
initTray(config.language); |
||||||
|
update(); |
||||||
|
}); |
||||||
|
|
||||||
|
app.on('will-quit', () => { |
||||||
|
unregisterAllGlobalShortcut(); |
||||||
|
}); |
||||||
|
|
||||||
|
app.on('window-all-closed', () => { |
||||||
|
// if (process.platform !== 'darwin') app.quit();
|
||||||
|
}); |
||||||
|
|
||||||
|
app.on('second-instance', () => { |
||||||
|
mainWin.focusMainWin(); |
||||||
|
}); |
||||||
|
|
||||||
|
app.on('activate', () => { |
||||||
|
const allWindows = BrowserWindow.getAllWindows(); |
||||||
|
if (allWindows.length) { |
||||||
|
allWindows[0].focus(); |
||||||
|
} else { |
||||||
|
createWindow(); |
||||||
|
} |
||||||
|
}); |
||||||
@ -0,0 +1,447 @@ |
|||||||
|
import { editConfig } from '@pear-rec/server/src/config'; |
||||||
|
import { |
||||||
|
BrowserWindow, |
||||||
|
app, |
||||||
|
desktopCapturer, |
||||||
|
dialog, |
||||||
|
ipcMain, |
||||||
|
screen, |
||||||
|
shell, |
||||||
|
webContents, |
||||||
|
} from 'electron'; |
||||||
|
import * as canvasWin from '../win/canvasWin'; |
||||||
|
import * as clipScreenWin from '../win/clipScreenWin'; |
||||||
|
import * as editGifWin from '../win/editGifWin'; |
||||||
|
import * as editImageWin from '../win/editImageWin'; |
||||||
|
import * as mainWin from '../win/mainWin'; |
||||||
|
import * as pinImageWin from '../win/pinImageWin'; |
||||||
|
import * as pinVideoWin from '../win/pinVideoWin'; |
||||||
|
import * as recorderAudioWin from '../win/recorderAudioWin'; |
||||||
|
import * as recorderFullScreenWin from '../win/recorderFullScreenWin'; |
||||||
|
import * as recorderScreenWin from '../win/recorderScreenWin'; |
||||||
|
import * as recorderVideoWin from '../win/recorderVideoWin'; |
||||||
|
import * as recordsWin from '../win/recordsWin'; |
||||||
|
import * as settingWin from '../win/settingWin'; |
||||||
|
import * as shotScreenWin from '../win/shotScreenWin'; |
||||||
|
import * as spliceImageWin from '../win/spliceImageWin'; |
||||||
|
import * as viewAudioWin from '../win/viewAudioWin'; |
||||||
|
import * as viewImageWin from '../win/viewImageWin'; |
||||||
|
import * as viewVideoWin from '../win/viewVideoWin'; |
||||||
|
import * as videoConverterWin from '../win/videoConverterWin'; |
||||||
|
import * as globalShortcut from './globalShortcut'; |
||||||
|
import logger from './logger'; |
||||||
|
import { showNotification } from './notification'; |
||||||
|
import * as utils from './utils'; |
||||||
|
|
||||||
|
const selfWindws = async () => |
||||||
|
await Promise.all( |
||||||
|
webContents |
||||||
|
.getAllWebContents() |
||||||
|
.filter((item) => { |
||||||
|
const win = BrowserWindow.fromWebContents(item); |
||||||
|
return win && win.isVisible(); |
||||||
|
}) |
||||||
|
.map(async (item) => { |
||||||
|
const win = BrowserWindow.fromWebContents(item); |
||||||
|
const thumbnail = await win?.capturePage(); |
||||||
|
return { |
||||||
|
name: win?.getTitle() + (item.devToolsWebContents === null ? '' : '-dev'), // 给dev窗口加上后缀
|
||||||
|
id: win?.getMediaSourceId(), |
||||||
|
thumbnail, |
||||||
|
display_id: '', |
||||||
|
appIcon: null, |
||||||
|
}; |
||||||
|
}), |
||||||
|
); |
||||||
|
|
||||||
|
function initIpcMain() { |
||||||
|
// 日志
|
||||||
|
ipcMain.on('lg:send-msg', (e, msg) => { |
||||||
|
logger.info(msg); |
||||||
|
}); |
||||||
|
|
||||||
|
// 通知
|
||||||
|
ipcMain.on('nt:send-msg', (e, options) => { |
||||||
|
showNotification(options); |
||||||
|
}); |
||||||
|
|
||||||
|
// 主页
|
||||||
|
ipcMain.on('ma:open-win', () => { |
||||||
|
mainWin.openMainWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('ma:close-win', () => { |
||||||
|
mainWin.closeMainWin(); |
||||||
|
}); |
||||||
|
// 录屏
|
||||||
|
ipcMain.on('rs:open-win', (e, search) => { |
||||||
|
clipScreenWin.closeClipScreenWin(); |
||||||
|
recorderScreenWin.closeRecorderScreenWin(); |
||||||
|
mainWin.closeMainWin(); |
||||||
|
recorderScreenWin.openRecorderScreenWin(search); |
||||||
|
}); |
||||||
|
ipcMain.on('rs:close-win', (e, filePath) => { |
||||||
|
recorderScreenWin.closeRecorderScreenWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('rs:hide-win', () => { |
||||||
|
recorderScreenWin.hideRecorderScreenWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('rs:minimize-win', () => { |
||||||
|
recorderScreenWin.minimizeRecorderScreenWin(); |
||||||
|
}); |
||||||
|
ipcMain.handle('rs:get-desktop-capturer-source', async () => { |
||||||
|
return [...(await desktopCapturer.getSources({ types: ['screen'] })), ...(await selfWindws())]; |
||||||
|
}); |
||||||
|
ipcMain.handle('rs:get-bounds-clip', async () => { |
||||||
|
return clipScreenWin.getBoundsClipScreenWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('rs:start-record', (event) => { |
||||||
|
clipScreenWin.setMovableClipScreenWin(false); |
||||||
|
clipScreenWin.setResizableClipScreenWin(false); |
||||||
|
clipScreenWin.setIgnoreMouseEventsClipScreenWin(event, true, { |
||||||
|
forward: true, |
||||||
|
}); |
||||||
|
clipScreenWin.setIsPlayClipScreenWin(true); |
||||||
|
}); |
||||||
|
ipcMain.on('rs:pause-record', (event) => { |
||||||
|
clipScreenWin.setMovableClipScreenWin(true); |
||||||
|
clipScreenWin.setResizableClipScreenWin(true); |
||||||
|
clipScreenWin.setIgnoreMouseEventsClipScreenWin(event, false); |
||||||
|
clipScreenWin.setIsPlayClipScreenWin(false); |
||||||
|
}); |
||||||
|
ipcMain.on('rs:stop-record', (event) => { |
||||||
|
clipScreenWin.setMovableClipScreenWin(true); |
||||||
|
clipScreenWin.setResizableClipScreenWin(true); |
||||||
|
clipScreenWin.setIgnoreMouseEventsClipScreenWin(event, false); |
||||||
|
clipScreenWin.setIsPlayClipScreenWin(false); |
||||||
|
}); |
||||||
|
ipcMain.handle('rs:get-cursor-screen-point', () => { |
||||||
|
return recorderScreenWin.getCursorScreenPointRecorderScreenWin(); |
||||||
|
}); |
||||||
|
ipcMain.handle('rs:is-focused', () => { |
||||||
|
return recorderScreenWin.isFocusedRecorderScreenWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('rs:focus', () => { |
||||||
|
recorderScreenWin.focusRecorderScreenWin(); |
||||||
|
}); |
||||||
|
// 录屏截图
|
||||||
|
ipcMain.on('cs:open-win', (e, search) => { |
||||||
|
clipScreenWin.closeClipScreenWin(); |
||||||
|
clipScreenWin.openClipScreenWin(search); |
||||||
|
}); |
||||||
|
ipcMain.on('cs:close-win', () => { |
||||||
|
clipScreenWin.closeClipScreenWin(); |
||||||
|
recorderScreenWin.closeRecorderScreenWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('cs:hide-win', () => { |
||||||
|
clipScreenWin.hideClipScreenWin(); |
||||||
|
recorderScreenWin.hideRecorderScreenWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('cs:minimize-win', () => { |
||||||
|
clipScreenWin.minimizeClipScreenWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('cs:set-bounds', (event, bounds) => { |
||||||
|
clipScreenWin.setBoundsClipScreenWin(bounds); |
||||||
|
}); |
||||||
|
ipcMain.on('cs:set-ignore-mouse-events', (event, ignore, options) => { |
||||||
|
recorderScreenWin.setIgnoreMouseEventsRecorderScreenWin(event, ignore, options); |
||||||
|
}); |
||||||
|
ipcMain.handle('cs:get-bounds', () => clipScreenWin.getBoundsClipScreenWin()); |
||||||
|
// 截图
|
||||||
|
ipcMain.handle('ss:get-shot-screen-img', async () => { |
||||||
|
const { id } = screen.getPrimaryDisplay(); |
||||||
|
const { width, height } = utils.getScreenSize(); |
||||||
|
const sources = [ |
||||||
|
...(await desktopCapturer.getSources({ |
||||||
|
types: ['screen'], |
||||||
|
thumbnailSize: { |
||||||
|
width, |
||||||
|
height, |
||||||
|
}, |
||||||
|
})), |
||||||
|
]; |
||||||
|
|
||||||
|
let source = sources.filter((e: any) => parseInt(e.display_id, 10) == id)[0]; |
||||||
|
source || (source = sources[0]); |
||||||
|
const img = source.thumbnail.toDataURL(); |
||||||
|
return img; |
||||||
|
}); |
||||||
|
ipcMain.on('ss:open-win', () => { |
||||||
|
shotScreenWin.hideShotScreenWin(); |
||||||
|
shotScreenWin.showShotScreenWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('ss:close-win', () => { |
||||||
|
shotScreenWin.hideShotScreenWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('ss:start-win', async () => { |
||||||
|
mainWin.closeMainWin(); |
||||||
|
setTimeout(() => { |
||||||
|
shotScreenWin.hideShotScreenWin(); |
||||||
|
shotScreenWin.showShotScreenWin(); |
||||||
|
}, 100 * 2); |
||||||
|
}); |
||||||
|
ipcMain.on('ss:save-img', async (e, downloadUrl) => { |
||||||
|
shotScreenWin.downloadURLShotScreenWin(downloadUrl); |
||||||
|
}); |
||||||
|
ipcMain.on('ss:download-img', async (e, downloadUrl) => { |
||||||
|
shotScreenWin.downloadURLShotScreenWin(downloadUrl, true); |
||||||
|
}); |
||||||
|
ipcMain.on('ss:open-external', async (e, tabUrl) => { |
||||||
|
shell.openExternal(tabUrl); |
||||||
|
}); |
||||||
|
ipcMain.handle('ss:get-desktop-capturer-source', async () => { |
||||||
|
return [...(await desktopCapturer.getSources({ types: ['screen'] })), ...(await selfWindws())]; |
||||||
|
}); |
||||||
|
ipcMain.on('ss:copy-img', (e, imgUrl) => { |
||||||
|
shotScreenWin.copyImg(imgUrl); |
||||||
|
}); |
||||||
|
// 图片展示
|
||||||
|
ipcMain.on('vi:open-win', (e, search) => { |
||||||
|
viewImageWin.closeViewImageWin(); |
||||||
|
viewImageWin.openViewImageWin(search); |
||||||
|
}); |
||||||
|
ipcMain.on('vi:close-win', () => { |
||||||
|
viewImageWin.closeViewImageWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('vi:hide-win', () => { |
||||||
|
viewImageWin.hideViewImageWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('vi:minimize-win', () => { |
||||||
|
viewImageWin.minimizeViewImageWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('vi:maximize-win', () => { |
||||||
|
viewImageWin.maximizeViewImageWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('vi:unmaximize-win', () => { |
||||||
|
viewImageWin.unmaximizeViewImageWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('vi:open-file', (e, imgUrl) => { |
||||||
|
shell.openExternal(imgUrl); |
||||||
|
}); |
||||||
|
ipcMain.on('vi:alwaysOnTop-win', (e, isTop) => { |
||||||
|
viewImageWin.setIsAlwaysOnTopViewImageWin(isTop); |
||||||
|
}); |
||||||
|
ipcMain.handle('vi:set-always-on-top', () => { |
||||||
|
const isAlwaysOnTop = viewImageWin.getIsAlwaysOnTopViewImageWin(); |
||||||
|
return viewImageWin.setIsAlwaysOnTopViewImageWin(!isAlwaysOnTop); |
||||||
|
}); |
||||||
|
ipcMain.handle('vi:get-imgs', async (e, img) => { |
||||||
|
const imgs = await viewImageWin.getImgs(img); |
||||||
|
return imgs; |
||||||
|
}); |
||||||
|
ipcMain.on('vi:download-img', async (e, imgUrl) => { |
||||||
|
viewImageWin.downloadImg(imgUrl); |
||||||
|
}); |
||||||
|
|
||||||
|
// 图片编辑
|
||||||
|
ipcMain.on('ei:close-win', () => { |
||||||
|
editImageWin.closeEditImageWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('ei:open-win', (e, search) => { |
||||||
|
editImageWin.openEditImageWin(search); |
||||||
|
}); |
||||||
|
ipcMain.on('ei:download-img', (e, imgUrl) => { |
||||||
|
editImageWin.downloadImg(imgUrl); |
||||||
|
}); |
||||||
|
|
||||||
|
// 图片拼接
|
||||||
|
ipcMain.on('si:close-win', () => { |
||||||
|
spliceImageWin.closeSpliceImageWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('si:open-win', () => { |
||||||
|
spliceImageWin.openSpliceImageWin(); |
||||||
|
}); |
||||||
|
|
||||||
|
// 视频转换
|
||||||
|
ipcMain.on('vc:close-win', () => { |
||||||
|
videoConverterWin.closeVideoConverterWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('vc:open-win', (e, search) => { |
||||||
|
videoConverterWin.openVideoConverterWin(search); |
||||||
|
}); |
||||||
|
|
||||||
|
// 动图编辑
|
||||||
|
ipcMain.on('eg:close-win', () => { |
||||||
|
editGifWin.closeEditGifWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('eg:open-win', (e, search) => { |
||||||
|
editGifWin.openEditGifWin(search); |
||||||
|
}); |
||||||
|
|
||||||
|
// 画画
|
||||||
|
ipcMain.on('ca:close-win', () => { |
||||||
|
canvasWin.closeCanvasWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('ca:open-win', () => { |
||||||
|
canvasWin.openCanvasWin(); |
||||||
|
}); |
||||||
|
|
||||||
|
// 视频音频展示;
|
||||||
|
ipcMain.on('vv:open-win', (e, search) => { |
||||||
|
viewVideoWin.closeViewVideoWin(); |
||||||
|
viewVideoWin.openViewVideoWin(search); |
||||||
|
}); |
||||||
|
ipcMain.on('vv:close-win', () => { |
||||||
|
viewVideoWin.closeViewVideoWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('vv:hide-win', () => { |
||||||
|
viewVideoWin.hideViewVideoWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('vv:minimize-win', () => { |
||||||
|
viewVideoWin.minimizeViewVideoWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('vv:maximize-win', () => { |
||||||
|
viewVideoWin.maximizeViewVideoWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('vv:unmaximize-win', () => { |
||||||
|
viewVideoWin.unmaximizeViewVideoWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('vv:set-always-on-top', (e, isAlwaysOnTop) => { |
||||||
|
viewVideoWin.setAlwaysOnTopViewVideoWin(isAlwaysOnTop); |
||||||
|
}); |
||||||
|
|
||||||
|
// 录音;
|
||||||
|
ipcMain.on('ra:open-win', () => { |
||||||
|
recorderAudioWin.closeRecorderAudioWin(); |
||||||
|
recorderAudioWin.openRecorderAudioWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('ra:close-win', () => { |
||||||
|
recorderAudioWin.closeRecorderAudioWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('ra:hide-win', () => { |
||||||
|
recorderAudioWin.hideRecorderAudioWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('ra:minimize-win', () => { |
||||||
|
recorderAudioWin.minimizeRecorderAudioWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('ra:download-record', (e, downloadUrl) => { |
||||||
|
recorderAudioWin.downloadURLRecorderAudioWin(downloadUrl); |
||||||
|
}); |
||||||
|
ipcMain.on('ra:start-record', () => { |
||||||
|
recorderAudioWin.setSizeRecorderAudioWin(285, 43); |
||||||
|
}); |
||||||
|
ipcMain.on('ra:pause-record', () => { |
||||||
|
recorderAudioWin.setSizeRecorderAudioWin(260, 43); |
||||||
|
}); |
||||||
|
ipcMain.on('ra:stop-record', () => {}); |
||||||
|
// 录像
|
||||||
|
ipcMain.on('rv:open-win', () => { |
||||||
|
recorderVideoWin.closeRecorderVideoWin(); |
||||||
|
mainWin.hideMainWin(); |
||||||
|
recorderVideoWin.openRecorderVideoWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('rv:close-win', () => { |
||||||
|
recorderVideoWin.closeRecorderVideoWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('rv:download-record', (e, downloadUrl) => { |
||||||
|
recorderVideoWin.downloadURLRecorderVideoWin(downloadUrl); |
||||||
|
}); |
||||||
|
// 音频
|
||||||
|
ipcMain.on('va:open-win', (e, search) => { |
||||||
|
viewAudioWin.closeViewAudioWin(); |
||||||
|
viewAudioWin.openViewAudioWin(search); |
||||||
|
}); |
||||||
|
ipcMain.handle('va:get-audios', async (e, audioUrl) => { |
||||||
|
const audios = await viewAudioWin.getAudios(audioUrl); |
||||||
|
return audios; |
||||||
|
}); |
||||||
|
// 设置
|
||||||
|
ipcMain.on('se:open-win', () => { |
||||||
|
settingWin.closeSettingWin(); |
||||||
|
settingWin.openSettingWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('se:close-win', () => { |
||||||
|
settingWin.closeSettingWin(); |
||||||
|
}); |
||||||
|
ipcMain.handle('se:set-filePath', async (e, filePath) => { |
||||||
|
let res = await dialog.showOpenDialog({ |
||||||
|
properties: ['openDirectory'], |
||||||
|
}); |
||||||
|
if (!res.canceled) { |
||||||
|
filePath = res.filePaths[0]; |
||||||
|
} |
||||||
|
return filePath; |
||||||
|
}); |
||||||
|
ipcMain.on('se:set-openAtLogin', (e, isOpen) => { |
||||||
|
app.setLoginItemSettings({ openAtLogin: isOpen }); |
||||||
|
}); |
||||||
|
ipcMain.on('se:set-language', (e, lng) => { |
||||||
|
editConfig('language', lng, () => { |
||||||
|
app.relaunch(); |
||||||
|
app.exit(0); |
||||||
|
}); |
||||||
|
}); |
||||||
|
ipcMain.on('se:set-shortcut', (e, data) => { |
||||||
|
if (data.name == 'screenshot') { |
||||||
|
globalShortcut.registerShotScreenShortcut(data); |
||||||
|
} else if (data.name == 'videoRecording') { |
||||||
|
globalShortcut.registerRecorderVideoShortcut(data); |
||||||
|
} else if (data.name == 'screenRecording') { |
||||||
|
globalShortcut.registerRecorderScreenShortcut(data); |
||||||
|
} else if (data.name == 'audioRecording') { |
||||||
|
globalShortcut.registerRecorderAudioShortcut(data); |
||||||
|
} |
||||||
|
}); |
||||||
|
ipcMain.on('se:set-shortcuts', (e, data) => { |
||||||
|
globalShortcut.registerGlobalShortcut(data); |
||||||
|
}); |
||||||
|
ipcMain.handle('se:get-openAtLogin', () => { |
||||||
|
return app.getLoginItemSettings(); |
||||||
|
}); |
||||||
|
|
||||||
|
// 记录
|
||||||
|
ipcMain.on('re:open-win', () => { |
||||||
|
recordsWin.closeRecordsWin(); |
||||||
|
recordsWin.openRecordsWin(); |
||||||
|
}); |
||||||
|
|
||||||
|
// 钉图
|
||||||
|
ipcMain.on('pi:set-size-win', (e, size) => { |
||||||
|
pinImageWin.setSizePinImageWin(size); |
||||||
|
}); |
||||||
|
ipcMain.on('pi:open-win', (e, search) => { |
||||||
|
pinImageWin.openPinImageWin(search); |
||||||
|
}); |
||||||
|
ipcMain.on('pi:close-win', () => { |
||||||
|
pinImageWin.closePinImageWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('pi:minimize-win', () => { |
||||||
|
pinImageWin.minimizePinImageWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('pi:maximize-win', () => { |
||||||
|
pinImageWin.maximizePinImageWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('pi:unmaximize-win', () => { |
||||||
|
pinImageWin.unmaximizePinImageWin(); |
||||||
|
}); |
||||||
|
ipcMain.handle('pi:get-size-win', () => { |
||||||
|
return pinImageWin.getSizePinImageWin(); |
||||||
|
}); |
||||||
|
|
||||||
|
// 钉图
|
||||||
|
ipcMain.on('pv:open-win', (e, search) => { |
||||||
|
pinVideoWin.openPinVideoWin(search); |
||||||
|
}); |
||||||
|
ipcMain.on('pv:close-win', () => { |
||||||
|
pinVideoWin.closePinVideoWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('pv:minimize-win', () => { |
||||||
|
pinVideoWin.minimizePinVideoWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('pv:maximize-win', () => { |
||||||
|
pinVideoWin.maximizePinVideoWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('pv:unmaximize-win', () => { |
||||||
|
pinVideoWin.unmaximizePinVideoWin(); |
||||||
|
}); |
||||||
|
|
||||||
|
// 录全屏
|
||||||
|
ipcMain.on('rfs:open-win', () => { |
||||||
|
recorderFullScreenWin.closeRecorderFullScreenWin(); |
||||||
|
recorderFullScreenWin.openRecorderFullScreenWin(); |
||||||
|
}); |
||||||
|
ipcMain.on('rfs:close-win', () => { |
||||||
|
recorderFullScreenWin.closeRecorderFullScreenWin(); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
initIpcMain(); |
||||||
@ -0,0 +1,79 @@ |
|||||||
|
// logger.ts
|
||||||
|
import { app } from 'electron'; |
||||||
|
import log from 'electron-log'; |
||||||
|
import path from 'node:path'; |
||||||
|
import { LOG_PATH } from './constant'; |
||||||
|
|
||||||
|
// 关闭控制台打印
|
||||||
|
log.transports.console.level = false; |
||||||
|
log.transports.file.level = 'debug'; |
||||||
|
log.transports.file.maxSize = 10024300; // 文件最大不超过 10M
|
||||||
|
// 输出格式
|
||||||
|
log.transports.file.format = '[{y}-{m}-{d} {h}:{i}:{s}.{ms}] [{level}]{scope} {text}'; |
||||||
|
let date = new Date(); |
||||||
|
let dateStr = date.getFullYear() + '-' + (date.getMonth() + 1) + '-' + date.getDate(); |
||||||
|
// 文件位置及命名方式
|
||||||
|
// 默认位置为:C:\Users\[user]\AppData\Roaming\[appname]\electron_log\
|
||||||
|
// 文件名为:年-月-日.log
|
||||||
|
// 自定义文件保存位置为安装目录下 \log\年-月-日.log
|
||||||
|
log.transports.file.resolvePathFn = () => path.join(LOG_PATH, dateStr + '.log'); |
||||||
|
|
||||||
|
// 有六个日志级别error, warn, info, verbose, debug, silly。默认是silly
|
||||||
|
export default { |
||||||
|
info(param: any) { |
||||||
|
log.info(param); |
||||||
|
}, |
||||||
|
warn(param: any) { |
||||||
|
log.warn(param); |
||||||
|
}, |
||||||
|
error(param: any) { |
||||||
|
log.error(param); |
||||||
|
}, |
||||||
|
debug(param: any) { |
||||||
|
log.debug(param); |
||||||
|
}, |
||||||
|
verbose(param: any) { |
||||||
|
log.verbose(param); |
||||||
|
}, |
||||||
|
silly(param: any) { |
||||||
|
log.silly(param); |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
app.on('ready', async () => { |
||||||
|
// 渲染进程崩溃
|
||||||
|
// app.on('renderer-process-crashed', (event, webContents, killed) => {
|
||||||
|
// log.error(
|
||||||
|
// `APP-ERROR:renderer-process-crashed; event: ${JSON.stringify(
|
||||||
|
// event,
|
||||||
|
// )}; webContents:${JSON.stringify(webContents)}; killed:${JSON.stringify(killed)}`,
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // GPU进程崩溃
|
||||||
|
// app.on('gpu-process-crashed', (event, killed) => {
|
||||||
|
// log.error(
|
||||||
|
// `APP-ERROR:gpu-process-crashed; event: ${JSON.stringify(event)}; killed: ${JSON.stringify(
|
||||||
|
// killed,
|
||||||
|
// )}`,
|
||||||
|
// );
|
||||||
|
// });
|
||||||
|
|
||||||
|
// 渲染进程结束
|
||||||
|
app.on('render-process-gone', async (event, webContents, details) => { |
||||||
|
log.error( |
||||||
|
`APP-ERROR:render-process-gone; event: ${JSON.stringify(event)}; webContents:${JSON.stringify( |
||||||
|
webContents, |
||||||
|
)}; details:${JSON.stringify(details)}`,
|
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
// 子进程结束
|
||||||
|
app.on('child-process-gone', async (event, details) => { |
||||||
|
log.error( |
||||||
|
`APP-ERROR:child-process-gone; event: ${JSON.stringify(event)}; details:${JSON.stringify( |
||||||
|
details, |
||||||
|
)}`,
|
||||||
|
); |
||||||
|
}); |
||||||
|
}); |
||||||
@ -0,0 +1,7 @@ |
|||||||
|
import { Notification } from 'electron'; |
||||||
|
import { ICON } from './constant'; |
||||||
|
|
||||||
|
export function showNotification(_options) { |
||||||
|
const options = { icon: ICON, ..._options }; |
||||||
|
new Notification(options).show(); |
||||||
|
} |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
import { net, protocol } from 'electron'; |
||||||
|
|
||||||
|
export function registerSchemesAsPrivileged() { |
||||||
|
protocol.registerSchemesAsPrivileged([ |
||||||
|
{ |
||||||
|
scheme: 'pearrec', |
||||||
|
privileges: { |
||||||
|
standard: true, |
||||||
|
secure: true, |
||||||
|
supportFetchAPI: true, |
||||||
|
stream: true, |
||||||
|
}, |
||||||
|
}, |
||||||
|
]); |
||||||
|
} |
||||||
|
|
||||||
|
export function protocolHandle() { |
||||||
|
protocol.handle('pearrec', (req) => { |
||||||
|
const { host, pathname } = new URL(req.url); |
||||||
|
|
||||||
|
try { |
||||||
|
return net.fetch(`file://${host}:\\${pathname}`); |
||||||
|
// return net.fetch(`file://C://Users/Administrator/Desktop/pear-rec_1709102672477.mp4`);
|
||||||
|
} catch (error) { |
||||||
|
console.error('protocolHandle', error); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
@ -0,0 +1,28 @@ |
|||||||
|
import { type UtilityProcess, utilityProcess } from 'electron'; |
||||||
|
import { url, serverPath } from './constant'; |
||||||
|
import logger from './logger'; |
||||||
|
|
||||||
|
let serverProcess: null | UtilityProcess = null; |
||||||
|
|
||||||
|
export function initServerProcess() { |
||||||
|
serverProcess = |
||||||
|
url || |
||||||
|
utilityProcess.fork(serverPath, [], { |
||||||
|
stdio: 'pipe', |
||||||
|
}); |
||||||
|
|
||||||
|
serverProcess.on?.('spawn', () => { |
||||||
|
serverProcess.stdout?.on('data', (data) => { |
||||||
|
console.log(`serverProcess output: ${data}`); |
||||||
|
logger.info(`serverProcess output: ${data}`); |
||||||
|
}); |
||||||
|
serverProcess.stderr?.on('data', (data) => { |
||||||
|
console.error(`serverProcess err: ${data}`); |
||||||
|
logger.error(`serverProcess output: ${data}`); |
||||||
|
}); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
export function quitServerProcess() { |
||||||
|
url || serverProcess?.kill(); |
||||||
|
} |
||||||
@ -0,0 +1,126 @@ |
|||||||
|
import { Menu, Tray, app, shell } from 'electron'; |
||||||
|
import { ICON } from './constant'; |
||||||
|
import { openMainWin } from '../win/mainWin'; |
||||||
|
import { showShotScreenWin } from '../win/shotScreenWin'; |
||||||
|
import { openClipScreenWin } from '../win/clipScreenWin'; |
||||||
|
import { openRecorderAudioWin } from '../win/recorderAudioWin'; |
||||||
|
import { openRecorderVideoWin } from '../win/recorderVideoWin'; |
||||||
|
import { openViewImageWin } from '../win/viewImageWin'; |
||||||
|
import { openViewVideoWin } from '../win/viewVideoWin'; |
||||||
|
import { openViewAudioWin } from '../win/viewAudioWin'; |
||||||
|
import { openSettingWin } from '../win/settingWin'; |
||||||
|
import * as zhCN from '../i18n/zh-CN.json'; |
||||||
|
import * as enUS from '../i18n/en-US.json'; |
||||||
|
import * as deDE from '../i18n/de-DE.json'; |
||||||
|
|
||||||
|
const lngMap = { |
||||||
|
zh: zhCN, |
||||||
|
en: enUS, |
||||||
|
de: deDE, |
||||||
|
} as any; |
||||||
|
|
||||||
|
export function initTray(lng: string) { |
||||||
|
let appIcon = new Tray(ICON); |
||||||
|
const t = lngMap[lng].tray; |
||||||
|
const contextMenu = Menu.buildFromTemplate([ |
||||||
|
{ |
||||||
|
label: t.screenshot, |
||||||
|
click: () => { |
||||||
|
showShotScreenWin(); |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: t.screenRecording, |
||||||
|
click: () => { |
||||||
|
openClipScreenWin(); |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: t.audioRecording, |
||||||
|
click: () => { |
||||||
|
openRecorderAudioWin(); |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: t.videoRecording, |
||||||
|
click: () => { |
||||||
|
openRecorderVideoWin(); |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: 'separator', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: t.viewImage, |
||||||
|
click: () => { |
||||||
|
openViewImageWin(); |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: t.watchVideo, |
||||||
|
click: () => { |
||||||
|
openViewVideoWin(); |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: t.playAudio, |
||||||
|
click: () => { |
||||||
|
openViewAudioWin(); |
||||||
|
}, |
||||||
|
}, |
||||||
|
// {
|
||||||
|
// type: "separator",
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// label: "开机自启动",
|
||||||
|
// type: "checkbox",
|
||||||
|
// checked: true,
|
||||||
|
// click: (i) => {
|
||||||
|
// app.setLoginItemSettings({ openAtLogin: i.checked });
|
||||||
|
// },
|
||||||
|
// },
|
||||||
|
{ |
||||||
|
type: 'separator', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: t.home, |
||||||
|
click: () => { |
||||||
|
openMainWin(); |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: t.setting, |
||||||
|
click: () => { |
||||||
|
openSettingWin(); |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: t.help, |
||||||
|
click: () => { |
||||||
|
shell.openExternal('https://027xiguapi.github.io/pear-rec/'); |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
type: 'separator', |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: t.relaunch, |
||||||
|
click: () => { |
||||||
|
app.relaunch(); |
||||||
|
app.exit(0); |
||||||
|
}, |
||||||
|
}, |
||||||
|
{ |
||||||
|
label: t.quit, |
||||||
|
click: () => { |
||||||
|
app.quit(); |
||||||
|
}, |
||||||
|
}, |
||||||
|
]); |
||||||
|
appIcon.setToolTip('梨子REC'); |
||||||
|
appIcon.setContextMenu(contextMenu); |
||||||
|
appIcon.addListener('click', function () { |
||||||
|
openMainWin(); |
||||||
|
}); |
||||||
|
return appIcon; |
||||||
|
} |
||||||
@ -0,0 +1,80 @@ |
|||||||
|
import { app, ipcMain } from 'electron'; |
||||||
|
import { type ProgressInfo, type UpdateDownloadedEvent, autoUpdater } from 'electron-updater'; |
||||||
|
import * as mainWin from '../win/mainWin'; |
||||||
|
|
||||||
|
export function update() { |
||||||
|
// When set to false, the update download will be triggered through the API
|
||||||
|
autoUpdater.autoDownload = false; |
||||||
|
autoUpdater.disableWebInstaller = false; |
||||||
|
autoUpdater.allowDowngrade = false; |
||||||
|
// start check
|
||||||
|
autoUpdater.on('checking-for-update', function () { |
||||||
|
console.log('Checking for update.'); |
||||||
|
}); |
||||||
|
// update available
|
||||||
|
autoUpdater.on('update-available', (arg) => { |
||||||
|
mainWin.sendEuUpdateCanAvailable(arg, true); |
||||||
|
// win.webContents.send('eu:update-can-available', {
|
||||||
|
// update: true,
|
||||||
|
// version: app.getVersion(),
|
||||||
|
// newVersion: arg?.version,
|
||||||
|
// });
|
||||||
|
}); |
||||||
|
// update not available
|
||||||
|
autoUpdater.on('update-not-available', (arg) => { |
||||||
|
mainWin.sendEuUpdateCanAvailable(arg, false); |
||||||
|
// win.webContents.send('eu:update-can-available', {
|
||||||
|
// update: false,
|
||||||
|
// version: app.getVersion(),
|
||||||
|
// newVersion: arg?.version,
|
||||||
|
// });
|
||||||
|
}); |
||||||
|
|
||||||
|
// Checking for updates
|
||||||
|
ipcMain.handle('eu:check-update', async () => { |
||||||
|
if (!app.isPackaged) { |
||||||
|
const error = new Error('The update feature is only available after the package.'); |
||||||
|
return { message: error.message, error }; |
||||||
|
} |
||||||
|
|
||||||
|
try { |
||||||
|
return await autoUpdater.checkForUpdatesAndNotify(); |
||||||
|
} catch (error) { |
||||||
|
return { message: 'Network error', error }; |
||||||
|
} |
||||||
|
}); |
||||||
|
|
||||||
|
// Start downloading and feedback on progress
|
||||||
|
ipcMain.handle('eu:start-download', (event) => { |
||||||
|
startDownload( |
||||||
|
(error, progressInfo) => { |
||||||
|
if (error) { |
||||||
|
// feedback download error message
|
||||||
|
event.sender.send('eu:update-error', { message: error.message, error }); |
||||||
|
} else { |
||||||
|
// feedback update progress message
|
||||||
|
event.sender.send('eu:download-progress', progressInfo); |
||||||
|
} |
||||||
|
}, |
||||||
|
() => { |
||||||
|
// feedback update downloaded message
|
||||||
|
event.sender.send('eu:update-downloaded'); |
||||||
|
}, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
// Install now
|
||||||
|
ipcMain.handle('eu:quit-and-install', () => { |
||||||
|
autoUpdater.quitAndInstall(false, true); |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
function startDownload( |
||||||
|
callback: (error: Error | null, info: ProgressInfo | null) => void, |
||||||
|
complete: (event: UpdateDownloadedEvent) => void, |
||||||
|
) { |
||||||
|
autoUpdater.on('download-progress', (info) => callback(null, info)); |
||||||
|
autoUpdater.on('error', (error) => callback(error, null)); |
||||||
|
autoUpdater.on('update-downloaded', complete); |
||||||
|
autoUpdater.downloadUpdate(); |
||||||
|
} |
||||||
@ -0,0 +1,142 @@ |
|||||||
|
import { screen } from 'electron'; |
||||||
|
import * as fs from 'node:fs'; |
||||||
|
import path from 'node:path'; |
||||||
|
import { PEAR_FILES_PATH } from './constant'; |
||||||
|
|
||||||
|
function getScreenSize() { |
||||||
|
const { size, scaleFactor } = screen.getPrimaryDisplay(); |
||||||
|
return { |
||||||
|
width: size.width * scaleFactor, |
||||||
|
height: size.height * scaleFactor, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
function downloadFile(fileInfo: any) { |
||||||
|
let { base64String, fileName, fileType = 'png' } = fileInfo; |
||||||
|
fileName || (fileName = Number(new Date())); |
||||||
|
const url = base64String.split(',')[1]; // 移除前缀,获取base64数据部分
|
||||||
|
const buffer = Buffer.from(url, 'base64'); // 将base64数据转化为buffer
|
||||||
|
const imagePath = path.join(PEAR_FILES_PATH, `/ss/${fileName}.${fileType}`); // 下载路径
|
||||||
|
|
||||||
|
const directory = path.dirname(imagePath); // 获取文件目录
|
||||||
|
if (!fs.existsSync(directory)) { |
||||||
|
// 检查目录是否存在
|
||||||
|
fs.mkdirSync(directory, { recursive: true }); // 不存在则创建目录
|
||||||
|
} |
||||||
|
fs.writeFileSync(imagePath, buffer); // 将buffer写入本地文件
|
||||||
|
} |
||||||
|
|
||||||
|
function saveFile(fileInfo: any) { |
||||||
|
let { base64String, fileName, fileType = 'png' } = fileInfo; |
||||||
|
fileName || (fileName = Number(new Date())); |
||||||
|
const url = base64String.split(',')[1]; // 移除前缀,获取base64数据部分
|
||||||
|
const buffer = Buffer.from(url, 'base64'); // 将base64数据转化为buffer
|
||||||
|
const filePath = path.join(PEAR_FILES_PATH, `/ss/${fileName}.${fileType}`); // 下载路径
|
||||||
|
|
||||||
|
const directory = path.dirname(filePath); // 获取文件目录
|
||||||
|
if (!fs.existsSync(directory)) { |
||||||
|
// 检查目录是否存在
|
||||||
|
fs.mkdirSync(directory, { recursive: true }); // 不存在则创建目录
|
||||||
|
} |
||||||
|
fs.writeFileSync(filePath, buffer); // 将buffer写入本地文件
|
||||||
|
} |
||||||
|
|
||||||
|
function getImgsByImgUrl(imgUrl: string) { |
||||||
|
const directoryPath = path.dirname(imgUrl); |
||||||
|
const files = fs.readdirSync(directoryPath); // 读取目录内容
|
||||||
|
let imgs: any[] = []; |
||||||
|
let index = 0; |
||||||
|
let currentIndex = 0; |
||||||
|
files.forEach((file) => { |
||||||
|
const filePath = path.join(directoryPath, file); |
||||||
|
|
||||||
|
if (isImageFile(filePath)) { |
||||||
|
filePath == imgUrl && (currentIndex = index); |
||||||
|
imgs.push({ url: `pearrec:///${filePath}`, index }); |
||||||
|
index++; |
||||||
|
} |
||||||
|
}); |
||||||
|
return { imgs, currentIndex }; |
||||||
|
} |
||||||
|
|
||||||
|
function isImageFile(filePath: string): boolean { |
||||||
|
const ext = path.extname(filePath).toLowerCase(); |
||||||
|
return [ |
||||||
|
'.jpg', |
||||||
|
'.jpeg', |
||||||
|
'.jfif', |
||||||
|
'.pjpeg', |
||||||
|
'.pjp', |
||||||
|
'.png', |
||||||
|
'apng', |
||||||
|
'.gif', |
||||||
|
'.bmp', |
||||||
|
'.avif', |
||||||
|
'.webp', |
||||||
|
'.ico', |
||||||
|
].includes(ext); |
||||||
|
} |
||||||
|
|
||||||
|
function getAudiosByAudioUrl(audioUrl: string) { |
||||||
|
const directoryPath = path.dirname(audioUrl); |
||||||
|
const files = fs.readdirSync(directoryPath); // 读取目录内容
|
||||||
|
let audios: any[] = []; |
||||||
|
let index = 0; |
||||||
|
files.forEach((file) => { |
||||||
|
const filePath = path.join(directoryPath, file); |
||||||
|
if (isAudioFile(filePath)) { |
||||||
|
const fileName = path.basename(filePath); |
||||||
|
if (filePath == audioUrl) { |
||||||
|
audios.unshift({ |
||||||
|
url: `pearrec:///${filePath}`, |
||||||
|
name: fileName, |
||||||
|
cover: './imgs/music.png', |
||||||
|
}); |
||||||
|
} else { |
||||||
|
audios.push({ |
||||||
|
url: `pearrec:///${filePath}`, |
||||||
|
name: fileName, |
||||||
|
cover: './imgs/music.png', |
||||||
|
}); |
||||||
|
} |
||||||
|
index++; |
||||||
|
} |
||||||
|
}); |
||||||
|
return audios; |
||||||
|
} |
||||||
|
|
||||||
|
function isAudioFile(filePath: string): boolean { |
||||||
|
const ext = path.extname(filePath).toLowerCase(); |
||||||
|
return [ |
||||||
|
'.mp3', |
||||||
|
'.wav', |
||||||
|
'.aac', |
||||||
|
'.ogg', |
||||||
|
'.flac', |
||||||
|
'.aiff', |
||||||
|
'aif', |
||||||
|
'.m4a', |
||||||
|
'.alac', |
||||||
|
'.ac3', |
||||||
|
].includes(ext); |
||||||
|
} |
||||||
|
|
||||||
|
function readDirectoryVideo(filePath: string) { |
||||||
|
filePath = filePath.replace(/\\/g, '/'); |
||||||
|
return filePath && `pearrec:///${filePath}`; |
||||||
|
} |
||||||
|
|
||||||
|
function readDirectoryImg(filePath: string) { |
||||||
|
filePath = filePath.replace(/\\/g, '/'); |
||||||
|
return filePath && `pearrec:///${filePath}`; |
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
downloadFile, |
||||||
|
getAudiosByAudioUrl, |
||||||
|
getImgsByImgUrl, |
||||||
|
getScreenSize, |
||||||
|
readDirectoryImg, |
||||||
|
readDirectoryVideo, |
||||||
|
saveFile, |
||||||
|
}; |
||||||
@ -0,0 +1,164 @@ |
|||||||
|
import { contextBridge, ipcRenderer } from 'electron'; |
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('electronAPI', { |
||||||
|
// logger
|
||||||
|
sendLogger: (msg: any) => ipcRenderer.send('lg:send-msg', msg), |
||||||
|
|
||||||
|
// notification
|
||||||
|
sendNotification: (options: any) => ipcRenderer.send('nt:send-msg', options), |
||||||
|
|
||||||
|
// mainWin
|
||||||
|
sendMaOpenWin: () => ipcRenderer.send('ma:open-win'), |
||||||
|
sendMaCloseWin: () => ipcRenderer.send('ma:close-win'), |
||||||
|
|
||||||
|
//raWin
|
||||||
|
sendRaCloseWin: () => ipcRenderer.send('ra:close-win'), |
||||||
|
sendRaOpenWin: () => ipcRenderer.send('ra:open-win'), |
||||||
|
sendRaDownloadRecord: (url: string) => ipcRenderer.send('ra:download-record', url), |
||||||
|
|
||||||
|
//rsWin
|
||||||
|
sendRsOpenWin: (search?: any) => ipcRenderer.send('rs:open-win', search), |
||||||
|
sendRsCloseWin: () => ipcRenderer.send('rs:close-win'), |
||||||
|
sendRsHideWin: () => ipcRenderer.send('rs:hide-win'), |
||||||
|
sendRsMinimizeWin: () => ipcRenderer.send('rs:minimize-win'), |
||||||
|
sendRsStartRecord: () => ipcRenderer.send('rs:start-record'), |
||||||
|
sendRsPauseRecord: () => ipcRenderer.send('rs:pause-record'), |
||||||
|
sendRsStopRecord: () => ipcRenderer.send('rs:stop-record'), |
||||||
|
invokeRsGetBoundsClip: () => ipcRenderer.invoke('rs:get-bounds-clip'), |
||||||
|
invokeRsGetDesktopCapturerSource: () => { |
||||||
|
return ipcRenderer.invoke('rs:get-desktop-capturer-source'); |
||||||
|
}, |
||||||
|
invokeRsGetCursorScreenPoint: () => ipcRenderer.invoke('rs:get-cursor-screen-point'), |
||||||
|
invokeRsIsFocused: () => ipcRenderer.invoke('rs:is-focused'), |
||||||
|
sendRsFocus: () => ipcRenderer.send('rs:focus'), |
||||||
|
sendRsSetIgnoreMouseEvents: (ignore: boolean, options: any) => { |
||||||
|
ipcRenderer.send('rs:set-ignore-mouse-events', ignore, options); |
||||||
|
}, |
||||||
|
handleRsGetSizeClipWin: (callback: any) => ipcRenderer.on('rs:get-size-clip-win', callback), |
||||||
|
handleRsGetShotScreen: (callback: any) => ipcRenderer.on('rs:get-shot-screen', callback), |
||||||
|
handleRsGetEndRecord: (callback: any) => ipcRenderer.on('rs:get-end-record', callback), |
||||||
|
|
||||||
|
//csWin
|
||||||
|
sendCsOpenWin: (search?: any) => ipcRenderer.send('cs:open-win', search), |
||||||
|
sendCsCloseWin: () => ipcRenderer.send('cs:close-win'), |
||||||
|
sendCsHideWin: () => ipcRenderer.send('cs:hide-win'), |
||||||
|
sendCsMinimizeWin: () => ipcRenderer.send('cs:minimize-win'), |
||||||
|
sendCsSetIgnoreMouseEvents: (ignore: boolean, options: any) => { |
||||||
|
ipcRenderer.send('cs:set-ignore-mouse-events', ignore, options); |
||||||
|
}, |
||||||
|
invokeCsGetBounds: () => ipcRenderer.invoke('cs:get-bounds'), |
||||||
|
handleCsSetIsPlay: (callback: any) => ipcRenderer.on('cs:set-isPlay', callback), |
||||||
|
sendCsSetBounds: (bounds: any) => { |
||||||
|
ipcRenderer.send('cs:set-bounds', bounds); |
||||||
|
}, |
||||||
|
|
||||||
|
//rvWin
|
||||||
|
sendRvCloseWin: () => ipcRenderer.send('rv:close-win'), |
||||||
|
sendRvOpenWin: () => ipcRenderer.send('rv:open-win'), |
||||||
|
sendRvDownloadRecord: (url: string) => ipcRenderer.send('rv:download-record', url), |
||||||
|
|
||||||
|
//ssWin
|
||||||
|
sendSsOpenWin: () => ipcRenderer.send('ss:open-win'), |
||||||
|
sendSsCloseWin: () => ipcRenderer.send('ss:close-win'), |
||||||
|
sendSsStartWin: () => ipcRenderer.send('ss:start-win'), |
||||||
|
sendSsShowWin: (callback) => ipcRenderer.on('ss:show-win', (e, img) => callback(img)), |
||||||
|
sendSsHideWin: (callback) => ipcRenderer.on('ss:hide-win', () => callback()), |
||||||
|
invokeSsGetShotScreenImg: () => ipcRenderer.invoke('ss:get-shot-screen-img'), |
||||||
|
sendSsDownloadImg: (imgUrl: string) => ipcRenderer.send('ss:download-img', imgUrl), |
||||||
|
sendSsSaveImg: (imgUrl: string) => ipcRenderer.send('ss:save-img', imgUrl), |
||||||
|
sendSsOpenExternal: (tabUrl: string) => ipcRenderer.send('ss:open-external', tabUrl), |
||||||
|
sendSsCopyImg: (imgUrl: string) => ipcRenderer.send('ss:copy-img', imgUrl), |
||||||
|
|
||||||
|
//viWin
|
||||||
|
sendViCloseWin: () => ipcRenderer.send('vi:close-win'), |
||||||
|
sendViOpenWin: (search?: string) => ipcRenderer.send('vi:open-win', search), |
||||||
|
sendViMinimizeWin: () => ipcRenderer.send('vi:minimize-win'), |
||||||
|
sendViMaximizeWin: () => ipcRenderer.send('vi:maximize-win'), |
||||||
|
sendViUnmaximizeWin: () => ipcRenderer.send('vi:unmaximize-win'), |
||||||
|
sendViAlwaysOnTopWin: (isTop: boolean) => ipcRenderer.send('vi:alwaysOnTop-win', isTop), |
||||||
|
sendViOpenFile: (imgUrl: string) => ipcRenderer.send('vi:open-file', imgUrl), |
||||||
|
invokeViSetIsAlwaysOnTop: () => ipcRenderer.invoke('vi:set-always-on-top'), |
||||||
|
invokeViGetImgs: (imgUrl: string) => ipcRenderer.invoke('vi:get-imgs', imgUrl), |
||||||
|
sendViDownloadImg: (img: string) => ipcRenderer.send('vi:download-img', img), |
||||||
|
sendViSetHistoryImg: (img: string) => { |
||||||
|
ipcRenderer.send('vi:set-historyImg', img); |
||||||
|
}, |
||||||
|
|
||||||
|
//eiWin
|
||||||
|
sendEiCloseWin: () => ipcRenderer.send('ei:close-win'), |
||||||
|
sendEiOpenWin: (search?: string) => ipcRenderer.send('ei:open-win', search), |
||||||
|
sendEiDownloadImg: (imgUrl?: string) => ipcRenderer.send('ei:download-img', imgUrl), |
||||||
|
|
||||||
|
//egWin
|
||||||
|
sendEgCloseWin: () => ipcRenderer.send('eg:close-win'), |
||||||
|
sendEgOpenWin: (search?: string) => ipcRenderer.send('eg:open-win', search), |
||||||
|
|
||||||
|
//vcWin
|
||||||
|
sendVcCloseWin: () => ipcRenderer.send('vc:close-win'), |
||||||
|
sendVcOpenWin: (search?: string) => ipcRenderer.send('vc:open-win', search), |
||||||
|
|
||||||
|
//caWin
|
||||||
|
sendCaCloseWin: () => ipcRenderer.send('ca:close-win'), |
||||||
|
sendCaOpenWin: () => ipcRenderer.send('ca:open-win'), |
||||||
|
|
||||||
|
//siWin
|
||||||
|
sendSiCloseWin: () => ipcRenderer.send('si:close-win'), |
||||||
|
sendSiOpenWin: () => ipcRenderer.send('si:open-win'), |
||||||
|
|
||||||
|
//vvWin
|
||||||
|
sendVvOpenWin: (search?: string) => ipcRenderer.send('vv:open-win', search), |
||||||
|
sendVvCloseWin: () => ipcRenderer.send('vv:close-win'), |
||||||
|
invokeVvGetHistoryVideo: () => ipcRenderer.invoke('vv:get-historyVideo'), |
||||||
|
sendVvSetHistoryVideo: (img: string) => ipcRenderer.send('vv:set-historyVideo', img), |
||||||
|
|
||||||
|
//vaWin
|
||||||
|
sendVaOpenWin: (search?: any) => ipcRenderer.send('va:open-win', search), |
||||||
|
invokeVaGetAudios: (audioUrl: any) => ipcRenderer.invoke('va:get-audios', audioUrl), |
||||||
|
//seWin 设置
|
||||||
|
sendSeOpenWin: () => ipcRenderer.send('se:open-win'), |
||||||
|
invokeSeGetUser: () => ipcRenderer.invoke('se:get-user'), |
||||||
|
invokeSeSetFilePath: (filePath: string) => ipcRenderer.invoke('se:set-filePath', filePath), |
||||||
|
invokeSeGetFilePath: () => ipcRenderer.invoke('se:get-filePath'), |
||||||
|
sendSeSetOpenAtLogin: (isOpen: boolean) => ipcRenderer.send('se:set-openAtLogin', isOpen), |
||||||
|
sendSeSetLanguage: (lng: string) => ipcRenderer.send('se:set-language', lng), |
||||||
|
sendSeSetShortcut: (data: string) => ipcRenderer.send('se:set-shortcut', data), |
||||||
|
sendSeSetShortcuts: (data: string) => ipcRenderer.send('se:set-shortcuts', data), |
||||||
|
invokeSeGetOpenAtLogin: () => ipcRenderer.invoke('se:get-openAtLogin'), |
||||||
|
|
||||||
|
//re 记录
|
||||||
|
sendReOpenWin: () => ipcRenderer.send('re:open-win'), |
||||||
|
|
||||||
|
//pi 钉图
|
||||||
|
sendPiSetSizeWin: (size: any) => ipcRenderer.send('pi:set-size-win', size), |
||||||
|
sendPiOpenWin: (search?: any) => ipcRenderer.send('pi:open-win', search), |
||||||
|
sendPiCloseWin: () => ipcRenderer.send('pi:close-win'), |
||||||
|
sendPiMinimizeWin: () => ipcRenderer.send('pi:minimize-win'), |
||||||
|
sendPiMaximizeWin: () => ipcRenderer.send('pi:maximize-win'), |
||||||
|
sendPiUnmaximizeWin: () => ipcRenderer.send('pi:unmaximize-win'), |
||||||
|
invokePiGetSizeWin: () => ipcRenderer.invoke('pi:get-size-win'), |
||||||
|
|
||||||
|
//pi 钉视频
|
||||||
|
sendPvOpenWin: (search?: any) => ipcRenderer.send('pv:open-win', search), |
||||||
|
sendPvCloseWin: () => ipcRenderer.send('pv:close-win'), |
||||||
|
sendPvMinimizeWin: () => ipcRenderer.send('pv:minimize-win'), |
||||||
|
sendPvMaximizeWin: () => ipcRenderer.send('pv:maximize-win'), |
||||||
|
sendPvUnmaximizeWin: () => ipcRenderer.send('pv:unmaximize-win'), |
||||||
|
|
||||||
|
// rfs 全屏录屏
|
||||||
|
sendRfsOpenWin: () => ipcRenderer.send('rfs:open-win'), |
||||||
|
sendRfsCloseWin: () => ipcRenderer.send('rfs:close-win'), |
||||||
|
|
||||||
|
// Eu 自动更新
|
||||||
|
handleEuUpdateCanAvailable: (callback: any) => |
||||||
|
ipcRenderer.on('eu:update-can-available', callback), |
||||||
|
handleEuUpdateeError: (callback: any) => ipcRenderer.on('eu:update-error', callback), |
||||||
|
handleEuDownloadProgress: (callback: any) => ipcRenderer.on('eu:download-progress', callback), |
||||||
|
handleEuUpdateDownloaded: (callback: any) => ipcRenderer.on('eu:update-downloaded', callback), |
||||||
|
invokeEuQuitAndInstall: () => ipcRenderer.invoke('eu:quit-and-install'), |
||||||
|
invokeEuStartDownload: () => ipcRenderer.invoke('eu:start-download'), |
||||||
|
invokeEuCheckUpdate: () => ipcRenderer.invoke('eu:check-update'), |
||||||
|
offEuUpdateCanAvailable: (callback: any) => ipcRenderer.on('eu:update-can-available', callback), |
||||||
|
offEuUpdateeError: (callback: any) => ipcRenderer.on('eu:update-error', callback), |
||||||
|
offEuDownloadProgress: (callback: any) => ipcRenderer.on('eu:download-progress', callback), |
||||||
|
offEuUpdateDownloaded: (callback: any) => ipcRenderer.on('eu:update-downloaded', callback), |
||||||
|
}); |
||||||
@ -0,0 +1,166 @@ |
|||||||
|
import './electronAPI'; |
||||||
|
|
||||||
|
function domReady(condition: DocumentReadyState[] = ['complete', 'interactive']) { |
||||||
|
return new Promise((resolve) => { |
||||||
|
if (condition.includes(document.readyState)) { |
||||||
|
resolve(true); |
||||||
|
} else { |
||||||
|
document.addEventListener('readystatechange', () => { |
||||||
|
if (condition.includes(document.readyState)) { |
||||||
|
resolve(true); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
const safeDOM = { |
||||||
|
append(parent: HTMLElement, child: HTMLElement) { |
||||||
|
if (!Array.from(parent.children).find((e) => e === child)) { |
||||||
|
return parent.appendChild(child); |
||||||
|
} |
||||||
|
}, |
||||||
|
remove(parent: HTMLElement, child: HTMLElement) { |
||||||
|
if (Array.from(parent.children).find((e) => e === child)) { |
||||||
|
return parent.removeChild(child); |
||||||
|
} |
||||||
|
}, |
||||||
|
}; |
||||||
|
|
||||||
|
function useLoading() { |
||||||
|
const className = `loaders-css__square-spin`; |
||||||
|
const styleContent = ` |
||||||
|
@keyframes square-spin { |
||||||
|
25% { transform: perspective(100px) rotateX(180deg) rotateY(0); } |
||||||
|
50% { transform: perspective(100px) rotateX(180deg) rotateY(180deg); } |
||||||
|
75% { transform: perspective(100px) rotateX(0) rotateY(180deg); } |
||||||
|
100% { transform: perspective(100px) rotateX(0) rotateY(0); } |
||||||
|
} |
||||||
|
.${className} > div { |
||||||
|
animation-fill-mode: both; |
||||||
|
width: 50px; |
||||||
|
height: 50px; |
||||||
|
background: #fff; |
||||||
|
animation: square-spin 3s 0s cubic-bezier(0.09, 0.57, 0.49, 0.9) infinite; |
||||||
|
} |
||||||
|
.app-loading-wrap { |
||||||
|
position: fixed; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
width: 100vw; |
||||||
|
height: 100vh; |
||||||
|
display: flex; |
||||||
|
align-items: center; |
||||||
|
justify-content: center; |
||||||
|
background: #282c34; |
||||||
|
z-index: 9; |
||||||
|
} |
||||||
|
`;
|
||||||
|
const oStyle = document.createElement('style'); |
||||||
|
const oDiv = document.createElement('div'); |
||||||
|
|
||||||
|
oStyle.id = 'app-loading-style'; |
||||||
|
oStyle.innerHTML = styleContent; |
||||||
|
oDiv.className = 'app-loading-wrap'; |
||||||
|
oDiv.innerHTML = `<div class="${className}"><div></div></div>`; |
||||||
|
|
||||||
|
return { |
||||||
|
appendLoading() { |
||||||
|
safeDOM.append(document.head, oStyle); |
||||||
|
safeDOM.append(document.body, oDiv); |
||||||
|
}, |
||||||
|
removeLoading() { |
||||||
|
safeDOM.remove(document.head, oStyle); |
||||||
|
safeDOM.remove(document.body, oDiv); |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
function useSkeleton() { |
||||||
|
const styleContent = ` |
||||||
|
.app-skeleton-wrap { |
||||||
|
position: fixed; |
||||||
|
top: 0; |
||||||
|
left: 0; |
||||||
|
width: 100vw; |
||||||
|
height: 100vh; |
||||||
|
background: #fff; |
||||||
|
z-index: 9; |
||||||
|
} |
||||||
|
.app-skeleton-nav { |
||||||
|
padding: 0 0 20px; |
||||||
|
border-bottom: 1px solid #ccc; |
||||||
|
} |
||||||
|
.app-skeleton-content { |
||||||
|
height: calc(100% - 60px); |
||||||
|
} |
||||||
|
.app-skeleton-footer { |
||||||
|
background-color: rgba(204, 201, 201, 0.2); |
||||||
|
height: 40px; |
||||||
|
width: 100%; |
||||||
|
} |
||||||
|
.wavesurfer { |
||||||
|
position: absolute; |
||||||
|
bottom: 40px; |
||||||
|
left: 0; |
||||||
|
width: 100%; |
||||||
|
height: 140px; |
||||||
|
--c: rgb(118, 218, 255); |
||||||
|
--w1: radial-gradient(100% 57% at top, #0000 100%, var(--c) 100.5%) no-repeat; |
||||||
|
--w2: radial-gradient(100% 57% at bottom, var(--c) 100%, #0000 100.5%) no-repeat; |
||||||
|
background: var(--w1), var(--w2), var(--w1), var(--w2); |
||||||
|
background-position-x: |
||||||
|
-200%, |
||||||
|
-100%, |
||||||
|
0%, |
||||||
|
100%; |
||||||
|
background-position-y: 100%; |
||||||
|
background-size: 50.5% 100%; |
||||||
|
} |
||||||
|
|
||||||
|
`;
|
||||||
|
const oStyle = document.createElement('style'); |
||||||
|
const oDiv = document.createElement('div'); |
||||||
|
|
||||||
|
oStyle.id = 'app-skeleton-style'; |
||||||
|
oStyle.innerHTML = styleContent; |
||||||
|
oDiv.className = 'app-skeleton-wrap'; |
||||||
|
oDiv.innerHTML = `<div class="app-skeleton-nav"></div><div class="app-skeleton-content"></div><div class="wavesurfer"></div><div class="app-skeleton-footer"></div>`; |
||||||
|
|
||||||
|
return { |
||||||
|
appendSkeleton() { |
||||||
|
safeDOM.append(document.head, oStyle); |
||||||
|
safeDOM.append(document.body, oDiv); |
||||||
|
}, |
||||||
|
removeSkeleton() { |
||||||
|
safeDOM.remove(document.head, oStyle); |
||||||
|
safeDOM.remove(document.body, oDiv); |
||||||
|
}, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
const { appendLoading, removeLoading } = useLoading(); |
||||||
|
const { appendSkeleton, removeSkeleton } = useSkeleton(); |
||||||
|
|
||||||
|
if (location.pathname.includes('/index.html')) { |
||||||
|
domReady().then(appendSkeleton); |
||||||
|
setTimeout(removeSkeleton, 4999); |
||||||
|
} else { |
||||||
|
if ( |
||||||
|
!location.pathname.includes('/shotScreen.html') && |
||||||
|
!location.pathname.includes('/clipScreen.html') && |
||||||
|
!location.pathname.includes('/canvas.html') && |
||||||
|
!location.pathname.includes('/recorderScreen.html') |
||||||
|
) { |
||||||
|
domReady().then(appendLoading); |
||||||
|
|
||||||
|
setTimeout(removeLoading, 4999); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
window.onmessage = (ev) => { |
||||||
|
if (ev.data.payload === 'removeLoading') { |
||||||
|
location.pathname.includes('/index.html') ? removeSkeleton() : removeLoading(); |
||||||
|
} |
||||||
|
}; |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
import { BrowserWindow } from 'electron'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
|
||||||
|
let canvasWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createCanvasWin(): BrowserWindow { |
||||||
|
canvasWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 画布', |
||||||
|
icon: ICON, |
||||||
|
height: WIN_CONFIG.canvas.height, |
||||||
|
width: WIN_CONFIG.canvas.width, |
||||||
|
autoHideMenuBar: WIN_CONFIG.canvas.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
// canvasWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
canvasWin.loadURL(WEB_URL + `canvas.html`); |
||||||
|
} else { |
||||||
|
canvasWin.loadFile(WIN_CONFIG.canvas.html); |
||||||
|
} |
||||||
|
|
||||||
|
canvasWin.once('ready-to-show', async () => { |
||||||
|
canvasWin?.show(); |
||||||
|
}); |
||||||
|
|
||||||
|
return canvasWin; |
||||||
|
} |
||||||
|
|
||||||
|
function openCanvasWin() { |
||||||
|
if (!canvasWin || canvasWin?.isDestroyed()) { |
||||||
|
canvasWin = createCanvasWin(); |
||||||
|
} |
||||||
|
canvasWin.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function closeCanvasWin() { |
||||||
|
canvasWin?.close(); |
||||||
|
} |
||||||
|
|
||||||
|
export { closeCanvasWin, createCanvasWin, openCanvasWin }; |
||||||
@ -0,0 +1,124 @@ |
|||||||
|
import { BrowserWindow } from 'electron'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
import { |
||||||
|
minimizeRecorderScreenWin, |
||||||
|
openRecorderScreenWin, |
||||||
|
setBoundsRecorderScreenWin, |
||||||
|
showRecorderScreenWin, |
||||||
|
} from './recorderScreenWin'; |
||||||
|
|
||||||
|
let clipScreenWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createClipScreenWin(): BrowserWindow { |
||||||
|
clipScreenWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec', |
||||||
|
icon: ICON, |
||||||
|
autoHideMenuBar: WIN_CONFIG.clipScreen.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
frame: WIN_CONFIG.clipScreen.frame, // 无边框窗口
|
||||||
|
resizable: WIN_CONFIG.clipScreen.resizable, // 窗口大小是否可调整
|
||||||
|
transparent: WIN_CONFIG.clipScreen.transparent, // 使窗口透明
|
||||||
|
fullscreenable: WIN_CONFIG.clipScreen.fullscreenable, // 窗口是否可以进入全屏状态
|
||||||
|
alwaysOnTop: WIN_CONFIG.clipScreen.alwaysOnTop, // 窗口是否永远在别的窗口的上面
|
||||||
|
skipTaskbar: WIN_CONFIG.clipScreen.skipTaskbar, |
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
// clipScreenWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
clipScreenWin.loadURL(WEB_URL + 'clipScreen.html'); |
||||||
|
} else { |
||||||
|
clipScreenWin.loadFile(WIN_CONFIG.clipScreen.html); |
||||||
|
} |
||||||
|
|
||||||
|
// clipScreenWin.on('resize', () => {
|
||||||
|
// const clipScreenWinBounds = getBoundsClipScreenWin();
|
||||||
|
// setBoundsRecorderScreenWin(clipScreenWinBounds);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// clipScreenWin.on('move', () => {
|
||||||
|
// const clipScreenWinBounds = getBoundsClipScreenWin();
|
||||||
|
// setBoundsRecorderScreenWin(clipScreenWinBounds);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// clipScreenWin.on('restore', () => {
|
||||||
|
// showRecorderScreenWin();
|
||||||
|
// });
|
||||||
|
|
||||||
|
// clipScreenWin.on('minimize', () => {
|
||||||
|
// hideRecorderScreenWin();
|
||||||
|
// });
|
||||||
|
|
||||||
|
return clipScreenWin; |
||||||
|
} |
||||||
|
|
||||||
|
function closeClipScreenWin() { |
||||||
|
clipScreenWin?.isDestroyed() || clipScreenWin?.close(); |
||||||
|
clipScreenWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
function showClipScreenWin() { |
||||||
|
clipScreenWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function openClipScreenWin(search?: any) { |
||||||
|
if (!clipScreenWin || clipScreenWin?.isDestroyed()) { |
||||||
|
clipScreenWin = createClipScreenWin(); |
||||||
|
} |
||||||
|
|
||||||
|
clipScreenWin?.show(); |
||||||
|
openRecorderScreenWin(search); |
||||||
|
} |
||||||
|
|
||||||
|
function getBoundsClipScreenWin() { |
||||||
|
return clipScreenWin?.getBounds(); |
||||||
|
} |
||||||
|
|
||||||
|
function hideClipScreenWin() { |
||||||
|
clipScreenWin?.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
function setAlwaysOnTopClipScreenWin(isAlwaysOnTop: boolean) { |
||||||
|
clipScreenWin?.setAlwaysOnTop(isAlwaysOnTop); |
||||||
|
} |
||||||
|
|
||||||
|
function setMovableClipScreenWin(movable: boolean) { |
||||||
|
clipScreenWin?.setMovable(movable); |
||||||
|
} |
||||||
|
|
||||||
|
function setResizableClipScreenWin(resizable: boolean) { |
||||||
|
clipScreenWin?.setResizable(resizable); |
||||||
|
} |
||||||
|
|
||||||
|
function minimizeClipScreenWin() { |
||||||
|
clipScreenWin?.minimize(); |
||||||
|
minimizeRecorderScreenWin(); |
||||||
|
} |
||||||
|
|
||||||
|
function setIgnoreMouseEventsClipScreenWin(event: any, ignore: boolean, options?: any) { |
||||||
|
clipScreenWin?.setIgnoreMouseEvents(ignore, options); |
||||||
|
} |
||||||
|
|
||||||
|
function setIsPlayClipScreenWin(isPlay: boolean) { |
||||||
|
clipScreenWin?.webContents.send('cs:set-isPlay', isPlay); |
||||||
|
} |
||||||
|
|
||||||
|
function setBoundsClipScreenWin(bounds: any) { |
||||||
|
clipScreenWin?.setBounds({ ...bounds }); |
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
closeClipScreenWin, |
||||||
|
getBoundsClipScreenWin, |
||||||
|
hideClipScreenWin, |
||||||
|
minimizeClipScreenWin, |
||||||
|
openClipScreenWin, |
||||||
|
setAlwaysOnTopClipScreenWin, |
||||||
|
setBoundsClipScreenWin, |
||||||
|
setIgnoreMouseEventsClipScreenWin, |
||||||
|
setIsPlayClipScreenWin, |
||||||
|
setMovableClipScreenWin, |
||||||
|
setResizableClipScreenWin, |
||||||
|
showClipScreenWin, |
||||||
|
}; |
||||||
@ -0,0 +1,53 @@ |
|||||||
|
import { BrowserWindow } from 'electron'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
|
||||||
|
let editGifWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createEditGifWin(search?: any): BrowserWindow { |
||||||
|
editGifWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 动图编辑', |
||||||
|
icon: ICON, |
||||||
|
height: WIN_CONFIG.editGif.height, |
||||||
|
width: WIN_CONFIG.editGif.width, |
||||||
|
autoHideMenuBar: WIN_CONFIG.editGif.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
const videoUrl = search?.videoUrl || ''; |
||||||
|
const filePath = search?.filePath || ''; |
||||||
|
const imgUrl = search?.imgUrl || ''; |
||||||
|
const recordId = search?.recordId || ''; |
||||||
|
|
||||||
|
// editGifWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
editGifWin.loadURL( |
||||||
|
WEB_URL + |
||||||
|
`editGif.html?${filePath ? 'filePath=' + filePath : ''}${imgUrl ? 'imgUrl=' + imgUrl : ''}${ |
||||||
|
recordId ? 'recordId=' + recordId : '' |
||||||
|
}${videoUrl ? 'videoUrl=' + videoUrl : ''}`,
|
||||||
|
); |
||||||
|
} else { |
||||||
|
editGifWin.loadFile(WIN_CONFIG.editGif.html, { |
||||||
|
search: `?${filePath ? 'filePath=' + filePath : ''}${imgUrl ? 'imgUrl=' + imgUrl : ''}${ |
||||||
|
recordId ? 'recordId=' + recordId : '' |
||||||
|
}${videoUrl ? 'videoUrl=' + videoUrl : ''}`,
|
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
return editGifWin; |
||||||
|
} |
||||||
|
|
||||||
|
function openEditGifWin(search?: any) { |
||||||
|
if (!editGifWin || editGifWin?.isDestroyed()) { |
||||||
|
editGifWin = createEditGifWin(search); |
||||||
|
} |
||||||
|
editGifWin.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function closeEditGifWin() { |
||||||
|
editGifWin?.close(); |
||||||
|
} |
||||||
|
|
||||||
|
export { closeEditGifWin, createEditGifWin, openEditGifWin }; |
||||||
@ -0,0 +1,66 @@ |
|||||||
|
import { BrowserWindow, dialog, nativeImage } from 'electron'; |
||||||
|
import { writeFile } from 'node:fs'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
|
||||||
|
let editImageWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createEditImageWin(search?: any): BrowserWindow { |
||||||
|
editImageWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 图片编辑', |
||||||
|
icon: ICON, |
||||||
|
height: WIN_CONFIG.editImage.height, |
||||||
|
width: WIN_CONFIG.editImage.width, |
||||||
|
autoHideMenuBar: WIN_CONFIG.editImage.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
const imgUrl = search?.imgUrl || ''; |
||||||
|
|
||||||
|
// editImageWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
editImageWin.loadURL(WEB_URL + `editImage.html?imgUrl=${imgUrl}`); |
||||||
|
} else { |
||||||
|
editImageWin.loadFile(WIN_CONFIG.editImage.html, { |
||||||
|
search: `?imgUrl=${imgUrl}`, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
editImageWin.once('ready-to-show', async () => { |
||||||
|
editImageWin?.show(); |
||||||
|
}); |
||||||
|
|
||||||
|
return editImageWin; |
||||||
|
} |
||||||
|
|
||||||
|
function openEditImageWin(search?: any) { |
||||||
|
if (!editImageWin || editImageWin?.isDestroyed()) { |
||||||
|
editImageWin = createEditImageWin(search); |
||||||
|
} |
||||||
|
editImageWin.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function closeEditImageWin() { |
||||||
|
editImageWin?.close(); |
||||||
|
} |
||||||
|
|
||||||
|
async function downloadImg(imgUrl: any) { |
||||||
|
let defaultPath = `pear-rec_${+new Date()}.png`; |
||||||
|
let res = await dialog.showSaveDialog({ |
||||||
|
defaultPath: defaultPath, |
||||||
|
filters: [{ name: 'Images', extensions: ['png', 'jpg', 'gif'] }], |
||||||
|
}); |
||||||
|
if (!res.canceled) { |
||||||
|
const imgData = nativeImage.createFromDataURL(imgUrl).toPNG(); |
||||||
|
writeFile(res.filePath, imgData, (err) => { |
||||||
|
if (err) { |
||||||
|
console.error(err); |
||||||
|
} else { |
||||||
|
console.log(`${defaultPath}:图片保存成功`); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export { closeEditImageWin, createEditImageWin, downloadImg, openEditImageWin }; |
||||||
@ -0,0 +1,100 @@ |
|||||||
|
import { BrowserWindow, app, shell } from 'electron'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
|
||||||
|
let mainWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
const createMainWin = (): BrowserWindow => { |
||||||
|
mainWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec', |
||||||
|
icon: ICON, |
||||||
|
width: WIN_CONFIG.main.width, // 宽度(px)
|
||||||
|
height: WIN_CONFIG.main.height, // 高度(px)
|
||||||
|
autoHideMenuBar: WIN_CONFIG.main.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
maximizable: WIN_CONFIG.main.maximizable, |
||||||
|
resizable: WIN_CONFIG.main.resizable, // gnome下为false时无法全屏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
if (url) { |
||||||
|
mainWin.loadURL(WEB_URL + 'index.html'); |
||||||
|
} else { |
||||||
|
mainWin.loadFile(WIN_CONFIG.main.html); |
||||||
|
} |
||||||
|
// mainWin.webContents.openDevTools();
|
||||||
|
|
||||||
|
// Make all links open with the browser, not with the application
|
||||||
|
mainWin.webContents.setWindowOpenHandler(({ url }) => { |
||||||
|
if (url.startsWith('https:')) shell.openExternal(url); |
||||||
|
return { action: 'deny' }; |
||||||
|
}); |
||||||
|
|
||||||
|
// mainWin.onbeforeunload = (e) => {
|
||||||
|
// console.log('I do not want to be closed');
|
||||||
|
|
||||||
|
// // 与通常的浏览器不同,会提示给用户一个消息框,
|
||||||
|
// //返回非空值将默认取消关闭
|
||||||
|
// //建议使用对话框 API 让用户确认关闭应用程序.
|
||||||
|
// e.returnValue = false;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// window.addEventListener('beforeunload', (e) => {
|
||||||
|
// e.returnValue = false;
|
||||||
|
// });
|
||||||
|
|
||||||
|
return mainWin; |
||||||
|
}; |
||||||
|
|
||||||
|
function closeMainWin() { |
||||||
|
if (mainWin && !mainWin?.isDestroyed()) { |
||||||
|
mainWin?.close(); |
||||||
|
} |
||||||
|
mainWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
function openMainWin() { |
||||||
|
if (!mainWin || mainWin?.isDestroyed()) { |
||||||
|
mainWin = createMainWin(); |
||||||
|
} |
||||||
|
mainWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function hideMainWin() { |
||||||
|
mainWin!.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
function minimizeMainWin() { |
||||||
|
mainWin!.minimize(); |
||||||
|
} |
||||||
|
|
||||||
|
function focusMainWin() { |
||||||
|
if (!mainWin || mainWin?.isDestroyed()) { |
||||||
|
mainWin = createMainWin(); |
||||||
|
} else { |
||||||
|
// Focus on the main window if the user tried to open another
|
||||||
|
if (mainWin.isMinimized()) mainWin.restore(); |
||||||
|
if (!mainWin.isVisible()) mainWin.show(); |
||||||
|
mainWin.focus(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function sendEuUpdateCanAvailable(arg, update) { |
||||||
|
if (mainWin && !mainWin?.isDestroyed()) { |
||||||
|
mainWin.webContents.send('eu:update-can-available', { |
||||||
|
update: update, |
||||||
|
version: app.getVersion(), |
||||||
|
newVersion: arg?.version, |
||||||
|
}); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
closeMainWin, |
||||||
|
createMainWin, |
||||||
|
focusMainWin, |
||||||
|
hideMainWin, |
||||||
|
minimizeMainWin, |
||||||
|
openMainWin, |
||||||
|
sendEuUpdateCanAvailable, |
||||||
|
}; |
||||||
@ -0,0 +1,93 @@ |
|||||||
|
import { BrowserWindow } from 'electron'; |
||||||
|
import { ICON, preload, url, WEB_URL, WIN_CONFIG } from '../main/constant'; |
||||||
|
|
||||||
|
let pinImageWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createPinImageWin(search?: any): BrowserWindow { |
||||||
|
const imgUrl = search?.imgUrl || ''; |
||||||
|
const recordId = search?.recordId || ''; |
||||||
|
const width = search?.width || 800; |
||||||
|
const height = search?.height || 600; |
||||||
|
|
||||||
|
pinImageWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 图片', |
||||||
|
icon: ICON, |
||||||
|
width: width, |
||||||
|
height: height, |
||||||
|
frame: WIN_CONFIG.pinImage.frame, // 无边框窗口
|
||||||
|
transparent: WIN_CONFIG.pinImage.transparent, // 使窗口透明
|
||||||
|
fullscreenable: WIN_CONFIG.pinImage.fullscreenable, // 窗口是否可以进入全屏状态
|
||||||
|
alwaysOnTop: WIN_CONFIG.pinImage.alwaysOnTop, // 窗口是否永远在别的窗口的上面
|
||||||
|
autoHideMenuBar: WIN_CONFIG.pinImage.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
// pinImageWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
pinImageWin.loadURL( |
||||||
|
WEB_URL + |
||||||
|
`pinImage.html?${imgUrl ? 'imgUrl=' + imgUrl : ''}${ |
||||||
|
recordId ? 'recordId=' + recordId : '' |
||||||
|
}`,
|
||||||
|
); |
||||||
|
} else { |
||||||
|
pinImageWin.loadFile(WIN_CONFIG.pinImage.html, { |
||||||
|
search: `?${imgUrl ? 'imgUrl=' + imgUrl : ''}${recordId ? 'recordId=' + recordId : ''}`, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
return pinImageWin; |
||||||
|
} |
||||||
|
|
||||||
|
function setSizePinImageWin(size: any) { |
||||||
|
pinImageWin.setBounds(size); |
||||||
|
} |
||||||
|
|
||||||
|
function closePinImageWin() { |
||||||
|
pinImageWin?.isDestroyed() || pinImageWin?.close(); |
||||||
|
pinImageWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
function openPinImageWin(search?: any) { |
||||||
|
pinImageWin = createPinImageWin(search); |
||||||
|
pinImageWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function showPinImageWin() { |
||||||
|
pinImageWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function hidePinImageWin() { |
||||||
|
pinImageWin?.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
function minimizePinImageWin() { |
||||||
|
pinImageWin?.minimize(); |
||||||
|
} |
||||||
|
|
||||||
|
function maximizePinImageWin() { |
||||||
|
pinImageWin?.maximize(); |
||||||
|
} |
||||||
|
|
||||||
|
function unmaximizePinImageWin() { |
||||||
|
pinImageWin?.unmaximize(); |
||||||
|
} |
||||||
|
|
||||||
|
function getSizePinImageWin() { |
||||||
|
return pinImageWin.getBounds(); |
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
setSizePinImageWin, |
||||||
|
closePinImageWin, |
||||||
|
createPinImageWin, |
||||||
|
hidePinImageWin, |
||||||
|
maximizePinImageWin, |
||||||
|
minimizePinImageWin, |
||||||
|
openPinImageWin, |
||||||
|
showPinImageWin, |
||||||
|
unmaximizePinImageWin, |
||||||
|
getSizePinImageWin, |
||||||
|
}; |
||||||
@ -0,0 +1,73 @@ |
|||||||
|
import { BrowserWindow } from 'electron'; |
||||||
|
import { ICON, preload, url, WEB_URL, WIN_CONFIG } from '../main/constant'; |
||||||
|
|
||||||
|
let pinVideoWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createPinVideoWin(search?: any): BrowserWindow { |
||||||
|
pinVideoWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 视频', |
||||||
|
icon: ICON, |
||||||
|
height: WIN_CONFIG.pinVideo.height, |
||||||
|
width: WIN_CONFIG.pinVideo.width, |
||||||
|
frame: WIN_CONFIG.pinVideo.frame, // 无边框窗口
|
||||||
|
resizable: WIN_CONFIG.pinVideo.resizable, // 窗口大小是否可调整
|
||||||
|
transparent: WIN_CONFIG.pinVideo.transparent, // 使窗口透明
|
||||||
|
fullscreenable: WIN_CONFIG.pinVideo.fullscreenable, // 窗口是否可以进入全屏状态
|
||||||
|
alwaysOnTop: WIN_CONFIG.pinVideo.alwaysOnTop, // 窗口是否永远在别的窗口的上面
|
||||||
|
autoHideMenuBar: WIN_CONFIG.pinVideo.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
// pinVideoWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
pinVideoWin.loadURL(WEB_URL + `pinVideo.html`); |
||||||
|
} else { |
||||||
|
pinVideoWin.loadFile(WIN_CONFIG.pinVideo.html); |
||||||
|
} |
||||||
|
|
||||||
|
return pinVideoWin; |
||||||
|
} |
||||||
|
|
||||||
|
// 打开关闭录屏窗口
|
||||||
|
function closePinVideoWin() { |
||||||
|
pinVideoWin?.isDestroyed() || pinVideoWin?.close(); |
||||||
|
pinVideoWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
function openPinVideoWin(search?: any) { |
||||||
|
pinVideoWin = createPinVideoWin(search); |
||||||
|
pinVideoWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function showPinVideoWin() { |
||||||
|
pinVideoWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function hidePinVideoWin() { |
||||||
|
pinVideoWin?.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
function minimizePinVideoWin() { |
||||||
|
pinVideoWin?.minimize(); |
||||||
|
} |
||||||
|
|
||||||
|
function maximizePinVideoWin() { |
||||||
|
pinVideoWin?.maximize(); |
||||||
|
} |
||||||
|
|
||||||
|
function unmaximizePinVideoWin() { |
||||||
|
pinVideoWin?.unmaximize(); |
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
closePinVideoWin, |
||||||
|
createPinVideoWin, |
||||||
|
hidePinVideoWin, |
||||||
|
maximizePinVideoWin, |
||||||
|
minimizePinVideoWin, |
||||||
|
openPinVideoWin, |
||||||
|
showPinVideoWin, |
||||||
|
unmaximizePinVideoWin, |
||||||
|
}; |
||||||
@ -0,0 +1,68 @@ |
|||||||
|
import { BrowserWindow } from 'electron'; |
||||||
|
import { ICON, preload, url, WEB_URL, WIN_CONFIG } from '../main/constant'; |
||||||
|
|
||||||
|
let recorderAudioWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createRecorderAudioWin(): BrowserWindow { |
||||||
|
recorderAudioWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 录音', |
||||||
|
icon: ICON, |
||||||
|
width: WIN_CONFIG.recorderAudio.width, // 宽度(px), 默认值为 800
|
||||||
|
height: WIN_CONFIG.recorderAudio.height, // 高度(px), 默认值为 600
|
||||||
|
autoHideMenuBar: WIN_CONFIG.recorderAudio.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
// recorderAudioWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
recorderAudioWin.loadURL(WEB_URL + 'recorderAudio.html'); |
||||||
|
} else { |
||||||
|
recorderAudioWin.loadFile(WIN_CONFIG.recorderAudio.html); |
||||||
|
} |
||||||
|
|
||||||
|
return recorderAudioWin; |
||||||
|
} |
||||||
|
|
||||||
|
// 打开关闭录屏窗口
|
||||||
|
function closeRecorderAudioWin() { |
||||||
|
recorderAudioWin?.isDestroyed() || recorderAudioWin?.close(); |
||||||
|
recorderAudioWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
function openRecorderAudioWin() { |
||||||
|
if (!recorderAudioWin || recorderAudioWin?.isDestroyed()) { |
||||||
|
recorderAudioWin = createRecorderAudioWin(); |
||||||
|
} |
||||||
|
recorderAudioWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function hideRecorderAudioWin() { |
||||||
|
recorderAudioWin?.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
function minimizeRecorderAudioWin() { |
||||||
|
recorderAudioWin?.minimize(); |
||||||
|
} |
||||||
|
|
||||||
|
function downloadURLRecorderAudioWin(downloadUrl: string) { |
||||||
|
// recorderAudioWin?.webContents.downloadURL(downloadUrl);
|
||||||
|
// downloadSet.add(downloadUrl);
|
||||||
|
} |
||||||
|
|
||||||
|
function setSizeRecorderAudioWin(width: number, height: number) { |
||||||
|
recorderAudioWin?.setResizable(true); |
||||||
|
recorderAudioWin?.setSize(width, height); |
||||||
|
recorderAudioWin?.setResizable(false); |
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
closeRecorderAudioWin, |
||||||
|
createRecorderAudioWin, |
||||||
|
downloadURLRecorderAudioWin, |
||||||
|
hideRecorderAudioWin, |
||||||
|
minimizeRecorderAudioWin, |
||||||
|
openRecorderAudioWin, |
||||||
|
setSizeRecorderAudioWin, |
||||||
|
}; |
||||||
@ -0,0 +1,77 @@ |
|||||||
|
import { BrowserWindow } from 'electron'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
|
||||||
|
let recorderFullScreenWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createRecorderFullScreenWin(): BrowserWindow { |
||||||
|
recorderFullScreenWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 录屏', |
||||||
|
icon: ICON, |
||||||
|
height: WIN_CONFIG.recorderFullScreen.height, |
||||||
|
width: WIN_CONFIG.recorderFullScreen.width, |
||||||
|
center: WIN_CONFIG.recorderFullScreen.center, |
||||||
|
transparent: WIN_CONFIG.recorderFullScreen.transparent, // 使窗口透明
|
||||||
|
autoHideMenuBar: WIN_CONFIG.recorderFullScreen.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
frame: WIN_CONFIG.recorderFullScreen.frame, // 无边框窗口
|
||||||
|
hasShadow: WIN_CONFIG.recorderFullScreen.hasShadow, // 窗口是否有阴影
|
||||||
|
fullscreenable: WIN_CONFIG.recorderFullScreen.fullscreenable, // 窗口是否可以进入全屏状态
|
||||||
|
alwaysOnTop: WIN_CONFIG.recorderFullScreen.alwaysOnTop, // 窗口是否永远在别的窗口的上面
|
||||||
|
skipTaskbar: WIN_CONFIG.recorderFullScreen.skipTaskbar, |
||||||
|
resizable: WIN_CONFIG.recorderFullScreen.resizable, |
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
recorderFullScreenWin?.setBounds({ y: 0 }); |
||||||
|
if (url) { |
||||||
|
recorderFullScreenWin.loadURL(WEB_URL + `recorderFullScreen.html`); |
||||||
|
} else { |
||||||
|
recorderFullScreenWin.loadFile(WIN_CONFIG.recorderFullScreen.html); |
||||||
|
} |
||||||
|
// recorderFullScreenWin.webContents.openDevTools();
|
||||||
|
|
||||||
|
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 { |
||||||
|
closeRecorderFullScreenWin, |
||||||
|
createRecorderFullScreenWin, |
||||||
|
hideRecorderFullScreenWin, |
||||||
|
minimizeRecorderFullScreenWin, |
||||||
|
openRecorderFullScreenWin, |
||||||
|
setAlwaysOnTopRecorderFullScreenWin, |
||||||
|
setMovableRecorderFullScreenWin, |
||||||
|
showRecorderFullScreenWin, |
||||||
|
}; |
||||||
@ -0,0 +1,164 @@ |
|||||||
|
import { BrowserWindow, Rectangle, screen } from 'electron'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
import { |
||||||
|
closeClipScreenWin, |
||||||
|
getBoundsClipScreenWin, |
||||||
|
showClipScreenWin, |
||||||
|
minimizeClipScreenWin, |
||||||
|
} from './clipScreenWin'; |
||||||
|
|
||||||
|
let recorderScreenWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createRecorderScreenWin(search?: any): BrowserWindow { |
||||||
|
const { x, y, width, height } = getBoundsClipScreenWin() as Rectangle; |
||||||
|
// let recorderScreenWinX = x + (width - WIN_CONFIG.recorderScreen.width) / 2;
|
||||||
|
let recorderScreenWinX = x + width + 4 - WIN_CONFIG.recorderScreen.width; |
||||||
|
let recorderScreenWinY = y + height; |
||||||
|
|
||||||
|
recorderScreenWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 录屏', |
||||||
|
icon: ICON, |
||||||
|
x: recorderScreenWinX, |
||||||
|
y: recorderScreenWinY, |
||||||
|
width: WIN_CONFIG.recorderScreen.width, |
||||||
|
height: WIN_CONFIG.recorderScreen.height, |
||||||
|
autoHideMenuBar: WIN_CONFIG.recorderScreen.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
maximizable: WIN_CONFIG.recorderScreen.maximizable, |
||||||
|
hasShadow: WIN_CONFIG.recorderScreen.hasShadow, // 窗口是否有阴影
|
||||||
|
fullscreenable: WIN_CONFIG.recorderScreen.fullscreenable, // 窗口是否可以进入全屏状态
|
||||||
|
alwaysOnTop: WIN_CONFIG.recorderScreen.alwaysOnTop, // 窗口是否永远在别的窗口的上面
|
||||||
|
// skipTaskbar: WIN_CONFIG.recorderScreen.skipTaskbar,
|
||||||
|
// resizable: WIN_CONFIG.recorderScreen.resizable,
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
// recorderScreenWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
recorderScreenWin.loadURL(WEB_URL + `recorderScreen.html?type=${search?.type || ''}`); |
||||||
|
} else { |
||||||
|
recorderScreenWin.loadFile(WIN_CONFIG.recorderScreen.html, { |
||||||
|
search: `?type=${search?.type || ''}`, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
// recorderScreenWin.on('move', () => {
|
||||||
|
// const recorderScreenWinBounds = getBoundsRecorderScreenWin() as Rectangle;
|
||||||
|
// const clipScreenWinBounds = getBoundsClipScreenWin() as Rectangle;
|
||||||
|
// setBoundsClipScreenWin({
|
||||||
|
// x: recorderScreenWinBounds.x,
|
||||||
|
// y: recorderScreenWinBounds.y - clipScreenWinBounds.height,
|
||||||
|
// width: clipScreenWinBounds.width,
|
||||||
|
// height: clipScreenWinBounds.height,
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
|
recorderScreenWin.on('restore', () => { |
||||||
|
showClipScreenWin(); |
||||||
|
}); |
||||||
|
|
||||||
|
recorderScreenWin.on('minimize', () => { |
||||||
|
minimizeClipScreenWin(); |
||||||
|
}); |
||||||
|
|
||||||
|
recorderScreenWin.on('close', () => { |
||||||
|
closeClipScreenWin(); |
||||||
|
}); |
||||||
|
|
||||||
|
return recorderScreenWin; |
||||||
|
} |
||||||
|
|
||||||
|
// 打开关闭录屏窗口
|
||||||
|
function closeRecorderScreenWin() { |
||||||
|
recorderScreenWin?.isDestroyed() || recorderScreenWin?.close(); |
||||||
|
recorderScreenWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
function openRecorderScreenWin(search?: any) { |
||||||
|
if (!recorderScreenWin || recorderScreenWin?.isDestroyed()) { |
||||||
|
recorderScreenWin = createRecorderScreenWin(search); |
||||||
|
} |
||||||
|
recorderScreenWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function hideRecorderScreenWin() { |
||||||
|
recorderScreenWin?.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
function showRecorderScreenWin() { |
||||||
|
recorderScreenWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function minimizeRecorderScreenWin() { |
||||||
|
recorderScreenWin?.minimize(); |
||||||
|
} |
||||||
|
|
||||||
|
function setSizeRecorderScreenWin(width: number, height: number) { |
||||||
|
// recorderScreenWin?.setResizable(true);
|
||||||
|
// recorderScreenWin?.setSize(width, height);
|
||||||
|
// recorderScreenWin?.setResizable(false);
|
||||||
|
} |
||||||
|
|
||||||
|
function getBoundsRecorderScreenWin() { |
||||||
|
return recorderScreenWin?.getBounds(); |
||||||
|
} |
||||||
|
|
||||||
|
function setMovableRecorderScreenWin(movable: boolean) { |
||||||
|
recorderScreenWin?.setMovable(movable); |
||||||
|
} |
||||||
|
|
||||||
|
function setResizableRecorderScreenWin(resizable: boolean) { |
||||||
|
recorderScreenWin?.setResizable(resizable); |
||||||
|
} |
||||||
|
|
||||||
|
function setAlwaysOnTopRecorderScreenWin(isAlwaysOnTop: boolean) { |
||||||
|
recorderScreenWin?.setAlwaysOnTop(isAlwaysOnTop); |
||||||
|
} |
||||||
|
|
||||||
|
function isFocusedRecorderScreenWin() { |
||||||
|
return recorderScreenWin?.isFocused(); |
||||||
|
} |
||||||
|
|
||||||
|
function focusRecorderScreenWin() { |
||||||
|
recorderScreenWin?.focus(); |
||||||
|
} |
||||||
|
|
||||||
|
function getCursorScreenPointRecorderScreenWin() { |
||||||
|
return screen.getCursorScreenPoint(); |
||||||
|
} |
||||||
|
|
||||||
|
function setBoundsRecorderScreenWin(clipScreenWinBounds: any) { |
||||||
|
let { x, y, width, height } = clipScreenWinBounds; |
||||||
|
let recorderScreenWinX = x; |
||||||
|
let recorderScreenWinY = y + height; |
||||||
|
recorderScreenWin?.setBounds({ |
||||||
|
x: recorderScreenWinX, |
||||||
|
y: recorderScreenWinY, |
||||||
|
width: width, |
||||||
|
}); |
||||||
|
recorderScreenWin?.webContents.send('rs:get-size-clip-win', clipScreenWinBounds); |
||||||
|
} |
||||||
|
|
||||||
|
function setIgnoreMouseEventsRecorderScreenWin(event: any, ignore: boolean, options: any) { |
||||||
|
const win = BrowserWindow.fromWebContents(event.sender); |
||||||
|
win?.setIgnoreMouseEvents(ignore, options); |
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
closeRecorderScreenWin, |
||||||
|
createRecorderScreenWin, |
||||||
|
focusRecorderScreenWin, |
||||||
|
getBoundsRecorderScreenWin, |
||||||
|
getCursorScreenPointRecorderScreenWin, |
||||||
|
hideRecorderScreenWin, |
||||||
|
isFocusedRecorderScreenWin, |
||||||
|
minimizeRecorderScreenWin, |
||||||
|
openRecorderScreenWin, |
||||||
|
setAlwaysOnTopRecorderScreenWin, |
||||||
|
setBoundsRecorderScreenWin, |
||||||
|
setIgnoreMouseEventsRecorderScreenWin, |
||||||
|
setMovableRecorderScreenWin, |
||||||
|
setResizableRecorderScreenWin, |
||||||
|
setSizeRecorderScreenWin, |
||||||
|
showRecorderScreenWin, |
||||||
|
}; |
||||||
@ -0,0 +1,76 @@ |
|||||||
|
import { BrowserWindow } from 'electron'; |
||||||
|
import { ICON, preload, url, WEB_URL, WIN_CONFIG } from '../main/constant'; |
||||||
|
|
||||||
|
let recorderVideoWin: BrowserWindow | null = null; |
||||||
|
let downloadSet: Set<string> = new Set(); |
||||||
|
|
||||||
|
function createRecorderVideoWin(): BrowserWindow { |
||||||
|
recorderVideoWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 录像', |
||||||
|
icon: ICON, |
||||||
|
height: WIN_CONFIG.recorderVideo.height, |
||||||
|
width: WIN_CONFIG.recorderVideo.width, |
||||||
|
autoHideMenuBar: WIN_CONFIG.recorderVideo.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
if (url) { |
||||||
|
recorderVideoWin.loadURL(WEB_URL + 'recorderVideo.html'); |
||||||
|
// recorderVideoWin.webContents.openDevTools();
|
||||||
|
} else { |
||||||
|
recorderVideoWin.loadFile(WIN_CONFIG.recorderVideo.html); |
||||||
|
} |
||||||
|
|
||||||
|
recorderVideoWin?.webContents.session.on( |
||||||
|
'will-download', |
||||||
|
async (event: any, item: any, webContents: any) => { |
||||||
|
const url = item.getURL(); |
||||||
|
if (downloadSet.has(url)) { |
||||||
|
// const fileName = item.getFilename();
|
||||||
|
// const filePath = (await getFilePath()) as string;
|
||||||
|
// const rvFilePath = join(`${filePath}/rv`, `${fileName}`);
|
||||||
|
// item.setSavePath(rvFilePath);
|
||||||
|
// item.once('done', (event: any, state: any) => {
|
||||||
|
// if (state === 'completed') {
|
||||||
|
// setHistoryVideo(rvFilePath);
|
||||||
|
// setTimeout(() => {
|
||||||
|
// closeRecorderVideoWin();
|
||||||
|
// // shell.showItemInFolder(filePath);
|
||||||
|
// }, 1000);
|
||||||
|
// } else {
|
||||||
|
// dialog.showErrorBox('下载失败', `文件 ${item.getFilename()} 因为某些原因被中断下载`);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
} |
||||||
|
}, |
||||||
|
); |
||||||
|
|
||||||
|
return recorderVideoWin; |
||||||
|
} |
||||||
|
|
||||||
|
// 打开关闭录屏窗口
|
||||||
|
function closeRecorderVideoWin() { |
||||||
|
recorderVideoWin?.isDestroyed() || recorderVideoWin?.close(); |
||||||
|
recorderVideoWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
function openRecorderVideoWin() { |
||||||
|
if (!recorderVideoWin || recorderVideoWin?.isDestroyed()) { |
||||||
|
recorderVideoWin = createRecorderVideoWin(); |
||||||
|
} |
||||||
|
recorderVideoWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function downloadURLRecorderVideoWin(downloadUrl: string) { |
||||||
|
recorderVideoWin?.webContents.downloadURL(downloadUrl); |
||||||
|
downloadSet.add(downloadUrl); |
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
closeRecorderVideoWin, |
||||||
|
createRecorderVideoWin, |
||||||
|
downloadURLRecorderVideoWin, |
||||||
|
openRecorderVideoWin, |
||||||
|
}; |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
import { BrowserWindow } from 'electron'; |
||||||
|
import { ICON, preload, url, WEB_URL, WIN_CONFIG } from '../main/constant'; |
||||||
|
|
||||||
|
let recordsWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createRecordsWin(): BrowserWindow { |
||||||
|
recordsWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 记录', |
||||||
|
icon: ICON, |
||||||
|
width: WIN_CONFIG.records.width, |
||||||
|
autoHideMenuBar: WIN_CONFIG.records.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
// recordsWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
recordsWin.loadURL(WEB_URL + 'records.html'); |
||||||
|
} else { |
||||||
|
recordsWin.loadFile(WIN_CONFIG.records.html); |
||||||
|
} |
||||||
|
|
||||||
|
return recordsWin; |
||||||
|
} |
||||||
|
|
||||||
|
// 打开关闭录屏窗口
|
||||||
|
function closeRecordsWin() { |
||||||
|
recordsWin?.isDestroyed() || recordsWin?.close(); |
||||||
|
recordsWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
function openRecordsWin() { |
||||||
|
if (!recordsWin || recordsWin?.isDestroyed()) { |
||||||
|
recordsWin = createRecordsWin(); |
||||||
|
} |
||||||
|
recordsWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function showRecordsWin() { |
||||||
|
recordsWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function hideRecordsWin() { |
||||||
|
recordsWin?.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
export { closeRecordsWin, createRecordsWin, hideRecordsWin, openRecordsWin, showRecordsWin }; |
||||||
@ -0,0 +1,54 @@ |
|||||||
|
import { BrowserWindow, shell } from 'electron'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
|
||||||
|
let settingWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createSettingWin(): BrowserWindow { |
||||||
|
settingWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 设置', |
||||||
|
icon: ICON, |
||||||
|
autoHideMenuBar: WIN_CONFIG.setting.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
width: WIN_CONFIG.setting.width, // 宽度(px)
|
||||||
|
height: WIN_CONFIG.setting.height, // 高度(px)
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
// settingWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
settingWin.loadURL(WEB_URL + 'setting.html'); |
||||||
|
} else { |
||||||
|
settingWin.loadFile(WIN_CONFIG.setting.html); |
||||||
|
} |
||||||
|
|
||||||
|
settingWin.webContents.setWindowOpenHandler(({ url }) => { |
||||||
|
if (url.startsWith('https:')) shell.openExternal(url); |
||||||
|
return { action: 'deny' }; |
||||||
|
}); |
||||||
|
|
||||||
|
return settingWin; |
||||||
|
} |
||||||
|
|
||||||
|
// 打开关闭录屏窗口
|
||||||
|
function closeSettingWin() { |
||||||
|
settingWin?.isDestroyed() || settingWin?.close(); |
||||||
|
settingWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
function openSettingWin() { |
||||||
|
if (!settingWin || settingWin?.isDestroyed()) { |
||||||
|
settingWin = createSettingWin(); |
||||||
|
} |
||||||
|
settingWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function showSettingWin() { |
||||||
|
settingWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function hideSettingWin() { |
||||||
|
settingWin?.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
export { closeSettingWin, createSettingWin, hideSettingWin, openSettingWin, showSettingWin }; |
||||||
@ -0,0 +1,130 @@ |
|||||||
|
import { BrowserWindow, clipboard, dialog, nativeImage, screen, desktopCapturer } from 'electron'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
import * as utils from '../main/utils'; |
||||||
|
|
||||||
|
let shotScreenWin: BrowserWindow | null = null; |
||||||
|
let savePath: string = ''; |
||||||
|
let downloadSet: Set<string> = new Set(); |
||||||
|
|
||||||
|
function createShotScreenWin(): BrowserWindow { |
||||||
|
shotScreenWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 截图', |
||||||
|
icon: ICON, |
||||||
|
show: false, |
||||||
|
autoHideMenuBar: WIN_CONFIG.shotScreen.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
useContentSize: WIN_CONFIG.shotScreen.useContentSize, // width 和 height 将设置为 web 页面的尺寸
|
||||||
|
movable: WIN_CONFIG.shotScreen.movable, // 是否可移动
|
||||||
|
frame: WIN_CONFIG.shotScreen.frame, // 无边框窗口
|
||||||
|
resizable: WIN_CONFIG.shotScreen.resizable, // 窗口大小是否可调整
|
||||||
|
hasShadow: WIN_CONFIG.shotScreen.hasShadow, // 窗口是否有阴影
|
||||||
|
transparent: WIN_CONFIG.shotScreen.transparent, // 使窗口透明
|
||||||
|
fullscreenable: WIN_CONFIG.shotScreen.fullscreenable, // 窗口是否可以进入全屏状态
|
||||||
|
fullscreen: WIN_CONFIG.shotScreen.fullscreen, // 窗口是否全屏
|
||||||
|
simpleFullscreen: WIN_CONFIG.shotScreen.simpleFullscreen, // 在 macOS 上使用 pre-Lion 全屏
|
||||||
|
alwaysOnTop: WIN_CONFIG.shotScreen.alwaysOnTop, |
||||||
|
skipTaskbar: WIN_CONFIG.shotScreen.skipTaskbar, |
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
// shotScreenWin.webContents.openDevTools();
|
||||||
|
|
||||||
|
if (url) { |
||||||
|
shotScreenWin.loadURL(WEB_URL + 'shotScreen.html'); |
||||||
|
} else { |
||||||
|
shotScreenWin.loadFile(WIN_CONFIG.shotScreen.html); |
||||||
|
} |
||||||
|
shotScreenWin.maximize(); |
||||||
|
shotScreenWin.setFullScreen(true); |
||||||
|
|
||||||
|
return shotScreenWin; |
||||||
|
} |
||||||
|
|
||||||
|
// 打开关闭录屏窗口
|
||||||
|
function closeShotScreenWin() { |
||||||
|
shotScreenWin?.isDestroyed() || shotScreenWin?.close(); |
||||||
|
shotScreenWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
function openShotScreenWin() { |
||||||
|
if (!shotScreenWin || shotScreenWin?.isDestroyed()) { |
||||||
|
shotScreenWin = createShotScreenWin(); |
||||||
|
} |
||||||
|
// shotScreenWin?.show();
|
||||||
|
} |
||||||
|
|
||||||
|
async function showShotScreenWin() { |
||||||
|
const { id } = screen.getPrimaryDisplay(); |
||||||
|
const { width, height } = utils.getScreenSize(); |
||||||
|
const sources = [ |
||||||
|
...(await desktopCapturer.getSources({ |
||||||
|
types: ['screen'], |
||||||
|
thumbnailSize: { |
||||||
|
width, |
||||||
|
height, |
||||||
|
}, |
||||||
|
})), |
||||||
|
]; |
||||||
|
|
||||||
|
let source = sources.filter((e: any) => parseInt(e.display_id, 10) == id)[0]; |
||||||
|
source || (source = sources[0]); |
||||||
|
const img = source.thumbnail.toDataURL(); |
||||||
|
shotScreenWin?.webContents.send('ss:show-win', img); |
||||||
|
if (!shotScreenWin || shotScreenWin?.isDestroyed()) { |
||||||
|
shotScreenWin = createShotScreenWin(); |
||||||
|
} |
||||||
|
shotScreenWin?.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function hideShotScreenWin() { |
||||||
|
shotScreenWin?.webContents.send('ss:hide-win'); |
||||||
|
shotScreenWin?.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
function minimizeShotScreenWin() { |
||||||
|
shotScreenWin?.minimize(); |
||||||
|
} |
||||||
|
|
||||||
|
function maximizeShotScreenWin() { |
||||||
|
shotScreenWin?.maximize(); |
||||||
|
} |
||||||
|
|
||||||
|
function unmaximizeShotScreenWin() { |
||||||
|
shotScreenWin?.unmaximize(); |
||||||
|
} |
||||||
|
|
||||||
|
async function downloadURLShotScreenWin(downloadUrl: string, isShowDialog?: boolean) { |
||||||
|
savePath = ''; |
||||||
|
isShowDialog && (savePath = await showOpenDialogShotScreenWin()); |
||||||
|
downloadSet.add(downloadUrl); |
||||||
|
shotScreenWin?.webContents.downloadURL(downloadUrl); |
||||||
|
} |
||||||
|
|
||||||
|
async function showOpenDialogShotScreenWin() { |
||||||
|
let res = await dialog.showOpenDialog({ |
||||||
|
properties: ['openDirectory'], |
||||||
|
}); |
||||||
|
|
||||||
|
const savePath = res.filePaths[0] || ''; |
||||||
|
|
||||||
|
return savePath; |
||||||
|
} |
||||||
|
|
||||||
|
function copyImg(filePath: string) { |
||||||
|
const image = nativeImage.createFromDataURL(filePath); |
||||||
|
clipboard.writeImage(image); |
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
closeShotScreenWin, |
||||||
|
copyImg, |
||||||
|
createShotScreenWin, |
||||||
|
downloadURLShotScreenWin, |
||||||
|
hideShotScreenWin, |
||||||
|
maximizeShotScreenWin, |
||||||
|
minimizeShotScreenWin, |
||||||
|
openShotScreenWin, |
||||||
|
showShotScreenWin, |
||||||
|
unmaximizeShotScreenWin, |
||||||
|
}; |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
import { BrowserWindow } from 'electron'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
|
||||||
|
let spliceImageWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createSpliceImageWin(): BrowserWindow { |
||||||
|
spliceImageWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 拼接图片', |
||||||
|
icon: ICON, |
||||||
|
height: WIN_CONFIG.spliceImage.height, |
||||||
|
width: WIN_CONFIG.spliceImage.width, |
||||||
|
autoHideMenuBar: WIN_CONFIG.spliceImage.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
// spliceImageWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
spliceImageWin.loadURL(WEB_URL + `spliceImage.html`); |
||||||
|
} else { |
||||||
|
spliceImageWin.loadFile(WIN_CONFIG.spliceImage.html); |
||||||
|
} |
||||||
|
|
||||||
|
spliceImageWin.once('ready-to-show', async () => { |
||||||
|
spliceImageWin?.show(); |
||||||
|
}); |
||||||
|
|
||||||
|
return spliceImageWin; |
||||||
|
} |
||||||
|
|
||||||
|
function openSpliceImageWin() { |
||||||
|
if (!spliceImageWin || spliceImageWin?.isDestroyed()) { |
||||||
|
spliceImageWin = createSpliceImageWin(); |
||||||
|
} |
||||||
|
spliceImageWin.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function closeSpliceImageWin() { |
||||||
|
spliceImageWin?.close(); |
||||||
|
} |
||||||
|
|
||||||
|
export { closeSpliceImageWin, createSpliceImageWin, openSpliceImageWin }; |
||||||
@ -0,0 +1,47 @@ |
|||||||
|
import { BrowserWindow, dialog, nativeImage } from 'electron'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
|
||||||
|
let videoConverterWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createVideoConverterWin(search?: any): BrowserWindow { |
||||||
|
videoConverterWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 图片编辑', |
||||||
|
icon: ICON, |
||||||
|
height: WIN_CONFIG.videoConverter.height, |
||||||
|
width: WIN_CONFIG.videoConverter.width, |
||||||
|
autoHideMenuBar: WIN_CONFIG.videoConverter.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
const videoUrl = search?.videoUrl || ''; |
||||||
|
|
||||||
|
// videoConverterWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
videoConverterWin.loadURL(WEB_URL + `videoConverter.html?videoUrl=${videoUrl}`); |
||||||
|
} else { |
||||||
|
videoConverterWin.loadFile(WIN_CONFIG.videoConverter.html, { |
||||||
|
search: `?videoUrl=${videoUrl}`, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
videoConverterWin.once('ready-to-show', async () => { |
||||||
|
videoConverterWin?.show(); |
||||||
|
}); |
||||||
|
|
||||||
|
return videoConverterWin; |
||||||
|
} |
||||||
|
|
||||||
|
function openVideoConverterWin(search?: any) { |
||||||
|
if (!videoConverterWin || videoConverterWin?.isDestroyed()) { |
||||||
|
videoConverterWin = createVideoConverterWin(search); |
||||||
|
} |
||||||
|
videoConverterWin.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function closeVideoConverterWin() { |
||||||
|
videoConverterWin?.close(); |
||||||
|
} |
||||||
|
|
||||||
|
export { closeVideoConverterWin, createVideoConverterWin, openVideoConverterWin }; |
||||||
@ -0,0 +1,61 @@ |
|||||||
|
import { BrowserWindow } from 'electron'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
import { getAudiosByAudioUrl } from '../main/utils'; |
||||||
|
|
||||||
|
let viewAudioWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createViewAudioWin(search?: any): BrowserWindow { |
||||||
|
viewAudioWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 音频', |
||||||
|
icon: ICON, |
||||||
|
autoHideMenuBar: WIN_CONFIG.viewAudio.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
const audioUrl = search?.audioUrl || ''; |
||||||
|
const recordId = search?.recordId || ''; |
||||||
|
// Open devTool if the app is not packaged
|
||||||
|
// viewAudioWin.webContents.openDevTools();
|
||||||
|
|
||||||
|
if (url) { |
||||||
|
viewAudioWin.loadURL( |
||||||
|
WEB_URL + |
||||||
|
`viewAudio.html?${audioUrl ? 'audioUrl=' + audioUrl : ''}${ |
||||||
|
recordId ? 'recordId=' + recordId : '' |
||||||
|
}`,
|
||||||
|
); |
||||||
|
} else { |
||||||
|
viewAudioWin.loadFile(WIN_CONFIG.viewAudio.html, { |
||||||
|
search: `${audioUrl ? 'audioUrl=' + audioUrl : ''}${recordId ? 'recordId=' + recordId : ''}`, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
viewAudioWin.once('ready-to-show', async () => { |
||||||
|
viewAudioWin?.show(); |
||||||
|
}); |
||||||
|
|
||||||
|
return viewAudioWin; |
||||||
|
} |
||||||
|
|
||||||
|
function openViewAudioWin(search?: any) { |
||||||
|
if (!viewAudioWin || viewAudioWin?.isDestroyed()) { |
||||||
|
viewAudioWin = createViewAudioWin(search); |
||||||
|
} |
||||||
|
viewAudioWin.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function closeViewAudioWin() { |
||||||
|
if (!viewAudioWin?.isDestroyed()) { |
||||||
|
viewAudioWin?.close(); |
||||||
|
} |
||||||
|
viewAudioWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
async function getAudios(audioUrl?: any) { |
||||||
|
let audios = await getAudiosByAudioUrl(audioUrl); |
||||||
|
return audios; |
||||||
|
} |
||||||
|
|
||||||
|
export { closeViewAudioWin, createViewAudioWin, getAudios, openViewAudioWin }; |
||||||
@ -0,0 +1,124 @@ |
|||||||
|
import { BrowserWindow, dialog } from 'electron'; |
||||||
|
import { readFile, writeFile } from 'node:fs'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
import { getImgsByImgUrl } from '../main/utils'; |
||||||
|
|
||||||
|
let viewImageWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createViewImageWin(search?: any): BrowserWindow { |
||||||
|
viewImageWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 图片', |
||||||
|
icon: ICON, |
||||||
|
frame: WIN_CONFIG.viewImage.frame, |
||||||
|
autoHideMenuBar: WIN_CONFIG.viewImage.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
const imgUrl = search?.imgUrl || ''; |
||||||
|
const recordId = search?.recordId || ''; |
||||||
|
// viewImageWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
viewImageWin.loadURL( |
||||||
|
WEB_URL + |
||||||
|
`viewImage.html?${imgUrl ? 'imgUrl=' + imgUrl : ''}${ |
||||||
|
recordId ? 'recordId=' + recordId : '' |
||||||
|
}`,
|
||||||
|
); |
||||||
|
// Open devTool if the app is not packaged
|
||||||
|
} else { |
||||||
|
viewImageWin.loadFile(WIN_CONFIG.viewImage.html, { |
||||||
|
search: `?${imgUrl ? 'imgUrl=' + imgUrl : ''}${recordId ? 'recordId=' + recordId : ''}`, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
return viewImageWin; |
||||||
|
} |
||||||
|
|
||||||
|
function openViewImageWin(search?: any) { |
||||||
|
if (!viewImageWin || viewImageWin?.isDestroyed()) { |
||||||
|
viewImageWin = createViewImageWin(search); |
||||||
|
} |
||||||
|
viewImageWin.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function closeViewImageWin() { |
||||||
|
if (!(viewImageWin && viewImageWin.isDestroyed())) { |
||||||
|
viewImageWin?.close(); |
||||||
|
} |
||||||
|
viewImageWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
function destroyViewImageWin() { |
||||||
|
viewImageWin?.destroy(); |
||||||
|
viewImageWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
function hideViewImageWin() { |
||||||
|
viewImageWin?.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
function minimizeViewImageWin() { |
||||||
|
viewImageWin?.minimize(); |
||||||
|
} |
||||||
|
|
||||||
|
function maximizeViewImageWin() { |
||||||
|
viewImageWin?.maximize(); |
||||||
|
} |
||||||
|
|
||||||
|
function unmaximizeViewImageWin() { |
||||||
|
viewImageWin?.unmaximize(); |
||||||
|
} |
||||||
|
|
||||||
|
function getIsAlwaysOnTopViewImageWin() { |
||||||
|
return viewImageWin?.isAlwaysOnTop(); |
||||||
|
} |
||||||
|
|
||||||
|
function setIsAlwaysOnTopViewImageWin(isAlwaysOnTop: boolean) { |
||||||
|
viewImageWin?.setAlwaysOnTop(isAlwaysOnTop); |
||||||
|
return isAlwaysOnTop; |
||||||
|
} |
||||||
|
|
||||||
|
async function getImgs(imgUrl: any) { |
||||||
|
let imgs = await getImgsByImgUrl(imgUrl); |
||||||
|
return imgs; |
||||||
|
} |
||||||
|
|
||||||
|
async function downloadImg(imgUrl: any) { |
||||||
|
let defaultPath = `pear-rec_${+new Date()}.png`; |
||||||
|
let res = await dialog.showSaveDialog({ |
||||||
|
defaultPath: defaultPath, |
||||||
|
filters: [{ name: 'Images', extensions: ['png', 'jpg', 'gif'] }], |
||||||
|
}); |
||||||
|
if (!res.canceled) { |
||||||
|
readFile(imgUrl, (err, imgData) => { |
||||||
|
if (err) { |
||||||
|
console.error(err); |
||||||
|
} else { |
||||||
|
writeFile(res.filePath, imgData, (err) => { |
||||||
|
if (err) { |
||||||
|
console.error(err); |
||||||
|
} else { |
||||||
|
console.log(`${defaultPath}:图片保存成功`); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
return imgUrl; |
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
closeViewImageWin, |
||||||
|
createViewImageWin, |
||||||
|
downloadImg, |
||||||
|
getImgs, |
||||||
|
getIsAlwaysOnTopViewImageWin, |
||||||
|
hideViewImageWin, |
||||||
|
maximizeViewImageWin, |
||||||
|
minimizeViewImageWin, |
||||||
|
openViewImageWin, |
||||||
|
setIsAlwaysOnTopViewImageWin, |
||||||
|
unmaximizeViewImageWin, |
||||||
|
}; |
||||||
@ -0,0 +1,95 @@ |
|||||||
|
import { BrowserWindow } from 'electron'; |
||||||
|
import { ICON, WEB_URL, WIN_CONFIG, preload, url } from '../main/constant'; |
||||||
|
|
||||||
|
let viewVideoWin: BrowserWindow | null = null; |
||||||
|
|
||||||
|
function createViewVideoWin(search?: any): BrowserWindow { |
||||||
|
viewVideoWin = new BrowserWindow({ |
||||||
|
title: 'pear-rec 视频', |
||||||
|
icon: ICON, |
||||||
|
autoHideMenuBar: WIN_CONFIG.viewVideo.autoHideMenuBar, // 自动隐藏菜单栏
|
||||||
|
webPreferences: { |
||||||
|
preload, |
||||||
|
}, |
||||||
|
}); |
||||||
|
|
||||||
|
const videoUrl = search?.videoUrl || ''; |
||||||
|
const recordId = search?.recordId || ''; |
||||||
|
// Open devTool if the app is not packaged
|
||||||
|
// viewVideoWin.webContents.openDevTools();
|
||||||
|
if (url) { |
||||||
|
viewVideoWin.loadURL( |
||||||
|
WEB_URL + |
||||||
|
`viewVideo.html?${videoUrl ? 'videoUrl=' + videoUrl : ''}${ |
||||||
|
recordId ? 'recordId=' + recordId : '' |
||||||
|
}`,
|
||||||
|
); |
||||||
|
} else { |
||||||
|
viewVideoWin.loadFile(WIN_CONFIG.viewVideo.html, { |
||||||
|
search: `?${videoUrl ? 'videoUrl=' + videoUrl : ''}${recordId ? 'recordId=' + recordId : ''}`, |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
viewVideoWin.once('ready-to-show', async () => { |
||||||
|
viewVideoWin?.show(); |
||||||
|
}); |
||||||
|
|
||||||
|
return viewVideoWin; |
||||||
|
} |
||||||
|
|
||||||
|
function openViewVideoWin(search?: any) { |
||||||
|
if (!viewVideoWin || viewVideoWin?.isDestroyed()) { |
||||||
|
viewVideoWin = createViewVideoWin(search); |
||||||
|
} |
||||||
|
viewVideoWin.show(); |
||||||
|
} |
||||||
|
|
||||||
|
function closeViewVideoWin() { |
||||||
|
if (!(viewVideoWin && viewVideoWin.isDestroyed())) { |
||||||
|
viewVideoWin?.close(); |
||||||
|
} |
||||||
|
viewVideoWin = null; |
||||||
|
} |
||||||
|
|
||||||
|
function hideViewVideoWin() { |
||||||
|
viewVideoWin?.hide(); |
||||||
|
} |
||||||
|
|
||||||
|
function minimizeViewVideoWin() { |
||||||
|
viewVideoWin?.minimize(); |
||||||
|
} |
||||||
|
|
||||||
|
function maximizeViewVideoWin() { |
||||||
|
viewVideoWin?.maximize(); |
||||||
|
} |
||||||
|
|
||||||
|
function unmaximizeViewVideoWin() { |
||||||
|
viewVideoWin?.unmaximize(); |
||||||
|
} |
||||||
|
|
||||||
|
function setAlwaysOnTopViewVideoWin(isAlwaysOnTop: boolean) { |
||||||
|
viewVideoWin?.setAlwaysOnTop(isAlwaysOnTop); |
||||||
|
} |
||||||
|
|
||||||
|
async function getHistoryVideoPath() { |
||||||
|
// const historyVideoPath = ((await getHistoryVideo()) as string) || '';
|
||||||
|
// return historyVideoPath;
|
||||||
|
} |
||||||
|
|
||||||
|
async function sendHistoryVideo() { |
||||||
|
// const filePath = await getHistoryVideoPath();
|
||||||
|
// let video = await readDirectoryVideo(filePath);
|
||||||
|
// return video;
|
||||||
|
} |
||||||
|
|
||||||
|
export { |
||||||
|
closeViewVideoWin, |
||||||
|
createViewVideoWin, |
||||||
|
hideViewVideoWin, |
||||||
|
maximizeViewVideoWin, |
||||||
|
minimizeViewVideoWin, |
||||||
|
openViewVideoWin, |
||||||
|
sendHistoryVideo, |
||||||
|
setAlwaysOnTopViewVideoWin, |
||||||
|
unmaximizeViewVideoWin, |
||||||
|
}; |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
|
||||||
|
<head> |
||||||
|
<meta charset="UTF-8" /> |
||||||
|
<link rel="icon" type="image/x-icon" href="/imgs/logo/favicon.ico" /> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||||
|
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" /> |
||||||
|
<title>pear-rec</title> |
||||||
|
</head> |
||||||
|
|
||||||
|
<body> |
||||||
|
<div id="root"></div> |
||||||
|
</body> |
||||||
|
|
||||||
|
</html> |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
{ |
||||||
|
"name": "@pear-rec/desktop", |
||||||
|
"version": "1.3.15", |
||||||
|
"main": "dist-electron/main/index.js", |
||||||
|
"description": "pear-rec", |
||||||
|
"author": "027xiguapi", |
||||||
|
"license": "Apache-2.0", |
||||||
|
"private": true, |
||||||
|
"debug": { |
||||||
|
"env": { |
||||||
|
"VITE_DEV_SERVER_URL": "http://127.0.0.1:7777/" |
||||||
|
} |
||||||
|
}, |
||||||
|
"scripts": { |
||||||
|
"dev": "vite", |
||||||
|
"build": "tsc && vite build", |
||||||
|
"build:win": "rimraf release && electron-builder", |
||||||
|
"preview": "vite preview", |
||||||
|
"pree2e": "vite build --mode=test", |
||||||
|
"e2e": "playwright test", |
||||||
|
"release": "electron-builder -p always", |
||||||
|
"server": "node dist-electron/server/main", |
||||||
|
"clear": "rimraf node_modules" |
||||||
|
}, |
||||||
|
"dependencies": { |
||||||
|
"electron-updater": "^6.1.8" |
||||||
|
}, |
||||||
|
"devDependencies": { |
||||||
|
"@pear-rec/server": "workspace:^", |
||||||
|
"@playwright/test": "^1.37.1", |
||||||
|
"@types/jsonfile": "^6.1.3", |
||||||
|
"@types/uuid": "^9.0.6", |
||||||
|
"@vitejs/plugin-react": "^4.0.4", |
||||||
|
"electron": "^29.1.3", |
||||||
|
"electron-builder": "^24.13.3", |
||||||
|
"electron-log": "^5.1.2", |
||||||
|
"jsonfile": "^6.1.0", |
||||||
|
"typescript": "^5.2.2", |
||||||
|
"uuid": "^9.0.1", |
||||||
|
"vite": "^5.1.5", |
||||||
|
"vite-plugin-electron": "^0.28.2" |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,54 @@ |
|||||||
|
import type { PlaywrightTestConfig } from "@playwright/test"; |
||||||
|
|
||||||
|
/** |
||||||
|
* Read environment variables from file. |
||||||
|
* https://github.com/motdotla/dotenv
|
||||||
|
*/ |
||||||
|
// require('dotenv').config();
|
||||||
|
|
||||||
|
/** |
||||||
|
* See https://playwright.dev/docs/test-configuration.
|
||||||
|
*/ |
||||||
|
const config: PlaywrightTestConfig = { |
||||||
|
testDir: "./e2e", |
||||||
|
/* Maximum time one test can run for. */ |
||||||
|
timeout: 30 * 1000, |
||||||
|
expect: { |
||||||
|
/** |
||||||
|
* Maximum time expect() should wait for the condition to be met. |
||||||
|
* For example in `await expect(locator).toHaveText();` |
||||||
|
*/ |
||||||
|
timeout: 5000, |
||||||
|
}, |
||||||
|
/* Run tests in files in parallel */ |
||||||
|
fullyParallel: true, |
||||||
|
/* Fail the build on CI if you accidentally left test.only in the source code. */ |
||||||
|
forbidOnly: !!process.env.CI, |
||||||
|
/* Retry on CI only */ |
||||||
|
retries: process.env.CI ? 2 : 0, |
||||||
|
/* Opt out of parallel tests on CI. */ |
||||||
|
workers: process.env.CI ? 1 : undefined, |
||||||
|
/* Reporter to use. See https://playwright.dev/docs/test-reporters */ |
||||||
|
reporter: "html", |
||||||
|
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ |
||||||
|
use: { |
||||||
|
/* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ |
||||||
|
actionTimeout: 0, |
||||||
|
/* Base URL to use in actions like `await page.goto('/')`. */ |
||||||
|
// baseURL: 'http://localhost:3000',
|
||||||
|
|
||||||
|
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ |
||||||
|
trace: "on-first-retry", |
||||||
|
}, |
||||||
|
|
||||||
|
/* Folder for test artifacts such as screenshots, videos, traces, etc. */ |
||||||
|
// outputDir: 'test-results/',
|
||||||
|
|
||||||
|
/* Run your local dev server before starting the tests */ |
||||||
|
// webServer: {
|
||||||
|
// command: 'npm run start',
|
||||||
|
// port: 3000,
|
||||||
|
// },
|
||||||
|
}; |
||||||
|
|
||||||
|
export default config; |
||||||
|
After Width: | Height: | Size: 402 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 757 B |
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 2.0 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 139 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
After Width: | Height: | Size: 353 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 155 KiB |
@ -0,0 +1 @@ |
|||||||
|
/// <reference types="vite/client" />
|
||||||
@ -0,0 +1,40 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"target": "ESNext", |
||||||
|
"experimentalDecorators": true, |
||||||
|
"emitDecoratorMetadata": true, |
||||||
|
"esModuleInterop": true, |
||||||
|
"useDefineForClassFields": true, |
||||||
|
"lib": [ |
||||||
|
"DOM", |
||||||
|
"DOM.Iterable", |
||||||
|
"ESNext" |
||||||
|
], |
||||||
|
"allowJs": false, |
||||||
|
"skipLibCheck": true, |
||||||
|
"allowSyntheticDefaultImports": true, |
||||||
|
"strict": false, |
||||||
|
"forceConsistentCasingInFileNames": true, |
||||||
|
"module": "ESNext", |
||||||
|
"moduleResolution": "Node", |
||||||
|
"resolveJsonModule": true, |
||||||
|
"isolatedModules": true, |
||||||
|
"noEmit": true, |
||||||
|
"jsx": "react-jsx", |
||||||
|
"baseUrl": "./", |
||||||
|
"paths": { |
||||||
|
"@/*": [ |
||||||
|
"src/*" |
||||||
|
] |
||||||
|
} |
||||||
|
}, |
||||||
|
"include": [ |
||||||
|
"src", |
||||||
|
"electron" |
||||||
|
], |
||||||
|
"references": [ |
||||||
|
{ |
||||||
|
"path": "./tsconfig.node.json" |
||||||
|
} |
||||||
|
] |
||||||
|
} |
||||||
@ -0,0 +1,13 @@ |
|||||||
|
{ |
||||||
|
"compilerOptions": { |
||||||
|
"composite": true, |
||||||
|
"module": "ESNext", |
||||||
|
"moduleResolution": "Node", |
||||||
|
"resolveJsonModule": true, |
||||||
|
"allowSyntheticDefaultImports": true |
||||||
|
}, |
||||||
|
"include": [ |
||||||
|
"vite.config.ts", |
||||||
|
"package.json" |
||||||
|
] |
||||||
|
} |
||||||
@ -0,0 +1,55 @@ |
|||||||
|
import { defineConfig } from 'vite'; |
||||||
|
import electron from 'vite-plugin-electron/simple'; |
||||||
|
import pkg from './package.json'; |
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig(({ command }) => { |
||||||
|
// rmSync('dist-electron', { recursive: true, force: true });
|
||||||
|
|
||||||
|
const isServe = command === 'serve'; |
||||||
|
const isBuild = command === 'build'; |
||||||
|
const sourcemap = isServe || !!process.env.VSCODE_DEBUG; |
||||||
|
|
||||||
|
return { |
||||||
|
plugins: [ |
||||||
|
electron({ |
||||||
|
main: { |
||||||
|
entry: 'electron/main/index.ts', |
||||||
|
vite: { |
||||||
|
build: { |
||||||
|
sourcemap: sourcemap ? 'inline' : undefined, // #332
|
||||||
|
minify: isBuild, |
||||||
|
outDir: 'dist-electron/main', |
||||||
|
rollupOptions: { |
||||||
|
external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}), |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
preload: { |
||||||
|
input: 'electron/preload/index.ts', |
||||||
|
vite: { |
||||||
|
build: { |
||||||
|
sourcemap: sourcemap ? 'inline' : undefined, // #332
|
||||||
|
minify: isBuild, |
||||||
|
outDir: 'dist-electron/preload', |
||||||
|
rollupOptions: { |
||||||
|
external: Object.keys('dependencies' in pkg ? pkg.dependencies : {}), |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}, |
||||||
|
}), |
||||||
|
], |
||||||
|
server: |
||||||
|
process.env.VSCODE_DEBUG && |
||||||
|
(() => { |
||||||
|
const url = new URL(pkg.debug.env.VITE_DEV_SERVER_URL); |
||||||
|
return { |
||||||
|
host: url.hostname, |
||||||
|
port: +url.port, |
||||||
|
}; |
||||||
|
})(), |
||||||
|
clearScreen: false, |
||||||
|
}; |
||||||
|
}); |
||||||
@ -0,0 +1,48 @@ |
|||||||
|
import { defineConfig } from 'vitepress'; |
||||||
|
import react from '@vitejs/plugin-react'; |
||||||
|
|
||||||
|
// https://vitepress.dev/reference/site-config
|
||||||
|
export default defineConfig({ |
||||||
|
vite: { |
||||||
|
plugins: [react()], |
||||||
|
}, |
||||||
|
title: 'pear-rec', |
||||||
|
base: '/pear-rec', |
||||||
|
description: '一个跨平台的截图、录屏、录音、录像软件', |
||||||
|
head: [['link', { rel: 'icon', href: '/favicon.ico' }]], |
||||||
|
themeConfig: { |
||||||
|
logo: '/favicon.ico', |
||||||
|
siteTitle: '『 pear-rec 』', |
||||||
|
outlineTitle: '🔴🟠🟡🟢🔵🟣🟤⚫⚪', |
||||||
|
outline: [2, 6], |
||||||
|
// 顶部导航
|
||||||
|
nav: [ |
||||||
|
{ text: 'Home', link: '/' }, |
||||||
|
{ text: '文档', link: '/desktop/examples.md' }, |
||||||
|
{ text: '下载', link: 'https://github.com/027xiguapi/pear-rec/releases' }, |
||||||
|
], |
||||||
|
// 侧边栏
|
||||||
|
sidebar: [ |
||||||
|
{ |
||||||
|
text: '文档', |
||||||
|
items: [ |
||||||
|
{ text: '桌面软件', link: '/desktop/examples.md' }, |
||||||
|
{ text: '截图插件', link: '/screenshot/examples' }, |
||||||
|
{ text: '录音插件', link: '/recorder/examples.md' }, |
||||||
|
{ text: '计时插件', link: '/timer/examples' }, |
||||||
|
{ text: '网页应用', link: '/web/examples.md' }, |
||||||
|
// { text: "markdown", link: "/markdown-examples.md" },
|
||||||
|
// { text: "Runtime API Examples", link: "/api-examples" },
|
||||||
|
], |
||||||
|
}, |
||||||
|
], |
||||||
|
|
||||||
|
socialLinks: [{ icon: 'github', link: 'https://github.com/027xiguapi/pear-rec' }], |
||||||
|
// 页脚
|
||||||
|
footer: { |
||||||
|
message: |
||||||
|
'<a href="https://www.apache.org/licenses/LICENSE-2.0" target="_blank">pear-rec is available under the Apache License V2.</a>', |
||||||
|
copyright: 'Copyright © 2023 西瓜皮', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}); |
||||||
|
After Width: | Height: | Size: 705 KiB |
|
After Width: | Height: | Size: 943 KiB |
|
After Width: | Height: | Size: 313 KiB |
|
After Width: | Height: | Size: 155 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
After Width: | Height: | Size: 1022 KiB |
|
After Width: | Height: | Size: 195 KiB |
|
After Width: | Height: | Size: 47 KiB |
|
After Width: | Height: | Size: 1.0 MiB |