first commit

This commit is contained in:
iamdj 2023-07-19 13:48:36 +08:00
parent ec86926894
commit 5b3612d8b6
70 changed files with 40697 additions and 3 deletions

65
LICENSE Normal file
View File

@ -0,0 +1,65 @@
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:
You must give any other recipients of the Work or Derivative Works a copy of this License; and
You must cause any modified files to carry prominent notices stating that You changed the files; and
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
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
Copyright [2023] [Dawn Launcher]
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.

View File

@ -1,5 +1,31 @@
# Dawn Launcher
Windows快捷启动工具帮助您整理杂乱无章的桌面分门别类管理您的桌面快捷方式让您的桌面保持干净整洁。
支持相对路径便携路径支持扫描本机开始菜单、本地Appx应用列表添加项目、支持多项目添加一次启动多个项目、支持添加网址并一键获取网址信息。
Windows快捷启动工具帮助您整理杂乱无章的桌面分门别类管理您的桌面快捷方式让您的桌面保持干净整洁。
官网https://dawnlauncher.com/
支持相对路径便携路径支持扫描本机开始菜单、本地Appx应用列表添加项目、支持多项目添加一次启动多个项目、支持添加网址并一键获取网址信息。
# 开发框架
Electron
# 支持平台
Windows
# 官网
[dawnlauncher.com](https://dawnlauncher.com/)
# 捐赠
![微信](/images/wechat.png)
![支付宝](/images/alipay.png)
# 界面
![界面](/images/界面.png)
## 子分类
![子分类](/images/子分类.png)
## 自定义主题
![自定义主题](/images/自定义主题.png)
## 自定义背景
![自定义背景](/images/自定义背景.png)
## 快速搜索
![快速搜索](/images/快速搜索.gif)
## 一键获取网址信息
![一键获取网址信息](/images/一键获取网址信息.gif)
## 相对路径(便携路径)
![相对路径(便携路径)](/images/相对路径.png)
## 关联文件夹
![关联文件夹](/images/关联文件夹.gif)
# License
Apache License Version 2.0

4
babel.config.js Normal file
View File

@ -0,0 +1,4 @@
module.exports = {
presets: ["@vue/cli-plugin-babel/preset"],
plugins: [],
};

14
binding.gyp Normal file
View File

@ -0,0 +1,14 @@
{
"targets": [
{
"target_name": "api",
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],
"sources": [ "./src/main/api/api.cc" ],
"include_dirs": [
"<!@(node -p \"require('node-addon-api').include\")"
],
'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
}
]
}

BIN
images/alipay.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

BIN
images/wechat.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

BIN
images/界面.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

19
jsconfig.JSON Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}

60
package.json Normal file
View File

@ -0,0 +1,60 @@
{
"name": "DawnLauncher",
"productName": "Dawn Launcher",
"author": "Dawn Launcher",
"version": "1.2.3",
"private": true,
"gypfile": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"electron:build": "vue-cli-service electron:build",
"electron:serve": "vue-cli-service electron:serve",
"postinstall": "electron-builder install-app-deps",
"postuninstall": "electron-builder install-app-deps"
},
"dependencies": {
"@ckpack/vue-color": "^1.4.1",
"bindings": "^1.5.0",
"cheerio": "^1.0.0-rc.12",
"core-js": "^3.8.3",
"dompurify": "^3.0.3",
"electron-store": "^8.1.0",
"jimp": "^0.22.7",
"mime": "^3.0.0",
"node-addon-api": "^5.0.0",
"pinyin-match": "^1.2.4",
"request": "^2.88.2",
"retry": "^0.13.1",
"simplebar": "^5.3.8",
"sortablejs": "^1.15.0",
"urijs": "^1.19.11",
"uuid": "^9.0.0",
"vue": "^3.3.4",
"vue-router": "^4.2.0",
"vuex": "^4.1.0",
"xml2js": "^0.5.0"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@tailwindcss/forms": "^0.5.3",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"autoprefixer": "^10.4.8",
"electron": "^22.3.17",
"electron-builder": "^24.4.0",
"electron-devtools-installer": "^3.2.0",
"modclean": "^3.0.0-beta.1",
"postcss": "^8.4.16",
"prettier": "^2.7.1",
"tailwindcss": "^3.1.8",
"terser-webpack-plugin": "^5.3.8",
"vue-cli-plugin-electron-builder": "^3.0.0-alpha.4"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead",
"not ie 11"
]
}

6
postcss.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/images/logo.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

BIN
public/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

589
src/main/api/api.cc Normal file
View File

@ -0,0 +1,589 @@
#include <napi.h>
#include <windows.h>
#include <atlimage.h>
#include <iostream>
#include <shobjidl.h>
#include <shlguid.h>
#include <string>
#include <atomic>
#include <shlobj.h>
#include <imm.h>
#pragma comment(lib, "imm32.lib")
std::string UTF8ToGBK(const char *source)
{
int length = MultiByteToWideChar(CP_UTF8, 0, source, -1, NULL, 0);
wchar_t *wcGBK = new wchar_t[length + 1];
memset(wcGBK, 0, length * 2 + 2);
MultiByteToWideChar(CP_UTF8, 0, source, -1, wcGBK, length);
length = WideCharToMultiByte(CP_ACP, 0, wcGBK, -1, NULL, 0, NULL, NULL);
char *cGBK = new char[length + 1];
memset(cGBK, 0, length + 1);
WideCharToMultiByte(CP_ACP, 0, wcGBK, -1, cGBK, length, NULL, NULL);
std::string strTemp(cGBK);
if (wcGBK)
delete[] wcGBK;
if (cGBK)
delete[] cGBK;
return strTemp;
}
std::string GBKToUTF8(const char *source)
{
int length = MultiByteToWideChar(CP_ACP, 0, source, -1, NULL, 0);
wchar_t *wStr = new wchar_t[length + 1];
memset(wStr, 0, length + 1);
MultiByteToWideChar(CP_ACP, 0, source, -1, wStr, length);
length = WideCharToMultiByte(CP_UTF8, 0, wStr, -1, NULL, 0, NULL, NULL);
char *str = new char[length + 1];
memset(str, 0, length + 1);
WideCharToMultiByte(CP_UTF8, 0, wStr, -1, str, length, NULL, NULL);
std::string strTemp = str;
if (wStr)
delete[] wStr;
if (str)
delete[] str;
return strTemp;
}
LPCWSTR StringToLPCWSTR(std::string source)
{
size_t size = source.length();
int wLen = ::MultiByteToWideChar(CP_UTF8,
0,
source.c_str(),
-1,
NULL,
0);
wchar_t *buffer = new wchar_t[wLen + 1];
memset(buffer, 0, (wLen + 1) * sizeof(wchar_t));
MultiByteToWideChar(CP_ACP, 0, source.c_str(), size, (LPWSTR)buffer, wLen);
return buffer;
}
Napi::Number SaveIcon(std::string source, std::string target, int size, Napi::Env env)
{
CoInitialize(NULL);
IShellItemImageFactory *itemImageFactory;
HBITMAP bitmap;
SIZE s = {size, size};
if (SUCCEEDED(SHCreateItemFromParsingName(StringToLPCWSTR(source), NULL, IID_PPV_ARGS(&itemImageFactory))))
{
itemImageFactory->GetImage(s, SIIGBF_ICONONLY, &bitmap);
itemImageFactory->Release();
}
CoUninitialize();
if (NULL == &bitmap)
{
return Napi::Number::New(env, 0);
}
else
{
CImage image;
image.Attach(bitmap);
image.SetHasAlphaChannel(1);
image.Save(target.c_str());
return Napi::Number::New(env, 1);
}
}
Napi::Number GetFileIcon(const Napi::CallbackInfo &info)
{
Napi::Env env = info.Env();
std::string source = info[0].ToString().Utf8Value();
std::string target = info[1].ToString().Utf8Value();
int size = info[2].As<Napi::Number>().Int32Value();
return SaveIcon(UTF8ToGBK(source.c_str()), UTF8ToGBK(target.c_str()), size, env);
}
Napi::Object GetShortcutFile(const Napi::CallbackInfo &info)
{
Napi::Env env = info.Env();
std::string source = info[0].ToString().Utf8Value();
source = UTF8ToGBK(source.c_str());
Napi::Object shortcutFileInfo;
CoInitialize(NULL);
CHAR target[MAX_PATH];
CHAR arguments[MAX_PATH];
WIN32_FIND_DATA fd;
IShellLink *shellLink;
HRESULT result = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (void **)&shellLink);
if (SUCCEEDED(result))
{
IPersistFile *persistFile;
result = shellLink->QueryInterface(IID_IPersistFile, (void **)&persistFile);
if (SUCCEEDED(result))
{
result = persistFile->Load(StringToLPCWSTR(source), STGM_READ);
if (SUCCEEDED(result))
{
shellLink->GetPath(target, MAX_PATH, &fd, SLGP_UNCPRIORITY);
shellLink->GetArguments(arguments, MAX_PATH);
shortcutFileInfo = Napi::Object::New(env);
shortcutFileInfo.Set(Napi::String::New(env, "target"), Napi::String::New(env, GBKToUTF8(target)));
shortcutFileInfo.Set(Napi::String::New(env, "arguments"), Napi::String::New(env, GBKToUTF8(arguments)));
}
}
if (NULL != persistFile)
{
persistFile->Release();
}
}
if (NULL != shellLink)
{
shellLink->Release();
}
CoUninitialize();
return shortcutFileInfo;
}
Napi::Number RunItem(const Napi::CallbackInfo &info)
{
Napi::Env env = info.Env();
std::string type = info[0].ToString().Utf8Value();
std::string source = info[1].ToString().Utf8Value();
std::string parameters = info[2].ToString().Utf8Value();
LPCWSTR ldir;
if (info[3] != NULL)
{
std::string dir = info[3].ToString().Utf8Value();
ldir = StringToLPCWSTR(UTF8ToGBK(dir.c_str()));
}
return Napi::Number::New(env, (unsigned long)ShellExecuteW(NULL, StringToLPCWSTR(type), StringToLPCWSTR(UTF8ToGBK(source.c_str())), StringToLPCWSTR(UTF8ToGBK(parameters.c_str())), ldir, SW_SHOWDEFAULT));
}
Napi::ThreadSafeFunction _tsfn;
HANDLE _hThread;
std::atomic_bool captureMouseMove = false;
// PostThreadMessage races with the actual thread; we'll get a thread ID
// and won't be able to post to it because it's "invalid" during the early
// lifecycle of the thread. To ensure that immediate pauses don't get dropped,
// we'll use this flag instead of distinct message IDs.
std::atomic_bool installEventHook = false;
DWORD dwThreadID = 0;
struct MouseEventContext
{
public:
int nCode;
WPARAM wParam;
LONG ptX;
LONG ptY;
DWORD mouseData;
};
void onMainThread(Napi::Env env, Napi::Function function, MouseEventContext *pMouseEvent)
{
auto nCode = pMouseEvent->nCode;
auto wParam = pMouseEvent->wParam;
auto ptX = pMouseEvent->ptX;
auto ptY = pMouseEvent->ptY;
auto nMouseData = pMouseEvent->mouseData;
delete pMouseEvent;
if (nCode >= 0)
{
auto name = "";
auto button = -1;
// Isolate mouse movement, as it's more CPU intensive
if (wParam == WM_MOUSEMOVE)
{
// Is mouse movement
if (captureMouseMove.load())
{
name = "mousemove";
}
}
else
{
// Is not mouse movement
// Determine event type
if (wParam == WM_LBUTTONUP || wParam == WM_RBUTTONUP || wParam == WM_MBUTTONUP)
{
name = "mouseup";
}
else if (wParam == WM_LBUTTONDOWN || wParam == WM_RBUTTONDOWN || wParam == WM_MBUTTONDOWN)
{
name = "mousedown";
}
else if (wParam == WM_MOUSEWHEEL || wParam == WM_MOUSEHWHEEL)
{
name = "mousewheel";
}
// Determine button
if (wParam == WM_LBUTTONUP || wParam == WM_LBUTTONDOWN)
{
button = 1;
}
else if (wParam == WM_RBUTTONUP || wParam == WM_RBUTTONDOWN)
{
button = 2;
}
else if (wParam == WM_MBUTTONUP || wParam == WM_MBUTTONDOWN)
{
button = 3;
}
else if (wParam == WM_MOUSEWHEEL)
{
button = 0;
}
else if (wParam == WM_MOUSEHWHEEL)
{
button = 1;
}
}
// Only proceed if an event was identified
if (name != "")
{
Napi::HandleScope scope(env);
auto x = Napi::Number::New(env, ptX);
auto y = Napi::Number::New(env, ptY);
auto mouseData = Napi::Number::New(env, nMouseData);
// Yell back to NodeJS
function.Call(env.Global(),
{Napi::String::New(env, name), x, y,
Napi::Number::New(env, button), mouseData});
}
}
}
LRESULT CALLBACK HookCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
// If not WM_MOUSEMOVE or WM_MOUSEMOVE has been requested, process event
if (!(wParam == WM_MOUSEMOVE && !captureMouseMove.load()))
{
// Prepare data to be processed
MSLLHOOKSTRUCT *data = (MSLLHOOKSTRUCT *)lParam;
auto pMouseEvent = new MouseEventContext();
pMouseEvent->nCode = nCode;
pMouseEvent->wParam = wParam;
pMouseEvent->ptX = data->pt.x;
pMouseEvent->ptY = data->pt.y;
pMouseEvent->mouseData = data->mouseData;
// Process event on non-blocking thread
_tsfn.NonBlockingCall(pMouseEvent, onMainThread);
}
// Let Windows continue with this event as normal
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
DWORD WINAPI MouseHookThread(LPVOID lpParam)
{
MSG msg;
HHOOK hook = installEventHook.load() ? SetWindowsHookEx(WH_MOUSE_LL, HookCallback, NULL, 0) : NULL;
while (GetMessage(&msg, NULL, 0, 0) > 0)
{
if (msg.message != WM_USER)
continue;
if (!installEventHook.load() && hook != NULL)
{
if (!UnhookWindowsHookEx(hook))
break;
hook = NULL;
}
else if (installEventHook.load() && hook == NULL)
{
hook = SetWindowsHookEx(WH_MOUSE_LL, HookCallback, NULL, 0);
if (hook == NULL)
break;
}
}
_tsfn.Release();
return GetLastError();
}
Napi::Boolean createMouseHook(const Napi::CallbackInfo &info)
{
_hThread = CreateThread(NULL, 0, MouseHookThread, NULL, CREATE_SUSPENDED, &dwThreadID);
_tsfn = Napi::ThreadSafeFunction::New(
info.Env(),
info[0].As<Napi::Function>(),
"WH_MOUSE_LL Hook Thread",
512,
1,
[](Napi::Env)
{ CloseHandle(_hThread); });
ResumeThread(_hThread);
return Napi::Boolean::New(info.Env(), true);
}
void enableMouseMove(const Napi::CallbackInfo &info)
{
captureMouseMove = true;
}
void disableMouseMove(const Napi::CallbackInfo &info)
{
captureMouseMove = false;
}
Napi::Boolean pauseMouseEvents(const Napi::CallbackInfo &info)
{
BOOL bDidPost = FALSE;
if (dwThreadID != 0)
{
installEventHook = false;
bDidPost = PostThreadMessageW(dwThreadID, WM_USER, NULL, NULL);
}
return Napi::Boolean::New(info.Env(), bDidPost);
}
Napi::Boolean resumeMouseEvents(const Napi::CallbackInfo &info)
{
BOOL bDidPost = FALSE;
if (dwThreadID != 0)
{
installEventHook = true;
bDidPost = PostThreadMessageW(dwThreadID, WM_USER, NULL, NULL);
}
return Napi::Boolean::New(info.Env(), bDidPost);
}
Napi::Boolean IsFullscreenSize(const Napi::CallbackInfo &info)
{
// 获取当前活动窗口的句柄
HWND foregroundWindow = GetForegroundWindow();
// 获取活动窗口的位置信息
RECT windowRect;
GetWindowRect(foregroundWindow, &windowRect);
// 获取包含活动窗口的显示器句柄
HMONITOR monitor = MonitorFromWindow(foregroundWindow, MONITOR_DEFAULTTONEAREST);
// 获取显示器信息
MONITORINFO monitorInfo = { sizeof(MONITORINFO) };
GetMonitorInfo(monitor, &monitorInfo);
// 获取屏幕的尺寸
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
// 比较窗口位置和显示器尺寸来判断是否处于全屏模式
if (windowRect.left <= 0 && windowRect.top <= 0 &&
windowRect.right >= screenWidth && windowRect.bottom >= screenHeight &&
monitorInfo.rcMonitor.left == 0 && monitorInfo.rcMonitor.top == 0 &&
monitorInfo.rcMonitor.right == screenWidth && monitorInfo.rcMonitor.bottom == screenHeight)
{
// 获取窗口类名
char className[256];
GetClassName(foregroundWindow, className, sizeof(className));
std::string classNameStr(className);
if (classNameStr != "WorkerW")
{
return Napi::Boolean::New(info.Env(), true);
}
}
return Napi::Boolean::New(info.Env(), false);
}
Napi::Boolean IsFullscreen(const Napi::CallbackInfo &info)
{
QUERY_USER_NOTIFICATION_STATE state;
HRESULT hr = SHQueryUserNotificationState(&state);
if (hr == S_OK)
{
switch (state)
{
case QUNS_NOT_PRESENT:
// 非全屏(机器锁定/屏幕保护程序/用户切换)
return Napi::Boolean::New(info.Env(), false);
case QUNS_BUSY:
// 全屏F11 全屏,我试过的所有视频游戏都使用它)
return IsFullscreenSize(info);
case QUNS_RUNNING_D3D_FULL_SCREEN:
// 全屏Direct3D 应用程序以独占模式运行,即全屏)
return Napi::Boolean::New(info.Env(), true);
case QUNS_PRESENTATION_MODE:
// 全屏(一种用于显示全屏演示文稿的特殊模式)
return Napi::Boolean::New(info.Env(), true);
case QUNS_ACCEPTS_NOTIFICATIONS:
// 不是全屏
return Napi::Boolean::New(info.Env(), false);
case QUNS_QUIET_TIME:
// 不是全屏
return Napi::Boolean::New(info.Env(), false);
case QUNS_APP:
// 不是全屏
return Napi::Boolean::New(info.Env(), false);
}
return Napi::Boolean::New(info.Env(), false);
}
else
{
return Napi::Boolean::New(info.Env(), false);
}
}
Napi::Boolean ContextMenu(const Napi::CallbackInfo &info)
{
// CoInitialize
CoInitialize(NULL);
// 获取文件路径
std::string filePath = info[1].ToString().Utf8Value();
// 获取文件的 IShellItem 接口
IShellItem *pItem;
HRESULT hr = SHCreateItemFromParsingName(StringToLPCWSTR(UTF8ToGBK(filePath.c_str())), NULL, IID_PPV_ARGS(&pItem));
if (FAILED(hr))
{
return Napi::Boolean::New(info.Env(), false);
}
// 获取文件的 IContextMenu 接口
IContextMenu *pContextMenu;
hr = pItem->BindToHandler(NULL, BHID_SFUIObject, IID_PPV_ARGS(&pContextMenu));
if (FAILED(hr))
{
pItem->Release();
return Napi::Boolean::New(info.Env(), false);
}
// 创建菜单
HMENU hMenu = CreatePopupMenu();
if (hMenu == NULL)
{
pContextMenu->Release();
pItem->Release();
return Napi::Boolean::New(info.Env(), false);
}
hr = pContextMenu->QueryContextMenu(hMenu, 0, 1, 0x7FFF, CMF_NORMAL);
if (FAILED(hr))
{
DestroyMenu(hMenu);
pContextMenu->Release();
pItem->Release();
return Napi::Boolean::New(info.Env(), false);
}
// 获取当前窗口句柄
Napi::Buffer<void *> buffer = info[0].As<Napi::Buffer<void *>>();
HWND hWnd = static_cast<HWND>(*reinterpret_cast<void **>(buffer.Data()));
if (!IsWindow(hWnd))
{
return Napi::Boolean::New(info.Env(), false);
}
// 弹出菜单
int command = TrackPopupMenuEx(hMenu, TPM_RETURNCMD | TPM_NONOTIFY, info[2].As<Napi::Number>(), info[3].As<Napi::Number>(), hWnd, NULL);
if (command > 0)
{
CMINVOKECOMMANDINFOEX info = {0};
info.cbSize = sizeof(info);
info.hwnd = hWnd;
info.lpVerb = MAKEINTRESOURCEA(command - 1);
info.nShow = SW_NORMAL;
pContextMenu->InvokeCommand((LPCMINVOKECOMMANDINFO)&info);
}
// 释放资源
DestroyMenu(hMenu);
pContextMenu->Release();
pItem->Release();
// CoUninitialize
CoUninitialize();
return Napi::Boolean::New(info.Env(), true);
}
Napi::Boolean SwitchEnglish(const Napi::CallbackInfo &info)
{
// 获取当前窗口句柄
Napi::Buffer<void *> buffer = info[0].As<Napi::Buffer<void *>>();
HWND hWnd = static_cast<HWND>(*reinterpret_cast<void **>(buffer.Data()));
if (!IsWindow(hWnd))
{
return Napi::Boolean::New(info.Env(), false);
}
// 获取输入法上下文
HIMC hImc = ImmGetContext(hWnd);
if (hImc == nullptr)
{
return Napi::Boolean::New(info.Env(), false);
}
// 设置输入法的首选转换模式为英文
ImmSetConversionStatus(hImc, IME_CMODE_ALPHANUMERIC, IME_SMODE_AUTOMATIC);
// 释放输入法上下文
ImmReleaseContext(hWnd, hImc);
return Napi::Boolean::New(info.Env(), true);
}
Napi::String getCursorPosWindowClassName(const Napi::CallbackInfo &info)
{
POINT cursorPos;
GetCursorPos(&cursorPos);
HWND windowHandle = WindowFromPoint(cursorPos);
char className[256];
GetClassName(windowHandle, className, sizeof(className));
std::string classNameStr(className);
return Napi::String::New(info.Env(), className);
}
Napi::Boolean TurnOffMonitor(const Napi::CallbackInfo &info)
{
// 关闭显示器
SendMessage(FindWindow(0, 0), WM_SYSCOMMAND, SC_MONITORPOWER, (LPARAM)2);
return Napi::Boolean::New(info.Env(), true);
}
Napi::Boolean EmptyRecycleBin(const Napi::CallbackInfo &info) {
// 获取当前窗口句柄
Napi::Buffer<void *> buffer = info[0].As<Napi::Buffer<void *>>();
HWND hWnd = static_cast<HWND>(*reinterpret_cast<void **>(buffer.Data()));
if (!IsWindow(hWnd))
{
return Napi::Boolean::New(info.Env(), false);
}
SHEmptyRecycleBinW(hWnd, NULL, SHERB_NOSOUND);
return Napi::Boolean::New(info.Env(), true);
}
Napi::Object Init(Napi::Env env, Napi::Object exports)
{
exports.Set(Napi::String::New(env, "GetFileIcon"),
Napi::Function::New(env, GetFileIcon));
exports.Set(Napi::String::New(env, "GetShortcutFile"),
Napi::Function::New(env, GetShortcutFile));
exports.Set(Napi::String::New(env, "RunItem"),
Napi::Function::New(env, RunItem));
exports.Set(Napi::String::New(env, "createMouseHook"),
Napi::Function::New(env, createMouseHook));
exports.Set(Napi::String::New(env, "enableMouseMove"),
Napi::Function::New(env, enableMouseMove));
exports.Set(Napi::String::New(env, "disableMouseMove"),
Napi::Function::New(env, disableMouseMove));
exports.Set(Napi::String::New(env, "pauseMouseEvents"),
Napi::Function::New(env, pauseMouseEvents));
exports.Set(Napi::String::New(env, "resumeMouseEvents"),
Napi::Function::New(env, resumeMouseEvents));
exports.Set(Napi::String::New(env, "IsFullscreen"),
Napi::Function::New(env, IsFullscreen));
exports.Set(Napi::String::New(env, "ContextMenu"),
Napi::Function::New(env, ContextMenu));
exports.Set(Napi::String::New(env, "SwitchEnglish"),
Napi::Function::New(env, SwitchEnglish));
exports.Set(Napi::String::New(env, "getCursorPosWindowClassName"),
Napi::Function::New(env, getCursorPosWindowClassName));
exports.Set(Napi::String::New(env, "TurnOffMonitor"),
Napi::Function::New(env, TurnOffMonitor));
exports.Set(Napi::String::New(env, "EmptyRecycleBin"),
Napi::Function::New(env, EmptyRecycleBin));
return exports;
}
NODE_API_MODULE(hello, Init)

32
src/main/appInit.js Normal file
View File

@ -0,0 +1,32 @@
import { app } from "electron";
import fs from "fs";
import path from "path";
function getDawnLauncherProfilePath() {
let p;
if (process.env.NODE_ENV !== "production") {
p = path.resolve(".");
} else {
p = path.dirname(process.execPath);
}
p = path.resolve(p, "..");
p = path.join(p, ".dawn_launcher_profile");
return p;
}
try {
// 安装版
// 记录一下默认目录
global.defaultAppDataPath = app.getPath("appData");
// 获取数据目录配置文件地址
let dataDirPath = getDawnLauncherProfilePath();
// 读取文件内容
let r = fs.readFileSync(dataDirPath);
if (r != null) {
let appDataPath = r.toString();
fs.statSync(appDataPath);
app.setPath("appData", appDataPath);
}
// 免安装版
// app.setPath("appData", process.env.NODE_ENV !== "production" ? path.resolve(".") + "/data" : path.dirname(process.execPath) + "/data");
} catch (e) {}

7
src/main/cache/data.js vendored Normal file
View File

@ -0,0 +1,7 @@
import Store from "electron-store";
const cacheStore = new Store({ name: "cache", encryptionKey: "41fdb85a-4706-57b1-ba22-d7556f3723c7", clearInvalidConfig: true });
export default {
cacheStore,
};

View File

@ -0,0 +1,50 @@
import util from "../util"
/**
* 获取分类
* @param parentId
* @param childId
* @returns {*|null|{childList}|any|any}
*/
function getClassificationById(parentId, childId) {
if (parentId != null) {
let classificationParent;
for (let c of global.list) {
if (c.id == parentId) {
classificationParent = c;
break;
}
}
if (classificationParent != null && childId != null) {
if (!util.arrayIsEmpty(classificationParent.childList)) {
let classificationChild;
for (let c of classificationParent.childList) {
if (c.id == childId) {
classificationChild = c;
break;
}
}
return classificationChild;
} else {
return classificationParent;
}
} else {
return classificationParent;
}
}
return null;
}
/**
* 转换ID
* @param id
* @param parentId
*/
function convertClassificationId(id, parentId) {
return { classificationParentId: parentId != null ? parentId : id, classificationChildId: parentId != null ? id : null };
}
export default {
getClassificationById,
convertClassificationId,
};

View File

@ -0,0 +1,338 @@
import { dialog, ipcMain, Menu } from "electron";
import data from "../data";
import ItemJS from "../item/index";
import cacheData from "../cache/data";
import util from "../util";
/**
* 删除分类提示
* @param params
* @param callback
*/
function classificationDeleteDialog(params, callback) {
let name = params.classificationChildId != null ? params.classificationChildName : params.classificationParentName;
dialog
.showMessageBox(global.mainWindow, {
message: global.currentLanguage.deleteClassificationMessage,
buttons: [global.currentLanguage.ok, global.currentLanguage.cancel],
type: "question",
noLink: true,
cancelId: 1,
})
.then((r) => {
if (r.response == 0) {
callback(params);
}
});
}
export default function () {
// 分类空白处右键菜单
ipcMain.on("classificationContentRightMenu", (event, args) => {
let m = Menu.buildFromTemplate([
{
label: global.currentLanguage.newClassification,
click: () => {
let params = {
type: 0,
};
global.mainWindow.webContents.send("showClassificationAddEditWindow", JSON.stringify(params));
},
},
]);
util.menuListen(m);
m.popup();
});
// 某个父级分类上右键菜单
ipcMain.on("classificationRightMenu", (event, args) => {
let p = JSON.parse(args);
let menuList = [
{
label: global.currentLanguage.newSubClassification,
click: () => {
let params = {
type: 0,
parentId: p.classificationParentId,
};
global.mainWindow.webContents.send("showClassificationAddEditWindow", JSON.stringify(params));
},
},
{ type: "separator" },
{
label: global.currentLanguage.newClassification,
click: () => {
let params = {
type: 0,
};
global.mainWindow.webContents.send("showClassificationAddEditWindow", JSON.stringify(params));
},
},
{ type: "separator" },
];
// 固定分类
let fixedClassificationData = data.store.get("fixedClassification");
let selected =
fixedClassificationData != null &&
fixedClassificationData.classificationParentId == p.classificationParentId &&
fixedClassificationData.classificationChildId == null;
menuList.push({
label: global.currentLanguage.fixedClassification,
icon: selected ? util.getDot() : null,
click: () => {
if (selected) {
data.store.set("fixedClassification", null);
} else {
data.store.set("fixedClassification", { classificationParentId: p.classificationParentId });
}
},
});
if (!p.aggregate) {
menuList.push({
label: global.currentLanguage.excludeSearch,
icon: p.excludeSearch ? util.getDot() : null,
click: () => {
let params = {
classificationParentId: p.classificationParentId,
};
global.mainWindow.webContents.send("classificationExcludeSearch", JSON.stringify(params));
},
});
}
menuList.push({ type: "separator" });
if (!p.haveClassificationChild) {
if (!p.aggregate) {
// 关联文件夹
menuList.push({
label: global.currentLanguage.associatedFolder,
click: () => {
let params = {
id: p.classificationParentId,
};
global.mainWindow.webContents.send("showClassificationAssociatedFolderWindow", JSON.stringify(params));
},
});
}
if (!p.isMapDirectory) {
menuList.push({
label: "聚合分类",
click: () => {
let params = {
id: p.classificationParentId,
};
global.mainWindow.webContents.send("showClassificationAggregateWindow", JSON.stringify(params));
},
});
}
menuList.push({ type: "separator" });
}
menuList.push({
label: global.currentLanguage.setIcon,
click: () => {
let params = {
id: p.classificationParentId,
};
global.mainWindow.webContents.send("showSetClassificationIconWindow", JSON.stringify(params));
},
});
menuList.push({
label: global.currentLanguage.deleteIcon,
click: () => {
let params = {
id: p.classificationParentId,
};
global.mainWindow.webContents.send("deleteSetClassificationIcon", JSON.stringify(params));
},
});
menuList.push({ type: "separator" });
if (!p.aggregate) {
menuList.push(ItemJS.itemSortMenu(p.classificationParentId, null, p.haveClassificationChild, p.sort));
}
menuList.push(...ItemJS.itemLayoutIconSize(p.classificationParentId, p.classificationChildId, p.haveClassificationChild, p.layout, p.iconSize));
menuList.push(ItemJS.itemShowOnly(p.classificationParentId, null, p.haveClassificationChild, p.showOnly));
if (
!p.haveClassificationChild &&
((p.layout != null && p.layout == "list") || (global.setting.item.layout == "list" && (p.layout == null || p.layout == "default")))
) {
menuList.push(ItemJS.itemColumnNumber(p.classificationParentId, null, p.haveClassificationChild, p.columnNumber));
}
menuList.push({ type: "separator" });
menuList.push(
{
label: global.currentLanguage.edit,
click: () => {
let params = {
type: 1,
id: p.classificationParentId,
};
global.mainWindow.webContents.send("showClassificationAddEditWindow", JSON.stringify(params));
},
},
{
label: global.currentLanguage.delete,
click: () => {
classificationDeleteDialog(p, (p) => {
let params = {
id: p.classificationParentId,
};
global.mainWindow.webContents.send("classificationDelete", JSON.stringify(params));
});
},
}
);
menuList.push({ type: "separator" });
menuList.push({
label: p.lockClassification ? global.currentLanguage.unlockClassification : global.currentLanguage.lockClassification,
click: () => {
global.mainWindow.webContents.send("setLockClassification", !p.lockClassification);
cacheData.cacheStore.set("lockClassification", !p.lockClassification);
},
});
let m = Menu.buildFromTemplate(menuList);
util.menuListen(m);
m.popup();
});
// 某个子级分类上右键菜单
ipcMain.on("classificationChildRightMenu", (event, args) => {
let p = JSON.parse(args);
let menuList = [];
// 固定分类
let fixedClassificationData = data.store.get("fixedClassification");
let selected =
fixedClassificationData != null &&
fixedClassificationData.classificationParentId == p.classificationParentId &&
fixedClassificationData.classificationChildId == p.classificationChildId;
menuList.push({
label: global.currentLanguage.fixedClassification,
icon: selected ? util.getDot() : null,
click: () => {
if (selected) {
data.store.set("fixedClassification", null);
} else {
data.store.set("fixedClassification", { classificationParentId: p.classificationParentId, classificationChildId: p.classificationChildId });
}
},
});
if (!p.aggregate) {
menuList.push({
label: global.currentLanguage.excludeSearch,
icon: p.excludeSearch ? util.getDot() : null,
click: () => {
let params = {
classificationParentId: p.classificationParentId,
classificationChildId: p.classificationChildId,
};
global.mainWindow.webContents.send("classificationExcludeSearch", JSON.stringify(params));
},
});
}
menuList.push({ type: "separator" });
if (!p.aggregate) {
// 关联文件夹
menuList.push({
label: global.currentLanguage.associatedFolder,
click: () => {
let params = {
id: p.classificationChildId,
parentId: p.classificationParentId,
};
global.mainWindow.webContents.send("showClassificationAssociatedFolderWindow", JSON.stringify(params));
},
});
}
if (!p.isMapDirectory) {
menuList.push({
label: "聚合分类",
click: () => {
let params = {
id: p.classificationChildId,
parentId: p.classificationParentId,
};
global.mainWindow.webContents.send("showClassificationAggregateWindow", JSON.stringify(params));
},
});
}
menuList.push({ type: "separator" });
menuList.push({
label: global.currentLanguage.setIcon,
click: () => {
let params = {
id: p.classificationChildId,
parentId: p.classificationParentId,
};
global.mainWindow.webContents.send("showSetClassificationIconWindow", JSON.stringify(params));
},
});
menuList.push({
label: global.currentLanguage.deleteIcon,
click: () => {
let params = {
id: p.classificationChildId,
parentId: p.classificationParentId,
};
global.mainWindow.webContents.send("deleteSetClassificationIcon", JSON.stringify(params));
},
});
menuList.push({ type: "separator" });
if (!p.aggregate) {
menuList.push(ItemJS.itemSortMenu(p.classificationParentId, p.classificationChildId, p.haveClassificationChild, p.sort));
}
menuList.push(...ItemJS.itemLayoutIconSize(p.classificationParentId, p.classificationChildId, p.haveClassificationChild, p.layout, p.iconSize));
menuList.push(ItemJS.itemShowOnly(p.classificationParentId, p.classificationChildId, p.haveClassificationChild, p.showOnly));
if (
!p.haveClassificationChild &&
((p.layout != null && p.layout == "list") || (global.setting.item.layout == "list" && (p.layout == null || p.layout == "default")))
) {
menuList.push(ItemJS.itemColumnNumber(p.classificationParentId, p.classificationChildId, p.haveClassificationChild, p.columnNumber));
}
menuList.push({ type: "separator" });
menuList.push(
{
label: global.currentLanguage.edit,
click: () => {
let params = {
type: 1,
id: p.classificationChildId,
parentId: p.classificationParentId,
};
global.mainWindow.webContents.send("showClassificationAddEditWindow", JSON.stringify(params));
},
},
{
label: global.currentLanguage.delete,
click: () => {
classificationDeleteDialog(p, (p) => {
let params = {
id: p.classificationChildId,
parentId: p.classificationParentId,
};
global.mainWindow.webContents.send("classificationDelete", JSON.stringify(params));
});
},
}
);
menuList.push({ type: "separator" });
menuList.push({
label: p.lockClassification ? global.currentLanguage.unlockClassification : global.currentLanguage.lockClassification,
click: () => {
global.mainWindow.webContents.send("setLockClassification", !p.lockClassification);
cacheData.cacheStore.set("lockClassification", !p.lockClassification);
},
});
let m = Menu.buildFromTemplate(menuList);
util.menuListen(m);
m.popup();
});
// 获取锁定分类状态
ipcMain.on("getLockClassification", (event, args) => {
let lockClassification = cacheData.cacheStore.get("lockClassification");
event.returnValue = lockClassification == null ? false : lockClassification;
});
// 获取固定分类
ipcMain.on("getFixedClassification", (event, args) => {
event.returnValue = data.store.get("fixedClassification");
});
// 设置固定分类
ipcMain.on("setFixedClassification", (event, args) => {
data.store.set("fixedClassification", args);
});
}

126
src/main/data.js Normal file
View File

@ -0,0 +1,126 @@
import ClassificationJS from "./classification/index";
const Store = require("electron-store");
const store = new Store({ name: "data", encryptionKey: "0b52eb58-4c0f-5ff1-b062-031546a8d269", clearInvalidConfig: true });
/**
* 默认初始化
* @returns {Promise<void>}
*/
function initData() {
let list = store.get("list");
if (list == null || list.length == 0) {
list = [
{
id: 1,
name: "新分类",
order: 0,
},
];
store.set("list", list);
}
global.list = list;
}
/**
* 分离数据
*/
function splitData() {
let iconData = store.get("iconData");
if (iconData == null) {
iconData = [];
for (let c of global.list) {
if (c.childList != null) {
for (let cc of c.childList) {
if (cc.itemList != null) {
for (let item of cc.itemList) {
let icon = {
classificationParentId: item.classificationParentId,
classificationChildId: item.classificationId,
itemId: item.id,
icon: item.icon,
};
item.icon = null;
iconData.push(icon);
}
}
}
} else {
if (c.itemList != null) {
for (let item of c.itemList) {
let icon = {
classificationParentId: item.classificationId,
classificationChildId: null,
itemId: item.id,
icon: item.icon,
};
item.icon = null;
iconData.push(icon);
}
}
}
}
store.set("iconData", iconData);
store.set("list", global.list);
}
}
/**
* 校验数据
*/
function validData() {
let iconData = store.get("iconData");
if (iconData != null) {
let newIconData = [];
for (let icon of iconData) {
let classification = ClassificationJS.getClassificationById(icon.classificationParentId, icon.classificationChildId);
if (classification != null && classification.itemList != null && classification.itemList.length > 0) {
for (let item of classification.itemList) {
if (icon.itemId == item.id) {
newIconData.push(icon);
break;
}
}
}
}
store.set("iconData", newIconData);
}
}
/**
* 获取分类数据
* @returns {Promise<T[string]>}
*/
function getList() {
this.initData();
let list = store.get("list");
global.list = list;
return list;
}
/**
* 保存
* @param list
* @returns {Promise<void>}
*/
function setList(list) {
store.set("list", list);
global.list = list;
}
/**
* 获取图标
* @returns {Promise<T[string]>}
*/
function getIconData() {
return store.get("iconData");
}
export default {
store,
initData,
splitData,
validData,
getList,
setList,
getIconData,
};

414
src/main/ipcEvent.js Normal file
View File

@ -0,0 +1,414 @@
import { ipcMain, dialog, Menu, app, shell } from "electron";
import os from "os";
import data from "@/main/data";
import fs from "fs";
import settingIndex from "./setting/index";
import util from "./util";
import retry from "retry";
import request from "request";
import mime from "mime";
import path from "path";
/**
* 固定位置
* @param fixedPosition
* @param alwaysCenter
*/
function setFixedPosition(fixedPosition, alwaysCenter) {
global.mainWindow.setMovable(fixedPosition);
if (alwaysCenter) {
global.mainWindow.setMovable(false);
}
}
export default function () {
// 隐藏
ipcMain.on("hide", (event, args) => {
global.mainWindow.hide();
});
// 隐藏
ipcMain.on("hideMainWindow", (event, args) => {
global.mainWindow.webContents.send("hideMainWindowBefore");
});
// 关闭
ipcMain.on("close", (event, args) => {
global.mainWindow.close();
});
// 获取数据
ipcMain.on("getList", (event) => {
let list = data.getList();
event.returnValue = list;
});
// 保存数据
ipcMain.on("setList", (event, args) => {
let params = JSON.parse(args);
data.setList(params.list);
settingIndex.setShortcutKey(global.setting);
if (params.searchWindowGetData != null && params.searchWindowGetData) {
if (global.searchWindow != null && !global.searchWindow.isDestroyed()) {
global.searchWindow.webContents.send("searchWindowGetData");
}
}
});
// 错误消息
ipcMain.on("errorMessage", (event, args) => {
dialog.showMessageBox(global.mainWindow, {
title: "Dawn Launcher",
message: args,
buttons: [global.currentLanguage.ok],
type: "error",
noLink: true,
cancelId: 1,
});
});
// 文本框菜单
ipcMain.on("textRightMenu", (event, args) => {
// 菜单
let m = Menu.buildFromTemplate([
{
role: "cut",
label: global.currentLanguage.cut,
},
{
role: "copy",
label: global.currentLanguage.copy,
},
{
role: "paste",
label: global.currentLanguage.paste,
},
]);
util.menuListen(m);
m.popup();
});
// 获取版本
ipcMain.on("getVersion", (event) => {
event.returnValue = app.getVersion();
});
// 打开网页
ipcMain.on("openUrl", (event, args) => {
shell.openExternal(args);
});
// 检查更新
ipcMain.on("checkUpdate", () => {
util.checkUpdate("checkUpdate");
});
// 统计
ipcMain.on("statistics", () => {
try {
let data = {
system: os.type(),
release: os.release(),
locale: app.getLocale(),
appVersion: app.getVersion(),
};
// 重试
const operation = retry.operation({
retries: 5, // 最多重试 5 次
factor: 1, // 每次重试之间的时间间隔加倍
minTimeout: 1000, // 第一次重试之前等待的时间
maxTimeout: 5000, // 最长等待时间
});
// 发起请求
operation.attempt((currentAttempt) => {
request(
{
uri: "https://client.dawnlauncher.com/access/statistics/add",
method: "POST",
json: true,
headers: {
"content-type": "application/json",
},
body: data,
timeout: 5000,
},
function (error, response, body) {
if (operation.retry(error)) {
return;
}
}
);
});
} catch (e) {}
});
// 备份数据
ipcMain.on("backup", () => {
try {
dialog
.showSaveDialog(global.mainWindow, {
title: global.currentLanguage.backUpData,
defaultPath: "data",
filters: [{ name: "JSON", extensions: ["json"] }],
})
.then((r) => {
if (!r.canceled && !util.strIsEmpty(r.filePath)) {
fs.copyFileSync(app.getPath("userData") + "\\data.json", r.filePath);
global.mainWindow.webContents.send("hideBackupRestore");
}
});
} catch (e) {
if (process.env.NODE_ENV !== "production") {
console.log(e);
}
}
});
// 恢复数据
ipcMain.on("restore", () => {
try {
dialog
.showOpenDialog(global.mainWindow, {
title: global.currentLanguage.restoreData,
filters: [{ name: "JSON", extensions: ["json"] }],
})
.then((r) => {
if (!r.canceled && !util.arrayIsEmpty(r.filePaths)) {
if (!util.strIsEmpty(r.filePaths[0])) {
fs.copyFileSync(r.filePaths[0], app.getPath("userData") + "\\data.json");
// 清空所有文件映射监听
if (global.mapDirectoryWatcher != null) {
for (let value of global.mapDirectoryWatcher.values()) {
if (value != null && value.watch != null && value.watch) {
value.watch.close();
}
}
global.mapDirectoryWatcher = new Map();
}
// 初始化图标数据
data.getList();
data.splitData();
// 重新获取数据
global.mainWindow.webContents.send("getAllData");
}
}
});
} catch (e) {
if (process.env.NODE_ENV !== "production") {
console.log(e);
}
}
});
// 设置置顶
ipcMain.on("setAlwaysTop", (event, args) => {
if (args) {
global.mainWindow.setAlwaysOnTop(true, "screen-saver");
} else {
global.mainWindow.setAlwaysOnTop(false);
}
});
// 反馈
ipcMain.on("feedback", () => {
shell.openExternal("https://support.qq.com/product/487828");
});
// 锁定尺寸
ipcMain.on("setResize", (event, args) => {
global.mainWindow.setResizable(args);
});
// 设置透明度
ipcMain.on("setOpacity", (event, args) => {
global.mainWindow.setOpacity(Number(args));
});
// 设置固定位置
ipcMain.on("setFixedPosition", (event, args) => {
setFixedPosition(args[0], args[1]);
});
// 永远居中
ipcMain.on("setAlwaysCenter", (event, args) => {
if (args[0]) {
global.mainWindow.center();
global.mainWindow.setMovable(false);
} else {
setFixedPosition(args[1], args[2]);
}
});
// 打赏赞助
ipcMain.on("rewardAndSponsorship", () => {
shell.openExternal("https://dawnlauncher.com/sponsor");
});
// 跳转搜索窗口高度
ipcMain.on("setSearchWindowHeight", (event, args) => {
global.searchWindow.setBounds({ height: args });
});
// 隐藏搜索窗口
ipcMain.on("hideSearchWindow", () => {
if (global.searchWindow.isVisible()) {
global.searchWindow.hide();
}
});
// 窗口设置透明
ipcMain.on("setSearchWindowOpacity", (event, args) => {
global.searchWindow.setOpacity(args);
event.returnValue = null;
});
// 获取release
ipcMain.on("getRelease", (event, args) => {
event.returnValue = os.release();
});
// 获取背景图
ipcMain.on("getBackgroundImageBase64", (event, args) => {
let params = JSON.parse(args);
fs.readFile(app.getPath("userData") + "\\images\\" + params.backgroundImage, (err, data) => {
if (!err) {
try {
let buffer = Buffer.from(data);
let image = "data:" + mime.getType(params.backgroundImage) + ";base64," + buffer.toString("base64");
if (params.page == "main") {
global.mainWindow.webContents.send("returnBackgroundImageBase64", image);
} else {
global.settingWindow.webContents.send("returnBackgroundImageBase64", image);
}
} catch (e) {
if (process.env.NODE_ENV !== "production") {
console.log(e);
}
}
}
});
});
// 通知快速搜索窗口获取数据
ipcMain.on("noticeSearchWindowGetData", () => {
if (global.searchWindow != null) {
global.searchWindow.webContents.send("searchWindowGetData");
}
});
// 获取软件目录
ipcMain.on("getPath", (event, args) => {
event.returnValue = process.env.NODE_ENV !== "production" ? path.resolve(".") : path.dirname(process.execPath);
});
// 获取图标
ipcMain.on("getIconData", (event) => {
let iconData = data.getIconData();
event.returnValue = iconData;
});
// 更新图标
ipcMain.on("updateIconData", (event, args) => {
// 参数
let updateIconData = JSON.parse(args);
// 获取图标数据
let iconData = data.store.get("iconData");
if (iconData != null) {
// 删除
if (!util.arrayIsEmpty(updateIconData.delete)) {
for (let del of updateIconData.delete) {
let index;
for (let i = 0; i < iconData.length; i++) {
if (
iconData[i].classificationParentId == del.classificationParentId &&
iconData[i].classificationChildId == del.classificationChildId &&
iconData[i].itemId == del.itemId
) {
index = i;
break;
}
}
if (index != null) {
iconData.splice(index, 1);
}
}
}
// 添加
if (!util.arrayIsEmpty(updateIconData.add)) {
for (let add of updateIconData.add) {
let icon = {
classificationParentId: add.classificationParentId,
classificationChildId: add.classificationChildId,
itemId: add.itemId,
icon: add.icon,
};
iconData.push(icon);
}
}
// 更新
if (!util.arrayIsEmpty(updateIconData.update)) {
for (let update of updateIconData.update) {
let flag = false;
for (let icon of iconData) {
if (
icon.classificationParentId == update.classificationParentId &&
icon.classificationChildId == update.classificationChildId &&
icon.itemId == update.itemId
) {
icon.icon = update.icon;
flag = true;
break;
}
}
if (!flag) {
iconData.push(update);
}
}
}
// set
data.store.set("iconData", iconData);
}
// 更新搜索框图标数据
if (global.searchWindow != null && !global.searchWindow.isDestroyed()) {
global.searchWindow.webContents.send("searchWindowUpdateIconData", args);
}
});
// 通知搜索窗口重新获取图标数据
ipcMain.on("searchWindowGetIconData", (event, args) => {
if (global.searchWindow != null && !global.searchWindow.isDestroyed()) {
global.searchWindow.webContents.send("getIconData");
}
});
// showMessageBoxSync
ipcMain.on("showMessageBoxSync", (event, args) => {
let index = dialog.showMessageBoxSync(global.mainWindow, {
title: "Dawn Launcher",
message: args,
buttons: [global.currentLanguage.ok, global.currentLanguage.cancel],
type: "question",
noLink: true,
cancelId: 1,
});
event.returnValue = index == 0 ? true : false;
});
// 选择文件夹
ipcMain.on("openDirectory", (event, args) => {
let params = JSON.parse(args);
let options = {
properties: ["openDirectory"],
};
if (!util.strIsEmpty(params.defaultPath)) {
options.defaultPath = params.defaultPath;
} else {
options.defaultPath = app.getPath("desktop");
}
dialog.showOpenDialog(params.window == "mainWindow" ? global.mainWindow : null, options).then((r) => {
if (r.filePaths.length > 0) {
event.returnValue = r.filePaths[0];
} else {
event.returnValue = null;
}
});
});
// 选择文件
ipcMain.on("openFile", (event, args) => {
let params = JSON.parse(args);
let options = {};
if (!util.strIsEmpty(params.defaultPath)) {
options.defaultPath = params.defaultPath;
} else {
options.defaultPath = app.getPath("desktop");
}
dialog.showOpenDialog(params.window == "mainWindow" ? global.mainWindow : null, options).then((r) => {
if (r.filePaths.length > 0) {
let filePath = r.filePaths[0];
if (params.target) {
if (mime.getType(filePath) == "application/x-ms-shortcut") {
// 快捷方式
// 获取真实文件路径和参数
let shortcutDetail = global.api.GetShortcutFile(filePath);
if (!util.strIsEmpty(shortcutDetail.target)) {
// 路径
filePath = shortcutDetail.target;
}
}
}
event.returnValue = filePath;
} else {
event.returnValue = null;
}
});
});
}

790
src/main/item/index.js Normal file
View File

@ -0,0 +1,790 @@
import { shell, dialog, app } from "electron";
import path from "path";
import fs from "fs";
import util from "../util";
import { v4 } from "uuid";
import Jimp from "jimp";
import ClassificationJS from "../classification/index.js";
import data from "../data";
const { exec } = require("child_process");
/**
* 校验无效项目
* @param itemList
*/
function checkInvalidItemList(itemList) {
let list = [];
if (!util.arrayIsEmpty(itemList)) {
for (let item of itemList) {
// 只校验文件和文件夹
if (item.type == 0 || item.type == 1) {
// 获取绝对路径
item.path = getAbsolutePath(item.path);
try {
fs.statSync(item.path);
} catch (e) {
if (item.classificationParentId != null) {
list.push(item.classificationParentId + "-" + item.classificationId + "-" + item.id);
} else {
list.push(item.classificationId + "-" + item.id);
}
}
}
}
}
return list;
}
/**
* 校验无效项目
* @returns {*[]}
*/
function checkInvalidItem() {
let list = [];
for (let c of global.list) {
if (!util.arrayIsEmpty(c.childList)) {
for (let cc of c.childList) {
if (util.strIsEmpty(cc.mapDirectory)) {
list.push(...checkInvalidItemList(cc.itemList));
}
}
} else {
if (util.strIsEmpty(c.mapDirectory)) {
list.push(...checkInvalidItemList(c.itemList));
}
}
}
return list;
}
/**
* 解析环境变量
* @param p
*/
function parseEnvPath(p) {
// 尝试解析环境变量
let parsedPath = path.parse(p);
let isBase = false;
let dirArr;
if (util.strIsEmpty(parsedPath.dir)) {
dirArr = parsedPath.base.split("\\");
isBase = true;
} else {
dirArr = parsedPath.dir.split("\\");
}
let newPathArr = [];
const pattern = /^%.*%$/;
for (let string of dirArr) {
if (pattern.test(string)) {
let nString = string.substring(1, string.length - 1);
if (!util.strIsEmpty(process.env[nString])) {
newPathArr.push(process.env[nString]);
} else {
newPathArr.push(string);
}
} else {
newPathArr.push(string);
}
}
if (!isBase) {
newPathArr.push(parsedPath.base);
}
return newPathArr.join("\\");
}
/**
* 是否是绝对路径
* @param p
*/
function isAbsolutePath(p) {
const regex = /^[a-zA-Z]:\\/;
return regex.test(p);
}
/**
* 获取绝对路径
* @param path
*/
function getAbsolutePath(p) {
if (!isAbsolutePath(p)) {
// 尝试解析环境变量
let newPath = parseEnvPath(p);
// 判断解析之后的路径是否是绝对路径
if (isAbsolutePath(newPath)) {
return newPath;
} else {
return path.resolve(process.env.NODE_ENV !== "production" ? path.resolve(".") : path.dirname(process.execPath), p);
}
}
return p;
}
/**
* 运行项目
* @param item
* @param location 是否打开文件所在的位置
* @param openWith 打开方式
*/
function itemRun(item, location, openWith) {
// 系统
if (item.type == 3) {
if (item.shell.indexOf("shell:") >= 0) {
shell.openExternal(item.shell);
} else {
if (item.shell == "cmd") {
if (item.admin) {
exec('powershell -Command "Start-Process cmd -Verb RunAs"', (error, stdout, stderr) => {});
} else {
exec("start cmd.exe", (error, stdout, stderr) => {});
}
} else if (item.shell == "turnOffMonitor") {
global.api.TurnOffMonitor();
} else {
exec(item.shell, (error, stdout, stderr) => {});
}
}
return;
} else if (item.type == 5) {
exec("start " + item.shell, (error, stdout, stderr) => {});
return;
}
// 如果是类型是0或者1并且是相对路径的话恢复为绝对路径
if (item.type == 0 || item.type == 1) {
// 获取路径
item.path = getAbsolutePath(item.path);
}
let t = item.path;
let params = !util.strIsEmpty(item.params) ? item.params.trim() : "";
if (openWith != null && openWith && item.type == 0) {
exec("RUNDLL32.EXE SHELL32.DLL,OpenAs_RunDLL " + t + "", (error, stdout, stderr) => {});
} else {
let type = "open";
if (item.type == 0) {
if (location) {
// 如果是打开文件所在位置
exec('start %windir%\\explorer.exe /select, "' + item.path + '"', (error, stdout, stderr) => {});
return;
} else {
// 以管理员身份运行
if (item.admin && (item.extension == ".exe" || item.extension == ".bat")) {
type = "runas";
}
}
} else if (item.type == 2) {
// 网址
t = item.url;
}
if (item.type == 0 || item.type == 1) {
// 判断文件或文件夹是否存在
try {
fs.accessSync(t);
global.api.RunItem(type, t, params, item.type == 0 && !util.strIsEmpty(item.startLocation) ? item.startLocation : path.dirname(item.path));
} catch (e) {
let message;
if (item.type == 0 && !location) {
message = global.currentLanguage.notFoundFileMessage;
} else {
message = global.currentLanguage.notFoundFolderMessage;
}
message += '"' + t + '"。';
dialog.showMessageBox(global.mainWindow, {
title: "Dawn Launcher",
message: message,
buttons: [global.currentLanguage.ok],
type: "error",
noLink: true,
});
}
} else {
global.api.RunItem(type, t, params, null);
}
}
}
/**
* 返回排序菜单
* @param classificationParentId
* @param classificationChildId
* @param haveClassificationChild
* @param sort
*/
function itemSortMenu(classificationParentId, classificationChildId, haveClassificationChild, sort) {
let submenu = [
{
label: global.currentLanguage.default,
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
sort: "default",
};
global.mainWindow.webContents.send("itemSort", JSON.stringify(params));
},
icon: (sort == null || sort == "default") && !haveClassificationChild ? util.getDot() : null,
},
{
label: global.currentLanguage.byInitial,
type: "normal",
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
sort: "initial",
};
global.mainWindow.webContents.send("itemSort", JSON.stringify(params));
},
icon: sort != null && sort == "initial" ? util.getDot() : null,
},
];
if (global.setting.item.openNumber) {
submenu.push({
label: global.currentLanguage.byOpenNumber,
type: "normal",
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
sort: "openNumber",
};
global.mainWindow.webContents.send("itemSort", JSON.stringify(params));
},
icon: sort != null && sort == "openNumber" ? util.getDot() : null,
});
}
return {
label: global.currentLanguage.sort,
type: "submenu",
submenu: submenu,
};
}
/**
* 返回布局和图标大小
* @param classificationParentId
* @param classificationChildId
* @param haveClassificationChild
* @param layout
* @param iconSize
*/
function itemLayoutIconSize(classificationParentId, classificationChildId, haveClassificationChild, layout, iconSize) {
let menuList = [
{
label: global.currentLanguage.layout,
type: "submenu",
submenu: [
{
label: global.currentLanguage.default,
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
type: "default",
};
global.mainWindow.webContents.send("itemTile", JSON.stringify(params));
},
icon: (layout == null || layout == "default") && !haveClassificationChild ? util.getDot() : null,
},
{
label: global.currentLanguage.tile,
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
type: "tile",
};
global.mainWindow.webContents.send("itemTile", JSON.stringify(params));
},
icon: layout != null && layout == "tile" ? util.getDot() : null,
},
{
label: global.currentLanguage.list,
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
type: "list",
};
global.mainWindow.webContents.send("itemTile", JSON.stringify(params));
},
icon: layout != null && layout == "list" ? util.getDot() : null,
},
],
},
];
menuList.push({
label: global.currentLanguage.iconSize,
type: "submenu",
submenu: [
{
label: global.currentLanguage.default,
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
type: null,
};
global.mainWindow.webContents.send("itemIconSize", JSON.stringify(params));
},
icon: iconSize == null && !haveClassificationChild ? util.getDot() : null,
},
{
label: global.currentLanguage.extraLarge,
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
type: 48,
};
global.mainWindow.webContents.send("itemIconSize", JSON.stringify(params));
},
icon: iconSize != null && iconSize == 48 ? util.getDot() : null,
},
{
label: global.currentLanguage.large,
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
type: 40,
};
global.mainWindow.webContents.send("itemIconSize", JSON.stringify(params));
},
icon: iconSize != null && iconSize == 40 ? util.getDot() : null,
},
{
label: global.currentLanguage.medium,
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
type: 32,
};
global.mainWindow.webContents.send("itemIconSize", JSON.stringify(params));
},
icon: iconSize != null && iconSize == 32 ? util.getDot() : null,
},
{
label: global.currentLanguage.small,
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
type: 24,
};
global.mainWindow.webContents.send("itemIconSize", JSON.stringify(params));
},
icon: iconSize != null && iconSize == 24 ? util.getDot() : null,
},
],
});
return menuList;
}
/**
* 返回显示菜单
* @param classificationParentId
* @param classificationChildId
* @param haveClassificationChild
* @param showOnly
*/
function itemShowOnly(classificationParentId, classificationChildId, haveClassificationChild, showOnly) {
let submenu = [
{
label: global.currentLanguage.default,
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
showOnly: "default",
};
global.mainWindow.webContents.send("itemShowOnly", JSON.stringify(params));
},
icon: (showOnly == null || showOnly == "default") && !haveClassificationChild ? util.getDot() : null,
},
{
label: global.currentLanguage.showOnlyFiles,
type: "normal",
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
showOnly: "file",
};
global.mainWindow.webContents.send("itemShowOnly", JSON.stringify(params));
},
icon: showOnly != null && showOnly == "file" ? util.getDot() : null,
},
{
label: global.currentLanguage.showOnlyFolders,
type: "normal",
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
showOnly: "folder",
};
global.mainWindow.webContents.send("itemShowOnly", JSON.stringify(params));
},
icon: showOnly != null && showOnly == "folder" ? util.getDot() : null,
},
];
return {
label: global.currentLanguage.show,
type: "submenu",
submenu: submenu,
};
}
/**
* 返回列数菜单
* @param classificationParentId
* @param classificationChildId
* @param haveClassificationChild
* @param columnNumber
*/
function itemColumnNumber(classificationParentId, classificationChildId, haveClassificationChild, columnNumber) {
let submenu = [
{
label: global.currentLanguage.default,
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
columnNumber: 0,
};
global.mainWindow.webContents.send("itemColumnNumber", JSON.stringify(params));
},
icon: (columnNumber == null || columnNumber == 0) && !haveClassificationChild ? util.getDot() : null,
},
];
for (let i = 0; i < 20; i++) {
submenu.push({
label: (i + 1).toString(),
click: () => {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
columnNumber: i + 1,
};
global.mainWindow.webContents.send("itemColumnNumber", JSON.stringify(params));
},
icon: columnNumber != null && columnNumber == i + 1 ? util.getDot() : null,
});
}
return {
label: global.currentLanguage.numberOfColumns,
type: "submenu",
submenu: submenu,
};
}
/**
* 获取文件图标
* @param target
* @param message
*/
async function getFileIcon(target, message) {
// 获取绝对路径
target = getAbsolutePath(target);
let size = 256;
try {
// 先获取一下文件判断是否存在,不存在抛出异常
let stats = fs.statSync(target);
// 图标临时地址
let tempPath = app.getPath("temp") + "\\" + v4() + ".png";
// 获取图标
let result = global.api.GetFileIcon(target, tempPath, size);
// 1为成功
if (result == 1) {
// 读取图标文件
let buffer = fs.readFileSync(tempPath);
// 如果透明区域占比大于80代表这个图标没有超大图标那么就获取48*48图标
let tempResult = await Jimp.read(tempPath);
let zero = 0;
let color = 0;
for (const { x, y, image } of tempResult.scanIterator(0, 0, tempResult.bitmap.width, tempResult.bitmap.height)) {
if (image.getPixelColor(x, y) == 0) {
zero++;
} else {
color++;
}
}
// 计算占比
let proportion = Math.round((zero / (zero + color)) * 100);
// 删除临时文件
fs.unlink(tempPath, (err) => {});
// 透明区域大于80获取48*48图标
if (proportion >= 80) {
// 图标临时地址
tempPath = app.getPath("temp") + "\\" + v4() + ".png";
// 获取48*48图标
result = global.api.GetFileIcon(target, tempPath, 48);
// 1成功
if (result == 1) {
// 读取图标文件
buffer = fs.readFileSync(tempPath);
// 删除文件
fs.unlink(tempPath, (err) => {});
}
}
// 图标
let base64 = "data:image/png;base64," + buffer.toString("base64");
// 返回base64
return base64;
}
} catch (e) {
if (message) {
dialog.showMessageBox(global.mainWindow, {
title: "Dawn Launcher",
message: global.currentLanguage.targetNotExist + '"' + target + '"。',
buttons: [global.currentLanguage.ok],
type: "error",
noLink: true,
cancelId: 1,
});
}
}
return null;
}
/**
* 新建文件夹监听
*/
function addMapDirectoryWatcher(classificationParentId, classificationChildId, mapDirectory) {
// key
let key = classificationParentId + (classificationChildId != null ? "-" + classificationChildId : "");
// 先删除原有监听
deleteMapDirectoryWatcher(classificationParentId, classificationChildId);
// 新建监听
let data = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
mapDirectory: mapDirectory,
};
let timer;
let watch = fs.watch(mapDirectory, (event, filename) => {
if (timer) {
clearTimeout(timer);
timer = null;
}
// 启动定时器,在指定的时间间隔后发送合并后的通知
timer = setTimeout(() => {
readMapDirectory(classificationParentId, classificationChildId, mapDirectory, false, true, true);
clearTimeout(timer);
timer = null;
}, 1000);
});
watch.on("error", (error) => {
watch.close();
global.mapDirectoryWatcher.delete(key);
});
// 保存
data.watch = watch;
global.mapDirectoryWatcher.set(key, data);
}
/**
* 删除文件夹监听
*/
function deleteMapDirectoryWatcher(classificationParentId, classificationChildId) {
// 判断是否存在
let key = classificationParentId + (classificationChildId != null ? "-" + classificationChildId : "");
let watcherData = global.mapDirectoryWatcher.get(key);
if (watcherData != null) {
// 存在
if (watcherData.watch != null && watcherData.watch) {
watcherData.watch.close();
watcherData.watch = null;
}
global.mapDirectoryWatcher.delete(key);
}
}
/**
* 读取映射文件夹内容
* @param classificationParentId
* @param classificationChildId
* @param mapDirectory
* @param listener
* @param old
* @param notice
*/
async function readMapDirectory(classificationParentId, classificationChildId, mapDirectory, listener, old, notice) {
let itemList = [];
try {
// 判断是否含有环境变量
mapDirectory = parseEnvPath(mapDirectory);
// 获取图标数据
let iconData = data.store.get("iconData");
let iconDataMap = new Map();
// 转为Map
if (!util.arrayIsEmpty(iconData)) {
for (let icon of iconData) {
iconDataMap.set(util.getKey(icon.classificationParentId, icon.classificationChildId, icon.itemId), icon);
}
}
// 文件类型
let stats = fs.statSync(mapDirectory);
// 必须是文件夹
if (stats.isDirectory()) {
// 获取旧列表
let oldItemList = [];
let hiddenItem;
// 分类
let classification = ClassificationJS.getClassificationById(classificationParentId, classificationChildId);
if (classification != null) {
if (old != null && old) {
oldItemList = classification.itemList;
}
hiddenItem = classification.hiddenItem;
}
// 转为数组
let hiddenItemArr = [];
if (!util.strIsEmpty(hiddenItem)) {
hiddenItemArr = hiddenItem.split(",");
}
// 读取文件夹下面的所有文件
let pathList = fs.readdirSync(mapDirectory);
let i = 1;
for (let p of pathList) {
try {
// 判断是否隐藏
let flag = false;
for (let item of hiddenItemArr) {
if (item != null && p == item.trim()) {
flag = true;
break;
}
}
if (flag) {
continue;
}
// 组合路径
let np = path.join(mapDirectory, p);
// 获取类型 0:文件 1:文件夹
let type = fs.statSync(np).isDirectory() ? 1 : 0;
// 如果旧数据有的话,获取旧数据
let icon;
let openNumber;
let lastOpen;
let quickSearchOpenNumber;
let quickSearchLastOpen;
if (!util.arrayIsEmpty(oldItemList)) {
for (let oldItem of oldItemList) {
if (
(type == 0 && oldItem.name == util.removeSuffix(p) && oldItem.path == np && oldItem.type == type) ||
(type == 1 && oldItem.name == p && oldItem.path == np && oldItem.type == type)
) {
// 通过旧数据获取图标
let oldIcon = iconDataMap.get(util.getKey(classificationParentId, classificationChildId, oldItem.id));
if (oldIcon != null) {
icon = oldIcon.icon;
}
openNumber = oldItem.openNumber;
lastOpen = oldItem.lastOpen;
quickSearchOpenNumber = oldItem.quickSearchOpenNumber;
quickSearchLastOpen = oldItem.quickSearchLastOpen;
break;
}
}
}
let item = {
// id
id: i++,
// 路径
path: np,
// 名称
name: type == 1 ? p : util.removeSuffix(p),
// 全称
fullName: p,
// 图标
icon: icon != null ? icon : await getFileIcon(np, false),
// 类型 0:文件 1:文件夹
type: type,
// 打开次数
openNumber: openNumber,
// 最近打开
lastOpen: lastOpen,
// 快速搜索 打开次数
quickSearchOpenNumber: quickSearchOpenNumber,
// 快速搜索 最近打开
quickSearchLastOpen: quickSearchLastOpen,
};
itemList.push(item);
} catch (e) {
if (process.env.NODE_ENV !== "production") {
console.log(e);
}
}
}
if (listener != null && listener) {
// 新建监听
addMapDirectoryWatcher(classificationParentId, classificationChildId, mapDirectory);
}
if (notice) {
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
itemList: itemList,
clear: true,
};
// 添加项目
global.mainWindow.webContents.send("itemAdd", JSON.stringify(params));
}
}
} catch (e) {
if (process.env.NODE_ENV !== "production") {
console.log(e);
}
}
return itemList;
}
/**
* 初始化映射文件夹
*/
async function initMapDirectory() {
let list = [];
for (let c of global.list) {
if (util.arrayIsEmpty(c.childList)) {
// 没有子级
if (!util.strIsEmpty(c.mapDirectory)) {
let { classificationParentId, classificationChildId } = ClassificationJS.convertClassificationId(c.id, c.parentId);
let itemList = await readMapDirectory(classificationParentId, classificationChildId, c.mapDirectory, true, true, false);
list.push({
classificationParentId: classificationParentId,
itemList: itemList,
});
}
} else {
for (let cc of c.childList) {
if (!util.strIsEmpty(cc.mapDirectory)) {
let { classificationParentId, classificationChildId } = ClassificationJS.convertClassificationId(cc.id, cc.parentId);
let itemList = await readMapDirectory(classificationParentId, classificationChildId, cc.mapDirectory, true, true, false);
list.push({
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
itemList: itemList,
});
}
}
}
}
return list;
}
export default {
checkInvalidItem,
isAbsolutePath,
getAbsolutePath,
itemRun,
itemSortMenu,
itemLayoutIconSize,
itemShowOnly,
itemColumnNumber,
getFileIcon,
addMapDirectoryWatcher,
readMapDirectory,
deleteMapDirectoryWatcher,
initMapDirectory,
};

1897
src/main/item/ipcEvent.js Normal file

File diff suppressed because it is too large Load Diff

816
src/main/main.js Normal file
View File

@ -0,0 +1,816 @@
import appInit from "./appInit";
import { app, BrowserWindow, dialog, ipcMain, Menu, protocol, Tray, screen } from "electron";
import { createProtocol } from "vue-cli-plugin-electron-builder/lib";
import classificationIpcEvent from "./classification/ipcEvent";
import itemIpcEvent from "./item/ipcEvent";
import settingIpcEvent from "./setting/ipcEvent";
import ipcEvent from "./ipcEvent";
import settingIndex from "./setting/index";
import path from "path";
import util from "./util";
import itemIndex from "./item/index";
import cacheData from "./cache/data";
import data from "./data";
// 解决透明窗口闪烁
app.commandLine.appendSwitch("wm-window-animations-disabled");
// 数据
const settingData = require("./setting/data");
protocol.registerSchemesAsPrivileged([{ scheme: "app", privileges: { secure: true, standard: true } }]);
// 主窗口
let mainWindow = null;
// 设置窗口
let settingWindow = null;
// 搜索框
let searchWindow = null;
/**
* 创建主窗口
* @param init
* @returns {Promise<void>}
*/
async function createWindow(init) {
// 浏览器开发者工具
let devTools;
// 环境判断
if (process.env.NODE_ENV !== "production") {
// 开发
devTools = true;
} else {
// 正式
devTools = false;
}
if (init) {
// 初始化监听
await ipcEvent();
await classificationIpcEvent();
await itemIpcEvent();
await settingIpcEvent();
// 初始化数据
await settingData.initData();
await data.initData();
await data.splitData();
await data.validData();
// watch
global.mapDirectoryWatcher = new Map();
}
// 记录是否透明
if (global.setting.appearance.backgroundTransparency == 1) {
global.backgroundTransparency = false;
} else {
global.backgroundTransparency = true;
}
// 主窗口
global.mainWindow = mainWindow = new BrowserWindow({
minWidth: 300,
minHeight: 400,
width: 800,
height: 600,
frame: false,
show: false,
maximizable: false,
minimizable: false,
fullscreenable: false,
transparent: global.backgroundTransparency,
skipTaskbar: true,
webPreferences: {
backgroundThrottling: false,
nodeIntegration: true,
contextIsolation: false,
spellcheck: false,
devTools: devTools,
},
});
// 加载页面
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
mainWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL);
} else {
createProtocol("app");
// Load the index.html when not in development
mainWindow.loadURL("app://./index.html");
}
// 加载完webContents后再显示窗口
mainWindow.webContents.on("did-finish-load", function () {
// 透明度
mainWindow.setOpacity(Number(global.setting.appearance.transparency));
// 永远居中不可移动
if (global.setting.general.alwaysCenter) {
// 是否可移动
mainWindow.setMovable(false);
} else {
// 是否可移动
mainWindow.setMovable(!global.setting.general.fixedPosition);
}
// 恢复上一次的位置
let bounds = cacheData.cacheStore.get("bounds");
if (bounds != null) {
mainWindow.setBounds(bounds);
}
// 永远置顶
if (global.setting.general.alwaysTop) {
mainWindow.setAlwaysOnTop(true, "screen-saver");
}
// 是否托盘化启动
if (!global.setting.general.startupTray) {
mainWindow.show();
}
// 锁定尺寸
mainWindow.setResizable(!global.setting.general.lockSize);
// 检查更新
let checkUpdate = cacheData.cacheStore.get("checkUpdate");
if (checkUpdate == null || checkUpdate) {
util.checkUpdate("init");
}
// 永远居中
if (global.setting.general.alwaysCenter) {
mainWindow.center();
}
// 判断窗口位置
let displays = util.getWindowInScreen();
if (displays.length == 0) {
// 代表窗口的位置不再任一屏幕内,将窗口位置移动到主窗口
mainWindow.center();
}
// 边缘吸附
util.edgeAdsorb();
});
// 禁用标题栏右键
mainWindow.hookWindowMessage(278, function (e) {
// 窗口禁用
mainWindow.setEnabled(false);
// 延时太快会立刻启动,太慢会妨碍窗口其他操作,可自行测试最佳时间
setTimeout(() => {
mainWindow.setEnabled(true);
}, 100);
return true;
});
// 窗口移动完毕
mainWindow.on("moved", () => {
// 永远居中
if (global.setting.general.alwaysCenter) {
mainWindow.center();
}
// 记录位置
cacheData.cacheStore.set("bounds", mainWindow.getBounds());
// 边缘吸附
util.edgeAdsorb();
});
// 改变窗口大小完毕
mainWindow.on("resized", () => {
// 永远居中
if (global.setting.general.alwaysCenter) {
mainWindow.center();
}
// 记录位置
cacheData.cacheStore.set("bounds", mainWindow.getBounds());
});
// 监听鼠标移动
const mouseEvent = require("./mouse");
mouseEvent.on("mousemove", (data) => {
if (global.setting.general.edgeAutoHide && (global.blurHide == null || !global.blurHide) && !global.setting.general.alwaysCenter) {
autoHide(data, 40, true);
}
});
// 监听鼠标抬起
mouseEvent.on("mouseup", (data) => {
// 中键单击
if (data.button == 3 && global.setting.general.showHideMouseWheelClick) {
if (util.notDisturb()) {
return;
}
if (global.mainWindow.isVisible()) {
// global.mainWindow.hide();
global.mainWindow.webContents.send("hideMainWindowBefore");
} else {
util.showFollowMousePosition();
global.mainWindow.show();
global.mainWindow.focus();
global.blurHide = true;
}
}
// 双击任务栏显示/隐藏窗口
if (global.setting.general.doubleClickTaskbar) {
// 不是左键的话
if (data.button != 1) {
// 清除timeout
clearTimeout(global.doubleClickTimer);
// 清空
global.doubleClickCounter = 0;
return;
}
// 双击操作
let displays = util.getWindowInScreen();
if (displays.length > 1 || displays.length == 0) {
// 清除timeout
clearTimeout(global.doubleClickTimer);
// 清空
global.doubleClickCounter = 0;
return;
}
// 获取鼠标位置
let point = screen.getCursorScreenPoint();
// 判断鼠标是否在当前屏幕内
if (
point.x >= displays[0].bounds.x &&
point.x <= displays[0].bounds.x + displays[0].bounds.width &&
point.y >= displays[0].bounds.y &&
point.y <= displays[0].bounds.y + displays[0].bounds.height
) {
// 判断是否双击在任务栏上
let flag = false;
// 判断任务栏在哪一侧
if (displays[0].bounds.height > displays[0].workArea.height) {
if (displays[0].bounds.y == displays[0].workArea.y) {
// 底部
let top = displays[0].workArea.y + displays[0].workArea.height;
let bottom = displays[0].bounds.y + displays[0].bounds.height;
if (point.y >= top && point.y <= bottom) {
flag = true;
}
} else {
// 顶部
if (point.y >= displays[0].bounds.y && point.y <= displays[0].workArea.y) {
flag = true;
}
}
} else if (displays[0].bounds.width > displays[0].workArea.width) {
if (displays[0].bounds.x == displays[0].workArea.x) {
// 右侧
let left = displays[0].workArea.x + displays[0].workArea.width;
let right = displays[0].bounds.x + displays[0].bounds.width;
if (point.x >= left && point.x <= right) {
flag = true;
}
} else {
// 左侧
if (point.x >= displays[0].bounds.x && point.x <= displays[0].workArea.x) {
flag = true;
}
}
}
if (flag) {
// 监听双击
if (global.doubleClickCounter == null) {
global.doubleClickCounter = 0;
}
// +1
global.doubleClickCounter++;
// 等于2就是双击
if (global.doubleClickCounter != null && global.doubleClickCounter == 2) {
// 清除timeout
clearTimeout(global.doubleClickTimer);
// 清空
global.doubleClickCounter = 0;
let className = global.api.getCursorPosWindowClassName();
if (className.indexOf("MSTask") >= 0 || className == "Shell_TrayWnd") {
if (mainWindow.isVisible()) {
// mainWindow.hide();
global.mainWindow.webContents.send("hideMainWindowBefore");
} else {
util.showFollowMousePosition();
mainWindow.show();
}
}
} else {
// 间隔为500毫秒如果超过500毫秒就代表不是双击
global.doubleClickTimer = setTimeout(function () {
global.doubleClickCounter = 0;
}, 500);
}
} else {
// 清除timeout
clearTimeout(global.doubleClickTimer);
// 清空
global.doubleClickCounter = 0;
}
} else {
// 清除timeout
clearTimeout(global.doubleClickTimer);
// 清空
global.doubleClickCounter = 0;
}
}
});
// 失去焦点
mainWindow.on("blur", () => {
if (global.setting.general.edgeAutoHide && global.blurHide) {
let scaleFactor = screen.getPrimaryDisplay().scaleFactor;
let data = {
x: screen.getCursorScreenPoint().x * scaleFactor,
y: screen.getCursorScreenPoint().y * scaleFactor,
};
autoHide(data, 0, false);
}
if (mainWindow.isVisible()) {
if (global.setting.general.hideLosingFocus && !global.setting.general.alwaysTop) {
// 隐藏
// mainWindow.hide();
global.mainWindow.webContents.send("hideMainWindowBefore");
}
}
});
// 显示窗口
mainWindow.on("show", () => {
// 检测无效项目
if (global.setting.item.checkInvalidItem) {
global.mainWindow.webContents.send("checkInvalidItemResult", JSON.stringify(itemIndex.checkInvalidItem()));
}
// 边缘吸附
util.edgeAdsorb();
// 显示窗口时将输入法切换为英文模式
if (global.setting.general.switchEnglish) {
global.api.SwitchEnglish(mainWindow.getNativeWindowHandle());
}
});
// 隐藏窗口
mainWindow.on("hide", () => {
global.blurHide = null;
});
// 关闭窗口事件
mainWindow.on("close", () => {
// 关闭搜索框
if (searchWindow != null && !searchWindow.isDestroyed()) {
searchWindow.close();
}
// 释放鼠标监听
global.api.disableMouseMove();
});
}
/**
* 创建搜索窗口
* @returns {Promise<void>}
*/
async function createSettingWindow() {
// 浏览器开发者工具
let devTools;
// 环境判断
if (process.env.NODE_ENV !== "production") {
// 开发
devTools = true;
} else {
// 正式
devTools = false;
}
// 设置窗口
global.settingWindow = settingWindow = new BrowserWindow({
width: 600,
height: 500,
frame: false,
show: false,
maximizable: false,
minimizable: false,
fullscreenable: false,
skipTaskbar: true,
parent: mainWindow,
resizable: false,
webPreferences: {
nodeIntegration: true,
contextIsolation: false,
spellcheck: false,
devTools: devTools,
},
});
// 加载页面
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
settingWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL + "#/setting");
} else {
createProtocol("app");
// Load the index.html when not in development
settingWindow.loadURL("app://./index.html#setting");
}
// 加载完webContents后再显示窗口
settingWindow.webContents.on("did-finish-load", function () {
// 显示窗口
settingWindow.show();
});
// 禁用标题栏右键
settingWindow.hookWindowMessage(278, function (e) {
// 窗口禁用
settingWindow.setEnabled(false);
// 延时太快会立刻启动,太慢会妨碍窗口其他操作,可自行测试最佳时间
setTimeout(() => {
settingWindow.setEnabled(true);
}, 100);
return true;
});
}
/**
* 创建搜索窗口
* @returns {Promise<void>}
*/
async function createSearchWindow() {
// 是否可以显示
global.searchWindowShow = false;
if (searchWindow != null && !searchWindow.isDestroyed()) {
searchWindow.destroy();
}
// 浏览器开发者工具
let devTools;
// 环境判断
if (process.env.NODE_ENV !== "production") {
// 开发
devTools = true;
} else {
// 正式
devTools = false;
}
// 窗口
global.searchWindow = searchWindow = new BrowserWindow({
width: 600,
height: 44,
type: "toolbar",
frame: false,
show: false,
maximizable: false,
minimizable: false,
fullscreenable: false,
resizable: false,
alwaysOnTop: true,
backgroundColor: global.setting.appearance.theme.mainBackground.replace("bg-[", "").replace("]", ""),
webPreferences: {
backgroundThrottling: false,
nodeIntegration: true,
contextIsolation: false,
spellcheck: false,
devTools: devTools,
},
});
// 加载页面
if (process.env.WEBPACK_DEV_SERVER_URL) {
// Load the url of the dev server if in development mode
searchWindow.loadURL(process.env.WEBPACK_DEV_SERVER_URL + "#/searchWindow");
} else {
createProtocol("app");
// Load the index.html when not in development
searchWindow.loadURL("app://./index.html#searchWindow");
}
// 加载完webContents后再显示窗口
searchWindow.webContents.on("did-finish-load", function () {
// 恢复上一次的位置
let bounds = cacheData.cacheStore.get("searchWindowBounds");
if (bounds != null) {
searchWindow.setBounds({ x: bounds.x, y: bounds.y });
}
// 设置可以显示
global.searchWindowShow = true;
});
// 隐藏窗口
searchWindow.on("hide", () => {
searchWindow.setBounds({ height: 44 });
searchWindow.webContents.send("hideSearchWindowOperation");
});
// 显示窗口
searchWindow.on("show", () => {
searchWindow.setBackgroundColor(global.setting.appearance.theme.mainBackground.replace("bg-[", "").replace("]", ""));
// 显示窗口时将输入法切换为英文模式
if (global.setting.general.switchEnglish) {
global.api.SwitchEnglish(searchWindow.getNativeWindowHandle());
}
});
// 窗口移动完毕
searchWindow.on("moved", () => {
// 记录位置
cacheData.cacheStore.set("searchWindowBounds", searchWindow.getBounds());
});
// 失去焦点
searchWindow.on("blur", () => {
if (searchWindow.isVisible() && global.setting.quickSearch.hideLosingFocus) {
global.searchWindow.webContents.send("hideSearchWindowBefore");
}
});
// 禁用标题栏右键
searchWindow.hookWindowMessage(278, function (e) {
// 窗口禁用
searchWindow.setEnabled(false);
// 延时太快会立刻启动,太慢会妨碍窗口其他操作,可自行测试最佳时间
setTimeout(() => {
searchWindow.setEnabled(true);
}, 100);
return true;
});
}
/**
* 自动隐藏
* @param data
* @param size
* @param timer
*/
function autoHide(data, size, timer) {
if (global.mainWindow.isDestroyed()) {
return;
}
try {
let displays = util.getWindowInScreen();
if (displays.length > 1 || displays.length == 0) {
return;
}
let workArea = displays[0].workArea;
let scaleFactor = displays[0].scaleFactor;
let bounds = mainWindow.getBounds();
if (mainWindow.isVisible()) {
let flag = false;
if (bounds.x + bounds.width >= workArea.x + workArea.width) {
// 右侧
flag = data.x <= bounds.x * scaleFactor - size || data.y <= bounds.y * scaleFactor - size || data.y >= (bounds.y + bounds.height) * scaleFactor + size;
} else if (bounds.x == workArea.x) {
// 左侧
flag =
data.x > (bounds.x + bounds.width) * scaleFactor + size ||
data.y <= bounds.y * scaleFactor - size ||
data.y >= (bounds.y + bounds.height) * scaleFactor + size;
} else if (bounds.y + bounds.height >= workArea.y + workArea.height) {
// 底部
flag = data.y < bounds.y * scaleFactor - size || data.x <= bounds.x * scaleFactor - size || data.x >= (bounds.x + bounds.width) * scaleFactor + size;
} else if (bounds.y == workArea.y) {
// 顶部
flag =
data.y > (bounds.y + bounds.height) * scaleFactor + size ||
data.x <= bounds.x * scaleFactor - size ||
data.x >= (bounds.x + bounds.width) * scaleFactor + size;
}
if (flag) {
if (global.menuShow != null && global.menuShow) {
return;
}
if (timer && global.setting.general.delayHidingMS > 0 && global.autoHideTimer == null) {
global.autoHideTimer = setTimeout(function () {
// 隐藏
// mainWindow.hide();
global.mainWindow.webContents.send("hideMainWindowBefore");
}, global.setting.general.delayHidingMS);
} else if (global.setting.general.delayHidingMS == 0 || !timer) {
// 隐藏
// mainWindow.hide();
global.mainWindow.webContents.send("hideMainWindowBefore");
}
} else {
clearTimeout(global.autoHideTimer);
global.autoHideTimer = null;
}
} else {
if (global.direction != "none") {
let flag = false;
let x = bounds.x * scaleFactor;
let y = bounds.y * scaleFactor;
let windowWidthPosition = (bounds.x + bounds.width) * scaleFactor;
let windowHeightPosition = (bounds.y + bounds.height) * scaleFactor;
if (global.direction == "right" && data.x >= windowWidthPosition - 1 && data.y >= y && data.y <= windowHeightPosition) {
// 右侧
flag = true;
} else if (global.direction == "left" && data.x <= workArea.x && data.y >= y && data.y <= windowHeightPosition) {
// 左侧
flag = true;
} else if (global.direction == "bottom" && data.y >= windowHeightPosition - 1 && data.x >= x && data.x <= windowWidthPosition) {
// 底部
flag = true;
} else if (global.direction == "top" && data.y <= workArea.y && data.x >= x && data.x <= windowWidthPosition) {
// 顶部
flag = true;
}
if (flag) {
if (util.notDisturb()) {
return;
}
if (timer && global.setting.general.delayDisplayMS > 0 && global.autoHideTimer == null) {
global.autoHideTimer = setTimeout(function () {
// 显示
mainWindow.show();
if (!global.setting.general.alwaysTop) {
global.mainWindow.setAlwaysOnTop(true, "screen-saver");
global.mainWindow.setAlwaysOnTop(false);
}
}, global.setting.general.delayDisplayMS);
} else if (global.setting.general.delayDisplayMS == 0 || !timer) {
// 显示
mainWindow.show();
if (!global.setting.general.alwaysTop) {
global.mainWindow.setAlwaysOnTop(true, "screen-saver");
global.mainWindow.setAlwaysOnTop(false);
}
}
} else {
clearTimeout(global.autoHideTimer);
global.autoHideTimer = null;
}
}
}
} catch (e) {
if (process.env.NODE_ENV !== "production") {
console.log(e);
}
global.mainWindow.setBounds({ x: 1, y: 1 });
}
}
/**
* 显示设置窗口
*/
function showSettingWindow() {
if (settingWindow != null && !settingWindow.isDestroyed()) {
if (!settingWindow.isVisible()) {
settingWindow.show();
}
settingWindow.focus();
} else {
createSettingWindow();
}
}
app.whenReady().then(() => {
// 禁用debugtron
for (let i = 0; i < process.argv.length; i++) {
const arg = process.argv[i];
if (arg.indexOf("--inspect") !== -1 || arg.indexOf("--remote-debugging-port") !== -1) {
dialog.showMessageBoxSync(global.mainWindow, {
title: "Dawn Launcher",
message: "达咩呦达咩达咩~",
buttons: ["确定"],
type: "error",
noLink: true,
});
app.quit();
return;
}
}
// 禁止多开
const instanceLock = app.requestSingleInstanceLock();
if (!instanceLock) {
app.quit();
return;
}
// 设置快捷键
let setting = settingData.get();
settingIndex.setShortcutKey(setting);
// 引用c++
try {
global.api = require("bindings")({
bindings: "api.node",
module_root: process.env.NODE_ENV !== "production" ? path.resolve(".") : path.dirname(process.execPath),
});
} catch (e) {
dialog.showMessageBoxSync(global.mainWindow, {
title: "Dawn Launcher",
message: "缺少DLL文件请重新下载安装包安装后运行。",
buttons: ["确定"],
type: "error",
noLink: true,
});
app.quit();
return;
}
// 创建窗口
createWindow(true);
});
app.on("second-instance", (event, commandLine, workingDirectory) => {
if (mainWindow) {
if (!mainWindow.isVisible()) {
mainWindow.show();
mainWindow.focus();
global.blurHide = true;
} else {
mainWindow.focus();
}
}
});
app.on("window-all-closed", () => {
// 释放鼠标监听
global.api.disableMouseMove();
if (process.platform !== "darwin") {
app.quit();
}
});
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow(false).then((r) => {});
}
});
if (process.env.NODE_ENV === "production") {
// 开机启动
const exeName = path.basename(process.execPath);
ipcMain.on("setAutoLaunch", (event, args) => {
app.setLoginItemSettings({
openAtLogin: args,
openAsHidden: false,
path: process.execPath,
args: ["--processStart", `"${exeName}"`],
});
});
}
// 设置当前语言
ipcMain.on("setCurrentLanguage", (event, args) => {
let currentLanguage = JSON.parse(args);
global.currentLanguage = currentLanguage;
// 托盘菜单
setTray(!global.setting.general.hideTray);
});
/**
* 托盘
*/
function setTray(show) {
// 获取语言
if (show) {
if (global.tray == null) {
// 环境判断
if (process.env.NODE_ENV !== "production") {
// 开发
global.tray = new Tray("./public/images/logo-thick.ico");
} else {
// 正式
global.tray = new Tray(path.join(__dirname, "./images/logo-thick.ico"));
}
}
let contextMenu = Menu.buildFromTemplate([
{
label: global.currentLanguage.displayMainInterface,
click: function () {
mainWindow.show();
global.blurHide = true;
},
},
{
label: global.currentLanguage.setting,
click: function () {
showSettingWindow();
},
},
{
// 点击退出菜单退出程序
label: global.currentLanguage.exit,
click: function () {
mainWindow.close();
},
},
]);
// 托盘
global.tray.setToolTip("Dawn Launcher");
global.tray.setContextMenu(contextMenu);
// 点击托盘图标,显示主窗口
global.tray.on("click", () => {
mainWindow.show();
global.blurHide = true;
});
} else {
// 隐藏托盘
if (global.tray != null) {
if (!global.tray.isDestroyed()) {
global.tray.destroy();
global.tray = null;
}
}
}
}
// 创建搜索窗口
ipcMain.on("createSearchWindow", () => {
// 搜索框
createSearchWindow();
});
// 托盘
ipcMain.on("setTray", (event, args) => {
setTray(args);
});
// 开启快捷搜索
ipcMain.on("setEnableQuickSearch", (event, args) => {
// 设置快捷键
let setting = settingData.get();
setting.quickSearch.enable = args;
settingIndex.setShortcutKey(setting);
});
// 设置背景透明度
ipcMain.on("setBackgroundTransparency", (event, args) => {
if ((Number(args) == 1 && global.backgroundTransparency) || (Number(args) != 1 && !global.backgroundTransparency)) {
mainWindow.destroy();
global.mainWindow = mainWindow = null;
createWindow(false);
}
});
// 创建设置窗口
ipcMain.on("createSettingWindow", (event, args) => {
showSettingWindow();
});
// 显示搜索窗口
ipcMain.on("searchWindowShow", (event, args) => {
if (args != null) {
searchWindow.setBounds({ height: args });
}
searchWindow.show();
});

70
src/main/mouse.js Normal file
View File

@ -0,0 +1,70 @@
"use strict";
const { EventEmitter } = require("events");
let paused = true;
class MouseEvents extends EventEmitter {
constructor() {
super();
if (require("os").platform() !== "win32") return;
let createdListener = false;
let registeredEvents = [];
this.on("newListener", (event) => {
if (registeredEvents.indexOf(event) !== -1) return;
// Enable WM_MOUSEMOVE capture if requested
if (event === "mousemove") {
global.api.enableMouseMove();
}
if ((event === "mouseup" || event === "mousedown" || event === "mousemove" || event === "mousewheel") && !createdListener) {
// Careful: this currently "leaks" a thread every time it's called.
// We should probably get around to fixing that.
createdListener = global.api.createMouseHook((event, x, y, button, delta) => {
const payload = { x, y };
if (event === "mousewheel") {
payload.delta = FromInt32(delta) / 120;
payload.axis = button;
} else if (event === "mousedown" || event === "mouseup") {
payload.button = button;
}
this.emit(event, payload);
});
if (createdListener) {
this.resumeMouseEvents();
}
} else {
return;
}
registeredEvents.push(event);
});
this.on("removeListener", (event) => {
if (this.listenerCount(event) > 0) return;
registeredEvents = registeredEvents.filter((x) => x !== event);
if (event === "mousemove") {
global.api.disableMouseMove();
}
});
}
getPaused() {
return paused;
}
pauseMouseEvents() {
if (paused) return false;
paused = true;
return global.api.pauseMouseEvents();
}
resumeMouseEvents() {
if (!paused) return false;
paused = false;
return global.api.resumeMouseEvents();
}
}
function FromInt32(x) {
var uint32 = x - Math.floor(x / 4294967296) * 4294967296;
if (uint32 >= 2147483648) {
return (uint32 - 4294967296) / 65536;
} else {
return uint32 / 65536;
}
}
module.exports = new MouseEvents();

516
src/main/setting/data.js Normal file
View File

@ -0,0 +1,516 @@
const Store = require("electron-store");
const settingStore = new Store({ name: "setting", encryptionKey: "732b6562-c2dd-56c7-8fe9-ef3ed6646128", clearInvalidConfig: true });
const store = new Store({ name: "data", encryptionKey: "0b52eb58-4c0f-5ff1-b062-031546a8d269", clearInvalidConfig: true });
module.exports = {
initData() {
let setting = store.get("setting");
if (setting == null) {
let ss = settingStore.get("setting");
if (ss == null) {
setting = {
general: {
// 开机启动
startup: false,
// 启动后最小化到系统托盘
startupTray: false,
// 显示/隐藏快捷键
showHideShortcutKey: null,
// 语言
language: "chinese",
// 永远置顶
alwaysTop: false,
// 停靠在桌面边缘时自动隐藏
edgeAutoHide: true,
// 锁定尺寸
lockSize: false,
// 失去焦点后隐藏
hideLosingFocus: false,
// 隐藏托盘图标
hideTray: false,
// 中间单击
showHideMouseWheelClick: false,
// 固定位置
fixedPosition: false,
// 永远居中
alwaysCenter: false,
// 显示时跟随鼠标位置
showFollowMousePosition: false,
// 隐藏任务栏
hideTaskbar: false,
// 勿扰模式
notDisturb: false,
// 双击任务栏
doubleClickTaskbar: false,
// 延迟显示(毫秒)
delayDisplayMS: 0,
// 延迟隐藏(毫秒)
delayHidingMS: 0,
// 显示窗口时将输入法切换为英文模式
switchEnglish: false,
},
appearance: {
// 主题
theme: {
name: "#FFFFFF",
fontBasic: "#505050",
fontHover: "#FFFFFF",
mainBackground: "#FFFFFF",
minorBackground: "#999999",
border: "#F0F0F0",
},
// 窗口透明度
transparency: 1,
// 背景透明度
backgroundTransparency: 1,
// 使用字体阴影
useFontShadow: false,
// 文字阴影
fontShadow: "#000000",
// 背景图
backgroundImage: null,
// 背景图模式
backgroundImageMode: "repeat",
// 背景图定位
backgroundImagePosition: "default",
// 背景图透明度
backgroundImageTransparency: 1,
// 窗口圆角
windowRoundedCorners: false,
// 标题
title: "Dawn Launcher",
},
classification: {
// 宽度
width: 140,
// 布局
layout: "left",
// 鼠标悬停切换
mouseHover: false,
// 悬停毫秒
mouseHoverMS: 1000,
// 滚轮切换
mouseWheel: false,
// 记住选择状态
rememberSelectionState: false,
// 名称对齐
nameAlign: "left",
// 模式
mode: "normal",
// 项目列表滚动到底部或顶部时自动切换分类
autoSwitchClassification: false,
// 隐藏窗口时折叠子分类
hideWindowFoldChildClassification: false,
// 切换分类时收起其他子分类
switchClassificationCollapseOtherSubClassification: false,
},
item: {
// 布局
layout: "tile",
// 图标大小
iconSize: 40,
// 搜索快捷键
searchShortcutKey: "TAB",
// 双击运行项目
doubleClickRunItem: false,
// 打开后隐藏主界面
openAfterHideMainInterface: false,
// 从程序外拖动文件到项目图标上时用此项目打开文件
useItemOpen: false,
// 记录打开次数
openNumber: false,
// 隐藏项目名称
hideItemName: false,
// 隐藏省略号
hideEllipsis: false,
// 项目名称行数
itemNameRowCount: 2,
// 宽度
width: 100,
// 列数 单列多列
columnNumber: 1,
// 检测无效项目
checkInvalidItem: true,
// 字体大小
fontSize: 14,
// 字体粗细
fontWeight: 400,
// 字体行高
fontLineHeight: 1.25,
},
quickSearch: {
// 开启
enable: true,
// 快捷键
showHideShortcutKey: "Alt + Enter",
// 打开快捷键
openShortcutKey: "none",
// 失去焦点后隐藏
hideLosingFocus: false,
// 仅剩一项时立即打开
openNow: false,
// 显示历史记录
showHistory: false,
// 历史记录排序
showHistorySort: "lastOpen",
// 从程序外拖动文件到项目图标上时用此项目打开文件
useItemOpen: false,
// 打开后隐藏快速搜索窗口
openAfterHideQuickSearchWindow: true,
// 匹配条件
matchingConditionsRemark: false,
},
webSearch: {
// 模式
mode: 0,
// 搜索源
searchSourceList: [
{
id: 1,
keyword: "g",
name: "Google",
URL: "https://www.google.com/search?q={w}",
},
{
id: 2,
keyword: "b",
name: "Baidu",
URL: "https://www.baidu.com/s?wd={w}",
},
{
id: 3,
keyword: "bing",
name: "Bing",
URL: "https://cn.bing.com/search?q={w}",
},
{
id: 4,
keyword: "so",
name: "360",
URL: "https://www.so.com/s?q={w}",
},
{
id: 5,
keyword: "sogou",
name: "Sogou",
URL: "https://www.sogou.com/web?query={w}",
},
],
},
network: {
// 使用代理
useProxy: false,
// 代理信息
proxy: {
// address
address: null,
// 用户名
username: null,
// 密码
password: null,
},
},
// 子分类
subClassification: {
// 名称字体(项目区域)
itemAreaNameFontSize: 14,
// 名称粗细(项目区域)
itemAreaNameFontWeight: 700,
// 名称字体行高(项目区域)
itemAreaNameFontLineHeight: 1.25,
},
};
} else {
setting = ss;
}
}
if (setting.general.language == null) {
setting.general.language = "chinese";
}
if (setting.general.alwaysTop == null) {
setting.general.alwaysTop = false;
}
if (setting.general.fixedPosition == null) {
setting.general.fixedPosition = false;
}
if (setting.general.alwaysCenter == null) {
setting.general.alwaysCenter = false;
}
if (setting.general.showFollowMousePosition == null) {
setting.general.showFollowMousePosition = false;
}
if (setting.general.edgeAutoHide == null) {
setting.general.edgeAutoHide = true;
}
if (setting.general.startupTray == null) {
setting.general.startupTray = false;
}
if (setting.general.lockSize == null) {
setting.general.lockSize = false;
}
if (setting.general.hideLosingFocus == null) {
setting.general.hideLosingFocus = false;
}
if (setting.general.hideTray == null) {
setting.general.hideTray = false;
}
if (setting.general.showHideMouseWheelClick == null) {
setting.general.showHideMouseWheelClick = false;
}
if (setting.general.hideTaskbar == null) {
setting.general.hideTaskbar = false;
}
if (setting.general.notDisturb == null) {
setting.general.notDisturb = false;
}
if (setting.general.doubleClickTaskbar == null) {
setting.general.doubleClickTaskbar = false;
}
if (setting.general.delayDisplayMS == null) {
setting.general.delayDisplayMS = 0;
}
if (setting.general.delayHidingMS == null) {
setting.general.delayHidingMS = 0;
}
if (setting.general.switchEnglish == null) {
setting.general.switchEnglish = false;
}
if (setting.item.layout == null) {
setting.item.layout = "tile";
}
if (setting.item.iconSize == null) {
setting.item.iconSize = 40;
}
if (setting.item.openAfterHideMainInterface == null) {
setting.item.openAfterHideMainInterface = false;
}
if (setting.item.useItemOpen == null) {
setting.item.useItemOpen = false;
}
if (setting.item.openNumber == null) {
setting.item.openNumber = false;
}
if (setting.item.hideItemName == null) {
setting.item.hideItemName = false;
}
if (setting.item.hideEllipsis == null) {
setting.item.hideEllipsis = false;
}
if (setting.item.itemNameRowCount == null) {
setting.item.itemNameRowCount = 2;
}
if (setting.item.width == null) {
setting.item.width = 100;
}
if (setting.item.columnNumber == null) {
setting.item.columnNumber = 1;
} else if (setting.item.columnNumber != null && setting.item.columnNumber == "single") {
setting.item.columnNumber = 1;
} else if (setting.item.columnNumber != null && setting.item.columnNumber == "multiple") {
setting.item.columnNumber = 2;
}
if (setting.item.checkInvalidItem == null) {
setting.item.checkInvalidItem = true;
}
if (setting.item.fontSize == null) {
setting.item.fontSize = 14;
}
if (setting.item.fontWeight == null) {
setting.item.fontWeight = 400;
}
if (setting.item.fontLineHeight == null) {
setting.item.fontLineHeight = 1.25;
}
if (setting.classification == null) {
setting.classification = {};
}
if (setting.classification.width == null) {
setting.classification.width = 140;
}
if (setting.classification.layout == null) {
setting.classification.layout = "left";
}
if (setting.classification.mouseHover == null) {
setting.classification.mouseHover = false;
}
if (setting.classification.mouseHoverMS == null) {
setting.classification.mouseHoverMS = 1000;
}
if (setting.classification.mouseWheel == null) {
setting.classification.mouseWheel = false;
}
if (setting.classification.rememberSelectionState == null) {
setting.classification.rememberSelectionState = false;
}
if (setting.classification.nameAlign == null) {
setting.classification.nameAlign = "left";
}
if (setting.classification.mode == null) {
setting.classification.mode = "normal";
}
if (setting.classification.autoSwitchClassification == null) {
setting.classification.autoSwitchClassification = false;
}
if (setting.classification.hideWindowFoldChildClassification == null) {
setting.classification.hideWindowFoldChildClassification = false;
}
if (setting.classification.switchClassificationCollapseOtherSubClassification == null) {
setting.classification.switchClassificationCollapseOtherSubClassification = false;
}
if (setting.appearance.theme.name.toUpperCase() == "#FFFFFF") {
setting.appearance.theme = {
name: "#FFFFFF",
fontBasic: "#505050",
fontHover: "#FFFFFF",
mainBackground: "#FFFFFF",
minorBackground: "#999999",
border: "#F0F0F0",
};
}
if (setting.appearance.theme.name.toUpperCase() == "#2B2B2B") {
setting.appearance.theme = {
name: "#2B2B2B",
fontBasic: "#BBBBBB",
fontHover: "#BBBBBB",
mainBackground: "#2B2B2B",
minorBackground: "#3C3F41",
border: "#3C3F41",
};
}
if (setting.appearance.transparency == null) {
setting.appearance.transparency = 1;
}
if (setting.appearance.backgroundTransparency == null) {
setting.appearance.backgroundTransparency = 1;
}
if (setting.appearance.useFontShadow == null) {
setting.appearance.useFontShadow = false;
}
if (setting.appearance.fontShadow == null) {
setting.appearance.fontShadow = "#000000";
}
if (setting.appearance.backgroundImageMode == null) {
setting.appearance.backgroundImageMode = "repeat";
}
if (setting.appearance.backgroundImagePosition == null) {
setting.appearance.backgroundImagePosition = "default";
}
if (setting.appearance.backgroundImageTransparency == null) {
setting.appearance.backgroundImageTransparency = 1;
}
if (setting.appearance.windowRoundedCorners == null) {
setting.appearance.windowRoundedCorners = false;
}
if (setting.appearance.title == null) {
setting.appearance.title = "Dawn Launcher";
}
if (setting.quickSearch == null) {
setting.quickSearch = {};
}
if (setting.quickSearch.enable == null) {
setting.quickSearch.enable = true;
}
if (setting.quickSearch.showHideShortcutKey == null) {
setting.quickSearch.showHideShortcutKey = "Alt + Enter";
}
if (setting.quickSearch.openShortcutKey == null) {
setting.quickSearch.openShortcutKey = "none";
}
if (setting.quickSearch.hideLosingFocus == null) {
setting.quickSearch.hideLosingFocus = false;
}
if (setting.quickSearch.openNow == null) {
setting.quickSearch.openNow = false;
}
if (setting.quickSearch.showHistory == null) {
setting.quickSearch.showHistory = false;
}
if (setting.quickSearch.showHistorySort == null) {
setting.quickSearch.showHistorySort = "lastOpen";
}
if (setting.quickSearch.useItemOpen == null) {
setting.quickSearch.useItemOpen = false;
}
if (setting.quickSearch.openAfterHideQuickSearchWindow == null) {
setting.quickSearch.openAfterHideQuickSearchWindow = true;
}
if (setting.quickSearch.matchingConditionsRemark == null) {
setting.quickSearch.matchingConditionsRemark = false;
}
if (setting.webSearch == null) {
setting.webSearch = {};
}
if (setting.webSearch.mode == null) {
setting.webSearch.mode = 0;
}
if (setting.webSearch.searchSourceList == null || setting.webSearch.searchSourceList.length == 0) {
setting.webSearch.searchSourceList = [
{
id: 1,
keyword: "g",
name: "Google",
URL: "https://www.google.com/search?q={w}",
},
{
id: 2,
keyword: "b",
name: "Baidu",
URL: "https://www.baidu.com/s?wd={w}",
},
{
id: 3,
keyword: "bing",
name: "Bing",
URL: "https://cn.bing.com/search?q={w}",
},
{
id: 4,
keyword: "so",
name: "360",
URL: "https://www.so.com/s?q={w}",
},
{
id: 5,
keyword: "sogou",
name: "Sogou",
URL: "https://www.sogou.com/web?query={w}",
},
];
}
if (setting.network == null) {
setting.network = {};
}
if (setting.network.useProxy == null) {
setting.network.useProxy = false;
}
if (setting.subClassification == null) {
setting.subClassification = {};
}
if (setting.subClassification.itemAreaNameFontSize == null) {
setting.subClassification.itemAreaNameFontSize = 14;
}
if (setting.subClassification.itemAreaNameFontWeight == null) {
setting.subClassification.itemAreaNameFontWeight = 700;
}
if (setting.subClassification.itemAreaNameFontLineHeight == null) {
setting.subClassification.itemAreaNameFontLineHeight = 1.25;
}
this.set(setting);
global.setting = setting;
},
/**
* get
*/
get() {
this.initData();
return store.get("setting");
},
/**
* set
* @param setting
*/
set(setting) {
store.set("setting", setting);
global.setting = setting;
},
};

149
src/main/setting/index.js Normal file
View File

@ -0,0 +1,149 @@
import { globalShortcut } from "electron";
import path from "path";
import util from "../util";
import itemJS from "../item/index";
import ClassificationJS from "../classification/index";
/**
* 设置快捷键
* @param setting
*/
function setShortcutKey(setting) {
// 取消所有快捷键
globalShortcut.unregisterAll();
if (setting != null) {
// 设置快捷键
if (setting.general != null) {
if (!util.strIsEmpty(setting.general.showHideShortcutKey)) {
globalShortcut.register(setting.general.showHideShortcutKey, () => {
if (util.notDisturb()) {
return;
}
if (global.mainWindow.isVisible()) {
// global.mainWindow.hide();
global.mainWindow.webContents.send("hideMainWindowBefore");
} else {
util.showFollowMousePosition();
global.mainWindow.show();
global.mainWindow.focus();
global.blurHide = true;
}
});
}
}
if (setting.quickSearch != null) {
if (setting.quickSearch.enable && !util.strIsEmpty(setting.quickSearch.showHideShortcutKey)) {
globalShortcut.register(setting.quickSearch.showHideShortcutKey, () => {
if (util.notDisturb()) {
return;
}
if (global.searchWindow != null && global.searchWindowShow) {
if (global.searchWindow.isVisible()) {
global.searchWindow.webContents.send("hideSearchWindowBefore");
} else {
let params = {
setting: global.setting,
list: global.list,
};
global.searchWindow.webContents.send("showSearchWindowOperation", JSON.stringify(params));
}
}
});
}
}
// 扫描有没有全局快捷键分类、项目
if (!util.arrayIsEmpty(global.list)) {
let itemList = [];
let classificationList = [];
for (let c of global.list) {
if (c.globalShortcutKey && !util.strIsEmpty(c.shortcutKey)) {
classificationList.push(c);
}
if (!util.arrayIsEmpty(c.childList)) {
for (let cc of c.childList) {
if (cc.globalShortcutKey && !util.strIsEmpty(cc.shortcutKey)) {
classificationList.push(cc);
}
if (!util.arrayIsEmpty(cc.itemList)) {
for (let item of cc.itemList) {
if (item.globalShortcutKey && !util.strIsEmpty(item.shortcutKey)) {
itemList.push(item);
}
}
}
}
} else {
if (!util.arrayIsEmpty(c.itemList)) {
for (let item of c.itemList) {
if (item.globalShortcutKey && !util.strIsEmpty(item.shortcutKey)) {
itemList.push(item);
}
}
}
}
}
// 设置快捷键
for (let item of itemList) {
globalShortcut.register(item.shortcutKey, () => {
if (util.notDisturb()) {
return;
}
if (item.type == 4) {
if (!util.arrayIsEmpty(item.itemList)) {
for (let iItem of item.itemList) {
itemJS.itemRun(iItem, null, null);
}
}
} else {
itemJS.itemRun(item, null, null);
}
});
}
for (let classification of classificationList) {
globalShortcut.register(classification.shortcutKey, () => {
if (util.notDisturb()) {
return;
}
if (global.mainWindow != null && !global.mainWindow.isDestroyed()) {
if (!global.mainWindow.isVisible()) {
global.mainWindow.show();
global.mainWindow.focus();
global.blurHide = true;
if (!global.setting.general.alwaysTop) {
global.mainWindow.setAlwaysOnTop(true, "screen-saver");
global.mainWindow.setAlwaysOnTop(false);
}
}
let { classificationParentId, classificationChildId } = ClassificationJS.convertClassificationId(classification.id, classification.parentId);
let params = {
classificationParentId: classificationParentId,
classificationChildId: classificationChildId,
};
global.mainWindow.webContents.send("changeClassification", JSON.stringify(params));
}
});
}
}
}
}
/**
* 获取数据目录配置文件地址
* @returns {string}
*/
function getDawnLauncherProfilePath() {
let p;
if (process.env.NODE_ENV !== "production") {
p = path.resolve(".");
} else {
p = path.dirname(process.execPath);
}
p = path.resolve(p, "..");
p = path.join(p, ".dawn_launcher_profile");
return p;
}
export default {
setShortcutKey,
getDawnLauncherProfilePath,
};

View File

@ -0,0 +1,162 @@
import { app, dialog, ipcMain } from "electron";
import data from "./data";
import index from "./index";
import util from "../util";
import fs from "fs";
import path from "path";
/**
* 判断路径是否一样
* @param currentAppDataPath
* @param profileAppDataPath
*/
function pathIsEqual(currentAppDataPath, profileAppDataPath) {
let c = path.normalize(currentAppDataPath);
let p = path.normalize(profileAppDataPath);
return c == p;
}
export default function () {
// 获取数据
ipcMain.on("getSetting", (event, args) => {
event.returnValue = data.get();
});
// set数据
ipcMain.on("setSetting", (event, args) => {
let params = JSON.parse(args);
// 保存数据
data.set(params.setting);
// 需要通知
if (params.other.main) {
if (global.mainWindow != null) {
global.mainWindow.webContents.send("mainWindowGetData");
}
}
if (params.other.search) {
if (global.searchWindow != null && !global.searchWindow.isDestroyed() && global.searchWindow.isVisible()) {
global.searchWindow.webContents.send("searchWindowGetData");
global.searchWindow.setBackgroundColor(global.setting.appearance.theme.mainBackground.replace("bg-[", "").replace("]", ""));
}
}
if (params.other.setting) {
if (global.settingWindow != null && !global.settingWindow.isDestroyed() && global.settingWindow.isVisible()) {
global.settingWindow.webContents.send("settingWindowGetData");
}
}
});
// 设置快捷键
ipcMain.on("setShortcutKey", (event, args) => {
let setting = JSON.parse(args);
index.setShortcutKey(setting);
});
// 设置启动后最小化到系统托盘
ipcMain.on("setStartupTray", (event, args) => {
if (args) {
util.edgeAdsorb();
}
});
// 获取数据目录
ipcMain.on("getAppDataPath", (event, args) => {
try {
// 获取数据目录配置文件地址
let p = index.getDawnLauncherProfilePath();
// 读取文件
let r = fs.readFileSync(p);
event.returnValue = r.toString();
} catch (e) {
event.returnValue = app.getPath("appData");
}
});
// 选择用户数据目录
ipcMain.on("chooseDataDirectory", (event, args) => {
dialog.showOpenDialog(global.settingWindow, { properties: ["openDirectory"] }).then((r) => {
if (r.filePaths.length > 0) {
event.returnValue = r.filePaths[0];
} else {
event.returnValue = null;
}
});
});
// 提示切换数据目录
ipcMain.on("promptChangeDataDirectory", (event, args) => {
dialog
.showMessageBox(global.mainWindow, {
message: global.currentLanguage.modifyDataDirectoryMessage,
buttons: [global.currentLanguage.ok, global.currentLanguage.cancel],
type: "question",
noLink: true,
cancelId: 1,
})
.then((r) => {
if (r.response == 0) {
// 获取数据目录配置文件地址
let p = index.getDawnLauncherProfilePath();
if (util.strIsEmpty(args)) {
try {
let profile = fs.readFileSync(p);
// 路径不一样的话,开始移动和删除文件
if (!pathIsEqual(global.defaultAppDataPath, profile.toString())) {
try {
// 删除默认路径下的文件
fs.rmdirSync(global.defaultAppDataPath + "\\Dawn Launcher");
} catch (e) {}
try {
// 将现在文件夹的内容移动到默认路径下
fs.cpSync(profile.toString() + "\\Dawn Launcher", global.defaultAppDataPath + "\\Dawn Launcher", { recursive: true });
} catch (e) {}
}
// 删除配置数据目录文件
try {
fs.rmSync(p);
} catch (e) {}
} catch (e) {
if (process.env.NODE_ENV !== "production") {
console.log(e);
}
}
} else {
try {
// 写入内容
fs.writeFileSync(p, args);
try {
// 删除写入内容文件夹
fs.rmdirSync(args + "\\Dawn Launcher");
} catch (e) {}
try {
// 将现有文件夹内容移动到写入文件夹内容
fs.cpSync(app.getPath("appData") + "\\Dawn Launcher", args + "\\Dawn Launcher", { recursive: true });
} catch (e) {}
} catch (e) {}
}
app.relaunch();
app.exit();
}
});
});
// 拷贝背景图
ipcMain.on("copyBackgroundImage", (event, args) => {
let parsedPath = path.parse(args);
let destPath = app.getPath("userData") + "\\images";
let name = "backgroundImage" + parsedPath.ext;
try {
fs.statSync(destPath);
} catch (e) {
fs.mkdirSync(destPath);
}
try {
fs.copyFileSync(args, destPath + "\\" + name);
event.returnValue = name;
} catch (e) {
if (process.env.NODE_ENV !== "production") {
console.log(e);
}
event.returnValue = null;
}
});
// 关闭设置窗口
ipcMain.on("closeSettingWindow", (event, args) => {
if (global.settingWindow != null && !global.settingWindow.isDestroyed()) {
global.settingWindow.close();
}
});
}

297
src/main/util.js Normal file
View File

@ -0,0 +1,297 @@
import { screen, nativeImage, nativeTheme, app, dialog, shell } from "electron";
import request from "request";
import retry from "retry";
import cacheData from "./cache/data";
/**
* 边缘吸附
*/
function edgeAdsorb(display) {
if (global.mainWindow.isDestroyed()) {
return;
}
try {
global.direction = "none";
let displays = display != null ? [display] : this.getWindowInScreen();
if (displays.length > 1 || displays.length == 0) {
return;
}
let workArea = displays[0].workArea;
let bounds = global.mainWindow.getBounds();
if (bounds.x + bounds.width >= workArea.x + workArea.width) {
// 右侧
global.mainWindow.setBounds({ x: workArea.x + workArea.width - bounds.width });
global.direction = "right";
global.blurHide = null;
} else if (bounds.x <= workArea.x) {
// 左侧
global.mainWindow.setBounds({ x: workArea.x });
global.direction = "left";
global.blurHide = null;
}
if (bounds.y + bounds.height >= workArea.y + workArea.height) {
// 底部
global.mainWindow.setBounds({ y: workArea.y + workArea.height - bounds.height });
global.direction = "bottom";
global.blurHide = null;
} else if (bounds.y <= workArea.y) {
// 顶部
global.mainWindow.setBounds({ y: workArea.y });
global.direction = "top";
global.blurHide = null;
}
} catch (e) {
if (process.env.NODE_ENV !== "production") {
console.log(e);
}
global.mainWindow.setBounds({ x: 1, y: 1 });
}
}
/**
* 获取图标点
* @returns {Electron.NativeImage}
*/
function getDot() {
return nativeImage.createFromDataURL(
!nativeTheme.shouldUseDarkColors
? "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAERJREFUOE9jZKAQMFKon2HUAAa8YWDDwMBQCQ3kdgYGhiPYAhxfIG5lYGDwgmraxsDA4E13Ayj2AlFpbDQh4U9IRAUiAEXYCBFBtkaAAAAAAElFTkSuQmCC"
: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAEtJREFUOE9jZKAQMFKon2HUAAbcYfD//38bBgaGSmggtzMyMh7BFuA4A/H///9bGRgYvKCatjEyMnrT3QDKvEBsAhtNSHgSErGBCAD0gBQREV/HsgAAAABJRU5ErkJggg=="
);
}
/**
* 显示时跟随鼠标位置
*/
function showFollowMousePosition() {
if (!global.setting.general.alwaysCenter && !global.setting.general.fixedPosition && global.setting.general.showFollowMousePosition) {
// 获取鼠标位置
let point = screen.getCursorScreenPoint();
// 获取窗口大小
let bounds = global.mainWindow.getBounds();
let x = Math.round(bounds.width / 2);
let y = Math.round(bounds.height / 2);
global.mainWindow.setPosition(point.x - x, point.y - y)
for (let i = 0; i < 10; i++) {
global.mainWindow.setSize(bounds.width, bounds.height)
}
// 获取当前鼠标所在屏幕
let display = screen.getDisplayNearestPoint(point);
// 边缘吸附
edgeAdsorb(display);
}
}
/**
* 获取窗口所在的屏幕
*/
function getWindowInScreen() {
let inDisplays = [];
let displays = screen.getAllDisplays();
let bounds = global.mainWindow.getBounds();
for (let display of displays) {
let workArea = display.workArea;
if (
((workArea.x <= bounds.x && workArea.x + workArea.width >= bounds.x) ||
(workArea.x <= bounds.x + bounds.width && workArea.x + workArea.width >= bounds.x + bounds.width)) &&
((workArea.y <= bounds.y && workArea.y + workArea.height >= bounds.y) ||
(workArea.y <= bounds.y + bounds.height && workArea.y + workArea.height >= bounds.y + bounds.height))
) {
inDisplays.push(display);
}
}
return inDisplays;
}
/**
* 去掉后缀
* @param name
*/
function removeSuffix(name) {
if (name != null && name.trim() != "") {
let arr = name.split(".");
if (arr.length > 1) {
let n = name.substring(0, name.lastIndexOf("."));
if (n.trim() != "") {
name = n;
}
}
}
return name;
}
/**
* 获取后缀
* @param name
*/
function getSuffix(name) {
let suffix = "";
if (name != null && name.trim() != "") {
let arr = name.split(".");
if (arr.length > 1) {
suffix = name.substring(name.lastIndexOf(".") + 1);
}
}
return suffix.toLowerCase();
}
/**
* 勿扰模式
*/
function notDisturb() {
return global.setting.general.notDisturb && global.api.IsFullscreen();
}
/**
* 检查更新
*/
function checkUpdate(type) {
try {
// 重试
const operation = retry.operation({
retries: 5, // 最多重试 5 次
factor: 1, // 每次重试之间的时间间隔加倍
minTimeout: 1000, // 第一次重试之前等待的时间
maxTimeout: 5000, // 最长等待时间
});
// 发起请求
operation.attempt((currentAttempt) => {
request(
{
uri: "https://dawnlauncher.com/version.json",
timeout: 5000,
},
function (error, response, body) {
if (operation.retry(error)) {
return;
}
if (!error && response.statusCode == 200) {
let buffer = Buffer.from(body);
let json = JSON.parse(buffer.toString());
if (json.version != app.getVersion()) {
if (type == "init") {
dialog
.showMessageBox(global.mainWindow, {
message: global.currentLanguage.checkForUpdatesNewVersionMessage,
buttons: [global.currentLanguage.ok, global.currentLanguage.cancel, global.currentLanguage.notPrompt],
type: "info",
noLink: true,
cancelId: 1,
})
.then((r) => {
if (r.response == 0) {
shell.openExternal("https://dawnlauncher.com/");
} else if (r.response == 2) {
cacheData.cacheStore.set("checkUpdate", false);
}
});
} else {
dialog
.showMessageBox(global.mainWindow, {
message: global.currentLanguage.checkForUpdatesNewVersionMessage,
buttons: [global.currentLanguage.ok, global.currentLanguage.cancel],
type: "info",
noLink: true,
cancelId: 1,
})
.then((r) => {
if (r.response == 0) {
shell.openExternal("https://dawnlauncher.com/");
}
});
}
} else {
if (type == "checkUpdate") {
dialog.showMessageBox(global.mainWindow, {
message: global.currentLanguage.checkForUpdatesLatestVersionMessage,
buttons: [global.currentLanguage.ok],
type: "info",
noLink: true,
});
}
}
} else {
if (type == "checkUpdate") {
dialog.showMessageBox(global.mainWindow, {
message: global.currentLanguage.checkForUpdatesFailedMessage,
buttons: [global.currentLanguage.ok],
type: "error",
noLink: true,
});
}
}
}
);
});
} catch (e) {
if (process.env.NODE_ENV !== "production") {
console.log(e);
}
}
}
/**
* 判断数组是否等于空
* @param arr
*/
function arrayIsEmpty(arr) {
if (arr == null || arr.length == 0) {
return true;
}
return false;
}
/**
* 判断字符串是否为空
* @param str
*/
function strIsEmpty(str) {
if (str == null || str.trim() == "") {
return true;
}
return false;
}
/**
* 菜单监听
* @param menu
*/
function menuListen(menu) {
menu.on("menu-will-show", () => {
global.menuShow = true;
});
menu.on("menu-will-close", () => {
global.menuShow = false;
});
}
/**
* 获取Key
* @param classificationParentId
* @param classificationChildId
* @param itemId
* @returns {*}
*/
function getKey(classificationParentId, classificationChildId, itemId) {
let key = classificationParentId;
if (classificationChildId != null) {
key += "-" + classificationChildId;
}
key += "-" + itemId;
return key;
}
export default {
edgeAdsorb,
getDot,
showFollowMousePosition,
getWindowInScreen,
removeSuffix,
notDisturb,
checkUpdate,
getSuffix,
arrayIsEmpty,
strIsEmpty,
menuListen,
getKey,
};

14
src/renderer/App.vue Normal file
View File

@ -0,0 +1,14 @@
<template>
<div id="app" class="cursor-default select-none">
<router-view />
</div>
</template>
<script>
export default {
name: "App",
created() {
document.title = "Dawn Launcher";
},
};
</script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,300 @@
import store from "../store";
import * as DOMPurify from "dompurify";
import ClassificationJS from "@/views/classification/js";
/**
* 校验KeyCode
* @param e
*/
function checkKeyCode(e) {
// (e.keyCode >= 48 && e.keyCode <= 57) 键盘上方数字
// (e.keyCode >= 65 && e.keyCode <= 90) 键盘字母a-z不区分大小写
// (e.keyCode >= 96 && e.keyCode <= 111) 数字键盘
// (e.keyCode >= 112 && e.keyCode <= 135) 键盘F1到F24
// e.keyCode == 8 退格键
// e.keyCode == 9 Tab
// e.keyCode == 12 Clear
// e.keyCode == 13 回车
// e.keyCode == 16 Shift
// e.keyCode == 17 Control
// e.keyCode == 18 Alt
// e.keyCode == 20 Cape Lock 大小写
// e.keyCode == 27 Esc
// (e.keyCode >= 32 && e.keyCode <= 40) 控制键盘区
// e.keyCode == 45 Insert
// e.keyCode == 46 Delete
// e.keyCode == 144 Num Lock
// (e.keyCode >= 186 && e.keyCode <= 192) 符号;: =+ ,< -_ .> /? `~
// (e.keyCode >= 219 && e.keyCode <= 222) 符号[{ \| ]} '"
if (
(e.keyCode >= 48 && e.keyCode <= 57) ||
(e.keyCode >= 65 && e.keyCode <= 90) ||
(e.keyCode >= 96 && e.keyCode <= 111) ||
(e.keyCode >= 112 && e.keyCode <= 135) ||
e.keyCode == 8 ||
e.keyCode == 9 ||
e.keyCode == 12 ||
e.keyCode == 13 ||
e.keyCode == 16 ||
e.keyCode == 17 ||
e.keyCode == 18 ||
e.keyCode == 20 ||
e.keyCode == 27 ||
(e.keyCode >= 32 && e.keyCode <= 40) ||
e.keyCode == 45 ||
e.keyCode == 46 ||
e.keyCode == 144 ||
(e.keyCode >= 186 && e.keyCode <= 192) ||
(e.keyCode >= 219 && e.keyCode <= 222)
) {
return true;
}
return false;
}
/**
* 获取Key
* @param classificationParentId
* @param classificationChildId
* @param itemId
* @returns {*}
*/
function getKey(classificationParentId, classificationChildId, itemId) {
let key = classificationParentId;
if (classificationChildId != null) {
key += "-" + classificationChildId;
}
key += "-" + itemId;
return key;
}
/**
* 获取图标
*/
function getIcon(classificationParentId, classificationChildId, itemId) {
if (store.state.iconDataMap != null) {
let icon = store.state.iconDataMap.get(getKey(classificationParentId, classificationChildId, itemId));
if (icon != null) {
return icon.icon;
} else {
return null;
}
}
return null;
}
export default {
/**
* 转int
* @param v
*/
parseInt(v) {
return v != null ? parseInt(v) : null;
},
/**
* 获取新ID
*/
getNewId(list) {
if (list == null || list.length == 0) {
return 1;
}
// 获取ID列表
let idList = list.map((item) => item.id);
// 获取最大ID
let maxId = Math.max(...idList);
// +1
return ++maxId;
},
/**
* 设置快捷键
*/
setShortcutKey(e, originalVal, preventDefault) {
if (preventDefault) {
e.preventDefault();
}
if (checkKeyCode(e)) {
// Esc
if (e.keyCode == 27) {
return originalVal;
}
if (e.keyCode == 8) {
return null;
} else {
let keys = [];
// 如果是组合键
if (e.ctrlKey && e.keyCode != 17) {
keys.push("Ctrl + ");
}
if (e.altKey && e.keyCode != 18) {
keys.push("Alt + ");
}
if (e.metaKey && e.keyCode != 91) {
keys.push("Win + ");
}
if (e.shiftKey && e.keyCode != 16) {
keys.push("Shift + ");
}
// 非组合键情况按键是ctrl alt shift win
if (e.keyCode == 16 || e.keyCode == 17 || e.keyCode == 18 || e.keyCode == 91) {
let key;
if (e.ctrlKey && e.keyCode == 17) {
key = "Ctrl + ";
}
if (e.altKey && e.keyCode == 18) {
key = "Alt + ";
}
if (e.metaKey && e.keyCode == 91) {
key = "Win + ";
}
if (e.shiftKey && e.keyCode == 16) {
key = "Shift + ";
}
keys.push(key);
} else {
// 排序
keys.sort((a, b) => a.localeCompare(b));
// 其他
if (e.key.toUpperCase() == "ENTER") {
keys.push("Enter");
} else {
if (e.keyCode == 32) {
keys.push("Space");
} else if (e.keyCode >= 96 && e.keyCode <= 105) {
keys.push("Num" + e.key);
} else if (e.keyCode >= 106 && e.keyCode <= 111) {
if (e.keyCode == 106) {
keys.push("NumMult");
} else if (e.keyCode == 107) {
keys.push("NumAdd");
} else if (e.keyCode == 109) {
keys.push("NumSub");
} else if (e.keyCode == 110) {
keys.push("NumDec");
} else if (e.keyCode == 111) {
keys.push("NumDiv");
}
} else {
keys.push(e.key.replace("Arrow", "").toUpperCase());
}
}
}
return keys.join("");
}
} else {
return null;
}
},
/**
* 校验快捷键完整
* @param shortcutKey
* @returns {boolean}
*/
checkShortcutKeys(shortcutKey) {
if (shortcutKey != null && shortcutKey.trim() != "") {
let flag = false;
let split = shortcutKey.split("+");
for (let s of split) {
if (s.trim() == "") {
return false;
}
if (s.trim() != "Ctrl" && s.trim() != "Alt" && s.trim() != "Shift" && s.trim() != "Win") {
flag = true;
}
}
return flag;
}
return false;
},
/**
* 校验应用程序内快捷是否重复
* @param shortcutKey
* @param appShortcutKeyMap
*/
checkAppShortcutKeysDuplicate(shortcutKey, appShortcutKeyMap) {
let s = appShortcutKeyMap.get(shortcutKey);
if (s != null) {
let name = s.classificationParentName;
if (s.type == "classification") {
if (s.classificationChildName != null && s.classificationChildName.trim() != "") {
name += "-" + s.classificationChildName;
}
return store.state.currentLanguage.shortcutKeyConflictMessage(name, 0);
} else {
if (s.classificationChildName != null && s.classificationChildName.trim() != "") {
name += "-" + s.classificationChildName;
}
name += "-" + s.itemName;
return store.state.currentLanguage.shortcutKeyConflictMessage(name, 1);
}
}
return null;
},
/**
* 校验设置中的快捷键是否重复
* @param shortcutKey
* @param setting
* @param exclude
* @returns {null}
*/
checkSettingShortcutKeysDuplicate(shortcutKey, setting, exclude) {
let msg = null;
let showHide, search, quickSearch;
if (setting.general != null && (exclude == null || exclude != "showHideShortcutKey")) {
showHide = setting.general.showHideShortcutKey;
}
if (setting.item != null && (exclude == null || exclude != "searchShortcutKey")) {
search = setting.item.searchShortcutKey;
}
if (setting.quickSearch != null && (exclude == null || exclude != "quickSearchShowHideShortcutKey")) {
quickSearch = setting.quickSearch.showHideShortcutKey;
}
if (showHide != null && showHide.trim() != "" && showHide == shortcutKey) {
msg = store.state.currentLanguage.shortcutKeyConflictSettingGeneralShowHideMessage;
} else if (search != null && search.trim() != "" && search == shortcutKey) {
msg = store.state.currentLanguage.shortcutKeyConflictSettingItemSearchMessage;
} else if (quickSearch != null && quickSearch.trim() != "" && quickSearch == shortcutKey) {
msg = store.state.currentLanguage.shortcutKeyConflictSettingQuickSearchShowHideMessage;
}
return msg;
},
/**
* 判断数组是否等于空
* @param arr
*/
arrayIsEmpty(arr) {
if (arr == null || arr.length == 0) {
return true;
}
return false;
},
/**
* 判断字符串是否为空
* @param str
*/
strIsEmpty(str) {
if (str == null || str.trim() == "") {
return true;
}
return false;
},
DOMPurify,
/**
* 获取图标
*/
getIcon,
/**
* 获取Key
* @param classificationParentId
* @param classificationChildId
* @param itemId
* @returns {*}
*/
getKey,
/**
* 获取图标根据分类
*/
getIconByClassification(item) {
let { classificationParentId, classificationChildId } = ClassificationJS.convertClassificationId(item.classificationId, item.classificationParentId);
return getIcon(classificationParentId, classificationChildId, item.id);
},
};

View File

@ -0,0 +1,71 @@
<template>
<span
class="flex items-center justify-center h-[28px] text-sm rounded button"
:style="{
backgroundColor: type == 'primary' ? $store.state.setting.appearance.theme.minorBackground : null,
color: type == 'primary' ? $store.state.setting.appearance.theme.fontHover : $store.state.setting.appearance.theme.fontBasic,
borderColor: type == 'cancel' ? $store.state.setting.appearance.theme.border : null,
}"
:class="[`${type == 'cancel' ? 'border' : ''}`]"
@mouseover="
$styleMouseover($event, 'button', type == 'primary' ? ['background-color'] : [], [$hexToRGBA($store.state.setting.appearance.theme.minorBackground, 0.8)])
"
@mouseout="$styleMouseout($event, 'button', ['background-color'], type == 'primary' ? [$store.state.setting.appearance.theme.minorBackground] : [])"
:title="text"
>
<svg v-if="icon != null && icon == 'folder'" class="w-[18px] h-[18px]" viewBox="0 0 24 24">
<path fill="currentColor" d="M9.17 6l2 2H20v10H4V6h5.17M10 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z" />
</svg>
<svg v-if="icon != null && icon == 'delete'" class="w-[18px] h-[18px]" viewBox="0 0 24 24">
<path fill="currentColor" d="M16 9v10H8V9h8m-1.5-6h-5l-1 1H5v2h14V4h-3.5l-1-1zM18 7H6v12c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7z" />
</svg>
<svg v-if="icon != null && icon == 'upload'" class="w-[18px] h-[18px]" viewBox="0 0 24 24">
<g><rect fill="none" height="18" width="18" /></g>
<g>
<path fill="currentColor" d="M18,15v3H6v-3H4v3c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2v-3H18z M7,9l1.41,1.41L11,7.83V16h2V7.83l2.59,2.58L17,9l-5-5L7,9z" />
</g>
</svg>
<svg v-if="icon != null && icon == 'link'" class="w-[18px] h-[18px]" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path
fill="currentColor"
d="M17 7h-4v2h4c1.65 0 3 1.35 3 3s-1.35 3-3 3h-4v2h4c2.76 0 5-2.24 5-5s-2.24-5-5-5zm-6 8H7c-1.65 0-3-1.35-3-3s1.35-3 3-3h4V7H7c-2.76 0-5 2.24-5 5s2.24 5 5 5h4v-2zm-3-4h8v2H8z"
/>
</svg>
<svg v-if="icon != null && icon == 'code'" class="w-[18px] h-[18px]" viewBox="0 0 24 24">
<path d="M0 0h24v24H0V0z" fill="none" />
<path fill="currentColor" d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z" />
</svg>
<svg v-if="icon != null && icon == 'reset'" class="w-[18px] h-[18px]" viewBox="0 0 24 24">
<g><path d="M0,0h24v24H0V0z" fill="none" /></g>
<g>
<g>
<path
fill="currentColor"
d="M6,13c0-1.65,0.67-3.15,1.76-4.24L6.34,7.34C4.9,8.79,4,10.79,4,13c0,4.08,3.05,7.44,7,7.93v-2.02 C8.17,18.43,6,15.97,6,13z M20,13c0-4.42-3.58-8-8-8c-0.06,0-0.12,0.01-0.18,0.01l1.09-1.09L11.5,2.5L8,6l3.5,3.5l1.41-1.41 l-1.08-1.08C11.89,7.01,11.95,7,12,7c3.31,0,6,2.69,6,6c0,2.97-2.17,5.43-5,5.91v2.02C16.95,20.44,20,17.08,20,13z"
/>
</g>
</g>
</svg>
<span v-else-if="icon == null">{{ text }}</span>
</span>
</template>
<script>
export default {
name: "Button",
props: {
text: {
type: String,
},
type: {
type: String,
},
icon: {
type: String,
},
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,56 @@
<template>
<div class="flex items-center">
<label
class="w-[16px] h-[16px] text-[12px] border flex items-center justify-center"
:style="{
backgroundColor: v ? $store.state.setting.appearance.theme.minorBackground : $store.state.setting.appearance.theme.mainBackground,
borderColor: $store.state.setting.appearance.theme.border,
color: v ? $store.state.setting.appearance.theme.fontHover : $store.state.setting.appearance.theme.fontBasic,
}"
@click="v = !v"
>{{ v ? "✓" : "" }}</label
>
<span class="pl-2" :class="size == 'small' ? 'text-xs' : 'text-sm'" @click="v = !v">{{ label }}</span>
</div>
</template>
<script>
export default {
name: "CheckBox",
props: {
value: {
type: Boolean,
},
label: {
type: String,
},
size: {
type: String,
},
},
data() {
return {
v: null,
init: true,
};
},
created() {
this.v = this.value;
},
watch: {
value: function () {
this.v = this.value;
},
v: function () {
if (!this.init) {
this.$emit("update:value", this.v);
this.$emit("change");
} else {
this.init = false;
}
},
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,33 @@
<template>
<div
class="p-2 app-region-no-drag icon-menu"
:title="$store.state.currentLanguage.close"
:style="{ color: this.page == 'main' ? $backgroundTransparencyFontColor($store.state.setting, 1) : $store.state.setting.appearance.theme.fontBasic }"
@mouseover="
$styleMouseover(
$event,
'icon-menu',
['color'],
[this.page == 'main' ? $backgroundTransparencyFontColor($store.state.setting, 0.8) : $hexToRGBA($store.state.setting.appearance.theme.fontBasic, 0.8)]
)
"
@mouseout="$styleMouseout($event, 'icon-menu', ['color'])"
>
<svg class="w-[18px] h-[18px]" viewBox="0 0 24 24">
<path fill="currentColor" d="M19,6.41L17.59,5L12,10.59L6.41,5L5,6.41L10.59,12L5,17.59L6.41,19L12,13.41L17.59,19L19,17.59L13.41,12L19,6.41Z" />
</svg>
</div>
</template>
<script>
export default {
name: "Close",
props: {
page: {
type: String,
},
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,93 @@
<template>
<div class="relative">
<div
class="color-content border rounded w-[60px] h-[30px] p-1"
:style="{
borderColor: $store.state.setting.appearance.theme.border,
}"
@click="show = !show"
:id="id + '-content'"
>
<div class="color-component rounded w-full h-full" :style="{ backgroundColor: typeof c == 'string' ? c : c.hex8 }"></div>
</div>
<Chrome :id="id + '-component'" v-model="c" class="color-component absolute" v-if="show"></Chrome>
</div>
</template>
<script>
import { Chrome } from "@ckpack/vue-color";
export default {
name: "Color",
props: {
id: {
type: String,
},
value: {
type: String,
},
},
components: {
Chrome,
},
data() {
return {
c: null,
show: false,
};
},
created() {
if (this.value == null) {
this.c = "#FFFFFF";
} else {
this.c = this.value;
}
},
watch: {
value: function () {
this.c = this.value;
},
show: function () {
if (this.show) {
document.addEventListener("mousedown", this.mousedown, true);
this.$nextTick(() => {
//
let componentEl = document.getElementById(this.id + "-component");
componentEl.style.top = -121 + "px";
componentEl.style.left = 62 + "px";
componentEl.style.zIndex = 9;
//
let divEl = componentEl.getElementsByClassName("vc-chrome-toggle-icon");
let svgEl = divEl[0].getElementsByTagName("svg");
let pathEl = svgEl[0].getElementsByTagName("path");
pathEl[0].setAttribute("fill", this.$store.state.setting.appearance.theme.fontBasic);
});
} else {
document.removeEventListener("mousedown", this.mousedown, true);
this.$emit("update:value", typeof this.c == "string" ? this.c : this.c.hex8);
this.$emit("change");
}
},
},
methods: {
mousedown(e) {
//
let colorFlag = false;
for (let i = 0; i < e.path.length; i++) {
if (e.path[i].id == null) {
continue;
}
if (e.path[i].id == this.id + "-content" || e.path[i].id == this.id + "-component") {
colorFlag = true;
break;
}
}
if (!colorFlag) {
this.show = false;
}
},
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,39 @@
<template>
<input
:type="type != null ? type : 'text'"
v-model="v"
:placeholder="placeholder"
class="border rounded text-sm py-1 px-2 min-w-0 grow hover:outline-0 focus-visible:outline-0"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderColor: $store.state.setting.appearance.theme.border,
}"
/>
</template>
<script>
export default {
name: "Input",
props: ["value", "placeholder", "type"],
data() {
return {
v: null,
};
},
created() {
this.v = this.value;
},
watch: {
value: function () {
this.v = this.value;
},
v: function () {
this.$emit("update:value", this.v);
this.$emit("change");
},
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,174 @@
<template>
<div class="popup top-0 left-0 w-full h-full fixed z-10">
<div :id="id" class="popup-content fixed z-20 drop-shadow-[0_0_6px_rgba(0,0,0,0.2)] app-region-no-drag">
<slot name="body"></slot>
</div>
</div>
</template>
<script>
import CommonJS from "@/common/index";
export default {
name: "Popup",
props: {
id: {
type: String,
},
width: {
type: Number,
},
height: {
type: Number,
},
},
data() {
return {
//
resize: true,
//
current: false,
//
maxTop: null,
maxLeft: null,
// T
startTop: null,
startLeft: null,
//
startY: null,
startX: null,
//
w: null,
h: null,
};
},
created() {
this.w = this.width;
this.h = this.height;
},
mounted() {
let _this = this;
//
if (this.resize) {
this.resize = false;
this.setSize();
}
window.addEventListener("resize", this.setSize, true);
//
let popupHeaderElementList = document.getElementsByClassName("popup-header");
for (let popupHeaderElement of popupHeaderElementList) {
popupHeaderElement.addEventListener("mousedown", function (e) {
//
_this.current = true;
//
let popup = document.getElementById(_this.id);
// popup-content
let { popupContentTop, popupContentLeft } = _this.getPopupContentTopLeft(popup);
//
_this.startTop = popup.offsetTop;
_this.startLeft = popup.offsetLeft;
//
_this.startY = e.clientY;
_this.startX = e.clientX;
//
let doc = document.documentElement;
//
_this.maxLeft = doc.clientWidth - _this.w - popupContentLeft;
_this.maxTop = doc.clientHeight - _this.h - popupContentTop;
//
document.onmousemove = function (e) {
if (_this.current) {
//
let currentLeft = e.clientX - _this.startX + _this.startLeft;
let currentTop = e.clientY - _this.startY + _this.startTop;
//
currentLeft = currentLeft < 0 - popupContentLeft ? 0 - popupContentLeft : currentLeft > _this.maxLeft ? _this.maxLeft : currentLeft;
currentTop = currentTop < 0 - popupContentTop ? 0 - popupContentTop : currentTop > _this.maxTop ? _this.maxTop : currentTop;
// top left
let popup = document.getElementById(_this.id);
popup.style.left = currentLeft + "px";
popup.style.top = currentTop + "px";
}
};
//
document.onmouseup = function (e) {
_this.current = false;
document.onmousemove = document.onmouseup = null;
};
});
}
},
unmounted() {
window.removeEventListener("resize", this.setSize, true);
},
watch: {
width(newVal, oldVal) {
this.w = newVal;
},
height(newVal, oldVal) {
this.h = newVal;
},
},
methods: {
/**
* 设置弹窗初始化位置
*/
setSize() {
let popup = document.getElementById(this.id);
if (popup != null) {
// popup-content
let { popupContentTop, popupContentLeft } = this.getPopupContentTopLeft(popup);
let body = document.querySelector("body");
popup.style.width = this.w + "px";
popup.style.height = this.h == null ? "auto" : this.h + "px";
popup.style.top = body.clientHeight / 2 - popup.clientHeight / 2 - popupContentTop + "px";
popup.style.left = body.clientWidth / 2 - popup.clientWidth / 2 - popupContentLeft + "px";
}
},
/**
* 判断是否是popupContent
*/
isPopupContent(el) {
if (el != null) {
let classList = el.classList;
if (!CommonJS.arrayIsEmpty(classList)) {
for (let clazz of classList) {
if (clazz == "popup-content") {
return true;
}
}
}
}
return false;
},
/**
* 获取popup-content位置
* @param popup
*/
getPopupContentTopLeft(popup) {
let parent = popup.parentElement;
let popupContent;
if (this.isPopupContent(parent)) {
popupContent = parent;
}
while (parent != null) {
parent = parent.parentElement;
if (this.isPopupContent(parent)) {
popupContent = parent;
}
}
let popupContentTop = 0;
let popupContentLeft = 0;
if (popupContent != null) {
if (popupContent.getBoundingClientRect() != null) {
popupContentTop = popupContent.getBoundingClientRect().top;
popupContentLeft = popupContent.getBoundingClientRect().left;
}
}
return { popupContentTop, popupContentLeft };
},
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,130 @@
<template>
<div class="text-sm">
<div
class="border rounded"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderColor: $store.state.setting.appearance.theme.border,
width: width + 'px',
}"
:id="id"
>
<div class="flex items-center py-1" @click="showList = !showList">
<span class="ml-[10px]">{{ label }}</span>
<svg class="w-4 h-4 ml-auto mr-[6px]" viewBox="0 0 24 24">
<path fill="currentColor" d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z" />
</svg>
</div>
</div>
<ul
class="border rounded mt-[1px] py-1 absolute max-h-[262px]"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderColor: $store.state.setting.appearance.theme.border,
width: width + 'px',
}"
v-if="showList"
>
<div class="max-h-[262px]" data-simplebar>
<li
class="py-1 px-[10px] select-item"
v-for="(item, index) of list"
@click="select(item)"
:key="'select_item_' + index"
@mouseover="
$styleMouseover(
$event,
'select-item',
['background-color', 'color'],
[$hexToRGBA($store.state.setting.appearance.theme.minorBackground, 1), $store.state.setting.appearance.theme.fontHover]
)
"
@mouseout="$styleMouseout($event, 'select-item', ['background-color', 'color'])"
>
{{ item.label }}
</li>
</div>
</ul>
</div>
</template>
<script>
import SimpleBar from "simplebar";
import "simplebar/dist/simplebar.css";
export default {
name: "Select",
props: {
id: {
required: true,
type: String,
},
value: {
required: true,
},
list: {
type: Array,
},
width: {
type: Number,
},
},
data() {
return {
v: null,
label: null,
showList: false,
init: true,
};
},
created() {
if (this.value != null) {
this.v = this.value;
for (let e of this.list) {
if (e.value == this.v) {
this.label = e.label;
}
}
}
},
mounted() {
document.addEventListener("click", this.click, true);
},
unmounted() {
document.removeEventListener("click", this.click, true);
},
methods: {
select(item) {
this.v = item.value;
this.label = item.label;
this.showList = false;
},
click(e) {
let flag = false;
for (let i = 0; i < e.path.length; i++) {
if (e.path[i].id != null && e.path[i].id == this.id) {
flag = true;
return;
}
}
if (!flag) {
this.showList = false;
}
},
},
watch: {
v: function () {
if (!this.init) {
this.$emit("update:value", this.v);
this.$emit("change");
} else {
this.init = false;
}
},
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,44 @@
<template>
<div
class="flex items-center"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderRadius: $store.state.setting.appearance.windowRoundedCorners ? '8px' : null,
}"
>
<h1 class="popup-header px-2 w-full text-sm flex items-center h-[34px]">
{{ title }}
</h1>
<Close @click="close" :key="'close-' + $store.state.setting.appearance.theme.name"></Close>
</div>
</template>
<script>
import Close from "@/components/Close";
export default {
name: "TopTitleBar",
components: { Close },
props: {
//
title: {
type: String,
},
//
show: {
type: Boolean,
},
},
methods: {
/**
* 关闭窗口
*/
close() {
this.$emit("update:show", false);
},
},
};
</script>
<style scoped></style>

125
src/renderer/main.js Normal file
View File

@ -0,0 +1,125 @@
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import "./style/tailwind.css";
import "./style/main.css";
/**
* 全局方法
*/
const getClassElement = function getClassElement(e, className) {
let target;
if (e instanceof HTMLElement) {
target = e;
} else {
for (let i = 0; i < e.path.length; i++) {
if (e.path[i].classList != null && e.path[i].classList.length > 0) {
for (let clazz of e.path[i].classList) {
if (clazz == className) {
target = e.path[i];
break;
}
}
}
}
}
return target;
};
/**
* 鼠标经过
* @param e
* @param className
* @param style
* @param value
*/
const styleMouseover = function styleMouseover(e, className, style, value) {
let target = getClassElement(e, className);
if (target != null && style != null) {
for (let i = 0; i < style.length; i++) {
target.style.setProperty(style[i], value[i]);
}
}
};
/**
* 鼠标移开
* @param e
* @param className
* @param style
*/
const styleMouseout = function styleMouseout(e, className, style, value) {
let target = getClassElement(e, className);
if (target != null && style != null) {
for (let i = 0; i < style.length; i++) {
if (value != null && value[i] != null) {
target.style.setProperty(style[i], value[i]);
} else {
target.style.removeProperty(style[i]);
}
}
}
};
/**
* 十六进制转 RGBA 的函数
* @param hex
* @param a
* @returns {`rgba(${number}, ${number}, ${number}, ${string})`}
*/
const hexToRGBA = function hexToRGBA(hex, a) {
let hexValue = hex.replace("#", "");
// 将六位颜色值转为八位
if (hexValue.length === 6) {
hexValue = hexValue + "ff";
}
// 获取rgba各分量值
const red = parseInt(hexValue.substring(0, 2), 16);
const green = parseInt(hexValue.substring(2, 4), 16);
const blue = parseInt(hexValue.substring(4, 6), 16);
const alpha = parseInt(hexValue.substring(6, 8), 16) / 255;
return `rgba(${red}, ${green}, ${blue}, ${a == 1 ? alpha : a})`;
};
/**
* 返回背景颜色
* @param setting
* @returns {`rgba(${number}, ${number}, ${number}, ${string})`}
*/
const backgroundTransparencyBackgroundColor = function backgroundTransparencyBackgroundColor(setting) {
return hexToRGBA(setting.appearance.theme.mainBackground, setting.appearance.backgroundTransparency);
};
/**
* 返回字体颜色
*/
const backgroundTransparencyFontColor = function backgroundTransparencyFontColor(setting, transparency) {
return hexToRGBA(setting.appearance.theme.fontBasic, transparency);
};
/**
* 获取冒号
*/
const getColon = function getColon() {
return store.state.currentLanguage.colon;
};
createApp(App)
.mixin({
methods: {
$styleMouseover: styleMouseover,
$styleMouseout: styleMouseout,
$hexToRGBA: hexToRGBA,
$backgroundTransparencyBackgroundColor: backgroundTransparencyBackgroundColor,
$backgroundTransparencyFontColor: backgroundTransparencyFontColor,
$getColon: getColon,
$getClassElement: getClassElement,
},
})
.use(store)
.use(router)
.mount("#app");

View File

@ -0,0 +1,39 @@
import { createRouter, createWebHashHistory } from "vue-router";
const routes = [
{
path: "/",
name: "Index",
component: () => import("@/views/index/Index.vue"),
},
{
path: "/classification/addEdit",
name: "ClassificationAddEdit",
component: () => import("@/views/classification/AddEdit.vue"),
},
{
path: "/item/addEdit",
name: "ItemAddEdit",
component: () => import("@/views/item/AddEdit.vue"),
},
{
path: "/about",
name: "About",
component: () => import("@/views/about/Index.vue"),
},
{
path: "/setting",
name: "Setting",
component: () => import("@/views/setting/Index.vue"),
},
{
path: "/searchWindow",
name: "searchWindow",
component: () => import("@/views/search/Window.vue"),
},
];
export default new createRouter({
routes,
history: createWebHashHistory(),
});

608
src/renderer/store/index.js Normal file
View File

@ -0,0 +1,608 @@
import { createStore } from "vuex";
export default createStore({
state: {
// 列表
list: [],
// 全局设置
setting: {},
// 应用内快捷键
appShortcutKeyMap: new Map(),
// 当前语言
currentLanguage: null,
// 语言
language: {
chinese: {
setting: "设置",
about: "关于",
checkForUpdates: "检查更新",
checkForUpdatesNewVersionMessage: '检查到有新版本,点击"确定"跳转官网下载最新版本。',
checkForUpdatesLatestVersionMessage: "当前已经是最新版本。",
checkForUpdatesFailedMessage: "检查更新失败,请确认网络。",
exit: "退出",
ok: "确定",
cancel: "取消",
newClassification: "新建分类",
newSubClassification: "新建子分类",
editClassification: "编辑分类",
editSubClassification: "编辑子分类",
edit: "编辑",
delete: "删除",
cut: "剪切",
copy: "复制",
paste: "粘贴",
notFoundFileMessage: "找不到指定的文件",
notFoundFolderMessage: "找不到指定的文件夹",
newItem: "新建项目",
clearItem: "清空项目",
open: "打开",
runAsAdministrator: "以管理员身份运行",
openLocation: "打开文件所在的位置",
moveTo: "移动到",
copyTo: "复制到",
name: "名称",
shortcutKey: "快捷键",
shortcutKeyIncompleteMessage: "快捷键不完整,请完善您的快捷键。",
shortcutKeyConflictMessage(text, type) {
if (type == 0) {
return '与"' + text + '"分类快捷键产生冲突,请重新设置。';
} else {
return '与"' + text + '"项目快捷键产生冲突,请重新设置。';
}
},
shortcutKeyConflictSettingGeneralShowHideMessage: '与"设置-常规"中的"显示/隐藏"快捷键产生冲突,请重新设置。',
shortcutKeyConflictSettingItemSearchMessage: '与"设置-项目"中的"搜索"快捷键产生冲突,请重新设置。',
shortcutKeyConflictSettingQuickSearchShowHideMessage: '与"设置-快速搜索"中的"显示/隐藏"快捷键产生冲突,请重新设置。',
editItem: "编辑项目",
colon: "",
target: "目标",
browse: "浏览",
defaultIcon: "默认图标",
url: "网址",
getUrlInfo: "获取网址信息",
urlErrorMessage: "获取网址信息失败,请手动填写。",
arguments: "参数",
file: "文件",
folder: "文件夹",
gettingUrlInfo: "获取中",
officialWebsite: "官方网站",
start: "启动",
autoStart: "开机启动",
showHide: "显示/隐藏",
language: "语言",
theme: "主题",
search: "搜索",
doubleClickOpen: "双击打开",
general: "常规",
appearance: "外观",
item: "项目",
layout: "布局",
iconSize: "图标大小",
backupRestoreData: "备份/恢复数据",
backup: "备份",
restore: "恢复",
openAfterHideMainInterface: "打开后隐藏主界面",
tile: "平铺",
list: "列表",
large: "大",
medium: "中",
small: "小",
chinese: "简体中文",
traditionalChinese: "繁體中文",
window: "窗口",
alwaysTop: "永远置顶",
notPrompt: "不再提示",
feedback: "反馈",
system: "系统",
computer: "计算机",
document: "文档",
controlPanel: "控制面板",
networkAndSharingCenter: "网络和共享中心",
recycleBin: "回收站",
fileExplorerOptions: "文件资源管理器选项",
programsAndFeatures: "程序和功能",
calculator: "计算器",
services: "服务",
commandPrompt: "命令提示符",
taskManager: "任务管理器",
registryEditor: "注册表编辑器",
powerOptions: "电源选项",
menu: "菜单",
close: "关闭",
edgeAutoHide: "停靠在桌面边缘时自动隐藏",
startupTray: "启动后最小化到系统托盘",
save: "保存",
lockItem: "锁定项目",
unlockItem: "解锁项目",
copyFullPath: "复制完整路径",
lockSize: "锁定尺寸",
classification: "分类",
width: "宽度",
size: "尺寸",
hideLosingFocus: "失去焦点后隐藏",
mouseHover: "鼠标悬停",
switch: "切换",
useItemOpen: "从程序外拖动文件到项目图标上时用此项目打开文件",
hideTray: "隐藏托盘图标",
middleClick: "中键单击",
mouseWheel: "鼠标滚轮",
remark: "备注",
show: "显示",
sort: "排序",
ascendingOrder: "升序",
descendingOrder: "降序",
transparency: "窗口透明度",
byInitial: "按首字母",
recordOpenNumber: "记录打开次数",
openNumber: "打开次数",
byOpenNumber: "按打开次数",
fixedPosition: "固定位置",
data: "数据",
dataDirectory: "数据目录",
dataDirectoryNote:
'注意无特殊需求请勿随意修改数据目录如果修改数据目录后会在Dawn Launcher程序目录的上一级目录创建名字为".dawn_launcher_profile"的文件来存储数据目录地址,请勿随意删除。',
select: "选择",
hide: "隐藏",
hideItemName: "隐藏名称",
alwaysCenter: "永远居中",
rewardAndSponsorship: "打赏&赞助",
showFollowMousePosition: "显示时跟随鼠标位置",
default: "默认",
quickSearch: "快速搜索",
rememberSelectionState: "记住选择状态",
onlyTileTakeEffect: "只在布局为“平铺”模式下生效。",
onlyListTakeEffect: "只在布局为“列表”模式下生效。",
left: "左侧",
top: "顶部",
right: "右侧",
enable: "启用",
lockClassification: "锁定分类",
unlockClassification: "解锁分类",
exportIcon: "导出图标",
information: "信息",
itemNameShowRowCount: "名称显示行数",
nameAlign: "名称对齐",
center: "居中",
webSearch: "网络搜索",
searchSource: "搜索源",
keyword: "关键字",
add: "新增",
wSearchKeyword: "{w}为搜索关键字。",
webSearchPrompt: '输入"冒号 + 关键字 + 空格"或"关键字 + 空格"使用网络搜索,例如使用谷歌搜索,输入":g"或"g",然后按下空格键,进入网络搜索模式。',
perfmon: "资源监视器",
computerManagement: "计算机管理",
openWith: "打开方式",
shutdown: "关机",
restart: "重启",
sleep: "睡眠",
lock: "锁定",
multipleItem: "多项目",
itemList: "项目列表",
selectedItems: "已选项目",
startMenu: "开始菜单",
loading: "加载中",
hoverMS: "悬停[毫秒]",
numberOfColumns: "列数",
globalShortcutKey: "全局快捷键",
backgroundTransparency: "背景透明度",
function: "功能",
checkInvalidItem: "检查无效项目",
useIconBackgroundColor: "使用图标背景颜色",
doNotDisturb: "勿扰模式",
doNotDisturbNote: "开启勿扰模式后计算机在游戏、应用全屏模式下不会弹出Dawn Launcher窗口。",
createDesktopShortcut: "创建快捷方式",
fontSize: "字体大小",
network: "网络",
proxy: "代理",
useProxy: "使用代理",
username: "用户名",
password: "密码",
address: "地址",
proxyNote: "仅支持HTTP代理填写“地址”时需要带通信协议和端口例如http://127.0.0.1:7890如果没有用户名和密码为空即可。",
color: "颜色",
fontShadow: "字体阴影",
mainColor: "主色",
secondaryColor: "副色",
mainFontColor: "字体主色",
secondaryFontColor: "字体副色",
borderColor: "边框色",
backgroundImage: "背景图",
backgroundImageMode: "背景图模式",
repeat: "重复",
notRepeat: "不重复",
zoom: "缩放",
backgroundImagePosition: "背景图定位",
bottom: "底部",
windowRoundedCorners: "窗口圆角",
doubleClickTaskbar: "双击任务栏",
refreshIconCache: "刷新图标缓存",
extraLarge: "超大",
titleBar: "标题栏",
title: "标题",
setClassificationIcon: "设置分类图标",
setIcon: "设置图标",
deleteIcon: "删除图标",
iconMode: "图标模式",
mode: "模式",
normal: "普通",
icon: "图标",
toRelativePath: "转相对路径",
toAbsolutePath: "转绝对路径",
delayDisplayMS: "延迟显示[毫秒]",
delayHidingMS: "延迟隐藏[毫秒]",
explorerMenu: "资源管理器菜单",
associatedFolder: "关联文件夹",
associatedFolderMessage: "设置关联文件夹会清空原分类下所有项目,请谨慎操作,是否确认?",
numberKey: "数字键",
none: "无",
ctrlNumberKey: "Ctrl + 数字键",
altNumberKey: "Alt + 数字键",
fixedClassification: "固定分类",
autoSwitchClassification: "项目列表滚动到底部或顶部时自动切换分类",
colonKeywordSpace: "冒号 + 关键字 + 空格",
keywordSpace: "关键字 + 空格",
excludeSearch: "排除搜索",
modifyIcon: "修改图标",
switchEnglish: "显示窗口时将输入法切换为英文模式",
startLocation: "起始位置",
startLocationNote: "无特殊需求为空即可",
batchOperation: "批量操作",
cancelBatchOperation: "取消批量操作",
openNow: "仅剩一项时立即打开",
backgroundImageTransparency: "背景图透明度",
batchDeleteItemMessage: "是否批量删除所选项目?",
turnOffMonitor: "关闭显示器",
showOnlyFiles: "只显示文件",
showOnlyFolders: "只显示文件夹",
description: "描述",
deleteClassificationMessage: "是否删除当前分类?",
batchMoveItemMessage: "是否批量移动项目?",
batchCopyItemMessage: "是否批量复制项目?",
downloadImageFailedMessage: "下载图片失败。",
downloadImageNotImageFormatMessage: "链接不是图片格式。",
refreshIconCurrentClassificationMessage: "是否刷新当前分类下所有项目图标缓存?",
relativeCurrentClassificationMessage: "是否将当前分类下所有项目都转为相对路径?",
absoluteCurrentClassificationMessage: "是否将当前分类下所有项目都转为绝对路径?",
batchRefreshIconCache: "批量刷新图标缓存",
batchConversionRelativePath: "批量转为相对路径",
batchConversionAbsolutePath: "批量转为绝对路径",
convertRelativePath: "转为相对路径",
convertAbsolutePath: "转为绝对路径",
deleteItemMessage: "是否删除项目?",
clearItemMessage: "是否清空当前分类下所有项目?",
modifyDataDirectoryMessage: "是否确定修改数据目录,点击”确定“后,将修改数据目录并重启应用程序。",
backUpData: "备份数据",
restoreData: "恢复数据",
aggregateClassification: "聚合分类",
aggregateClassificationNote: "说明:聚合分类可以将所有分类下的项目聚合到一起,按照设置的排序方式、项目数量进行排序和显示。",
aggregateClassificationSortNote: "如果选择按打开次数排序,需要进入设置-项目-打开-记录打开次数,开启记录打开次数功能。",
aggregateClassificationMessage: "设置聚合分类会清空原分类下所有项目,请谨慎操作,是否确认?",
itemNumber: "项目数量",
byLastOpen: "按最近打开",
hiddenItem: "隐藏项",
hiddenItemNote: "输入需要隐藏文件/文件夹名称,多个按英文逗号分割。",
svgCodeIcon: "SVG代码图标",
svgCodeIconPlaceholder: "请输入SVG代码...",
svgCodeIconValidationNote: "输入SVG代码后需要先点击“校验”按钮。",
networkIcon: "网络图标",
networkIconPlaceholder: "请输入链接地址...",
networkIconNote: "支持JPG、JPEG、GIF、PNG、ICO、SVG格式图片。",
getIcon: "获取图标",
localIcon: "本地图标",
notRefreshIcon: "不参与刷新图标缓存",
targetNotExist: "目标不存在",
switchClassificationCollapseOtherSubClassification: "切换分类时收起其他子分类",
hideWindowFoldChildClassification: "隐藏窗口时收起子分类",
displayMainInterface: "显示主界面",
hideThisItem: "隐藏该项",
selectAll: "全选",
emptyRecycleBin: "清空回收站",
matchingConditions: "匹配条件",
historyRecord: "历史记录",
openAfterHideQuickSearchWindow: "打开后隐藏快速搜索窗口",
subClassification: "子分类",
fontWeight: "字体粗细",
fontLineHeight: "字体行高",
},
traditionalChinese: {
setting: "設定",
about: "關於",
checkForUpdates: "檢查更新",
checkForUpdatesNewVersionMessage: '檢查到有新版本,點選"確定"跳轉官網下載最新版本。',
checkForUpdatesLatestVersionMessage: "當前已經是最新版本。",
checkForUpdatesFailedMessage: "檢查更新失敗,請確認網路。",
exit: "退出",
ok: "確定",
cancel: "取消",
newClassification: "新建分類",
newSubClassification: "新建子分類",
editClassification: "編輯分類",
editSubClassification: "編輯子分類",
edit: "編輯",
delete: "刪除",
cut: "剪切",
copy: "復制",
paste: "黏貼",
notFoundFileMessage: "找不到指定的文件",
notFoundFolderMessage: "找不到指定的檔案夾",
newItem: "新建項目",
clearItem: "清空項目",
open: "打開",
runAsAdministrator: "以管理員身份運行",
openLocation: "打開文件所在的位置",
moveTo: "移動到",
copyTo: "復制到",
name: "名稱",
shortcutKey: "快捷鍵",
shortcutKeyIncompleteMessage: "快捷鍵不完整,請完善您的快捷鍵。",
shortcutKeyConflictMessage(text, type) {
if (type == 0) {
return '與"' + text + '"分類快捷鍵產生沖突,請重新設定。';
} else {
return '與"' + text + '"項目快捷鍵產生沖突,請重新設定。';
}
},
shortcutKeyConflictSettingGeneralShowHideMessage: '與"設定-常規"中的"顯示/隱藏"快捷鍵產生沖突,請重新設定。',
shortcutKeyConflictSettingItemSearchMessage: '與"設定-項目"中的"搜索"快捷鍵產生沖突,請重新設定。',
shortcutKeyConflictSettingQuickSearchShowHideMessage: '與"設定-快速搜索"中的"顯示/隱藏"快捷鍵產生沖突,請重新設定。',
editItem: "編輯項目",
colon: "",
target: "目標",
browse: "瀏覽",
defaultIcon: "預設圖示",
url: "網址",
getUrlInfo: "獲取網址信息",
urlErrorMessage: "獲取網址信息失敗,請手動填寫。",
arguments: "參數",
file: "文件",
folder: "檔案夾",
gettingUrlInfo: "獲取中",
officialWebsite: "官方網站",
start: "啟動",
autoStart: "開機啟動",
showHide: "顯示/隱藏",
language: "語言",
theme: "主題",
search: "搜索",
doubleClickOpen: "雙擊打開",
general: "常規",
appearance: "外觀",
item: "項目",
layout: "佈局",
iconSize: "圖示大小",
backupRestoreData: "備份/恢復數據",
backup: "備份",
restore: "恢復",
openAfterHideMainInterface: "打開後隱藏主界面",
tile: "平鋪",
list: "列錶",
large: "大",
medium: "中",
small: "小",
chinese: "简体中文",
traditionalChinese: "繁體中文",
window: "視窗",
alwaysTop: "永遠置頂",
notPrompt: "不再提示",
feedback: "反饋",
system: "繫統",
computer: "計算機",
document: "文檔",
controlPanel: "控制面闆",
networkAndSharingCenter: "網路和共享中心",
recycleBin: "回收站",
fileExplorerOptions: "文件資源管理器選項",
programsAndFeatures: "程式和功能",
calculator: "計算器",
services: "服務",
commandPrompt: "命令提示符",
taskManager: "任務管理器",
registryEditor: "註冊錶編輯器",
powerOptions: "電源選項",
menu: "菜單",
close: "關閉",
edgeAutoHide: "停靠在桌面邊緣時自動隱藏",
startupTray: "啟動後最小化到繫統托盤",
save: "保存",
lockItem: "鎖定項目",
unlockItem: "解鎖項目",
copyFullPath: "復制完整路徑",
lockSize: "鎖定尺寸",
classification: "分類",
width: "寬度",
size: "尺寸",
hideLosingFocus: "失去焦點後隱藏",
mouseHover: "滑鼠懸停",
switch: "切換",
useItemOpen: "從程式外拖動文件到項目圖示上時用此項目打開文件",
hideTray: "隱藏托盤圖示",
middleClick: "中鍵單擊",
mouseWheel: "滑鼠滾輪",
remark: "備註",
show: "顯示",
sort: "排序",
ascendingOrder: "升序",
descendingOrder: "降序",
transparency: "視窗透明度",
byInitial: "按首字母",
recordOpenNumber: "記錄打開次數",
openNumber: "打開次數",
byOpenNumber: "按打開次數",
fixedPosition: "固定位置",
data: "數據",
dataDirectory: "數據目錄",
dataDirectoryNote:
'註意無特殊需求請勿隨意修改數據目錄如果修改數據目錄後會在Dawn Launcher程式目錄的上一級目錄創建名字為".dawn_launcher_profile"的文件來存儲數據目錄地址,請勿隨意刪除。',
select: "選擇",
hide: "隱藏",
hideItemName: "隱藏名稱",
alwaysCenter: "永遠居中",
rewardAndSponsorship: "打賞&贊助",
showFollowMousePosition: "顯示時跟隨滑鼠位置",
default: "預設",
quickSearch: "快速搜索",
rememberSelectionState: "記住選擇狀態",
onlyTileTakeEffect: "只在佈局為“平鋪”模式下生效。",
onlyListTakeEffect: "只在佈局為“列錶”模式下生效。",
left: "左側",
top: "頂部",
right: "右側",
enable: "啟用",
lockClassification: "鎖定分類",
unlockClassification: "解鎖分類",
exportIcon: "導出圖示",
information: "信息",
itemNameShowRowCount: "名稱顯示行數",
nameAlign: "名稱對齊",
center: "居中",
webSearch: "網路搜索",
searchSource: "搜索源",
keyword: "關鍵字",
add: "新增",
wSearchKeyword: "{w}為搜索關鍵字。",
webSearchPrompt: '輸入"冒號 + 關鍵字 + 空格"或"關鍵字 + 空格"使用網路搜索,例如使用谷歌搜索,輸入":g"或"g",然後按下空格鍵,進入網路搜索模式。',
perfmon: "資源監視器",
computerManagement: "計算機管理",
openWith: "打開方式",
shutdown: "關機",
restart: "重啟",
sleep: "睡眠",
lock: "鎖定",
multipleItem: "多項目",
itemList: "項目列錶",
selectedItems: "已選項目",
startMenu: "開始菜單",
loading: "加載中",
hoverMS: "懸停[毫秒]",
numberOfColumns: "列數",
globalShortcutKey: "全局快捷鍵",
backgroundTransparency: "背景透明度",
function: "功能",
checkInvalidItem: "檢查無效項目",
useIconBackgroundColor: "使用圖示背景顏色",
doNotDisturb: "勿擾模式",
doNotDisturbNote: "開啟勿擾模式後計算機在遊戲、應用全屏模式下不會彈出Dawn Launcher視窗。",
createDesktopShortcut: "創建捷徑",
fontSize: "字體大小",
network: "網路",
proxy: "代理",
useProxy: "使用代理",
username: "用戶名",
password: "密碼",
address: "地址",
proxyNote: "僅支援HTTP代理填寫“地址”時需要帶通信協議和端口例如http://127.0.0.1:7890如果沒有用戶名和密碼為空即可。",
color: "顏色",
fontShadow: "字體陰影",
mainColor: "主色",
secondaryColor: "副色",
mainFontColor: "字體主色",
secondaryFontColor: "字體副色",
borderColor: "邊框色",
backgroundImage: "背景圖",
backgroundImageMode: "背景圖模式",
repeat: "重復",
notRepeat: "不重復",
zoom: "縮放",
backgroundImagePosition: "背景圖定位",
bottom: "底部",
windowRoundedCorners: "視窗圓角",
doubleClickTaskbar: "雙擊任務欄",
refreshIconCache: "刷新圖示緩存",
extraLarge: "超大",
titleBar: "標題欄",
title: "標題",
setClassificationIcon: "設定分類圖示",
setIcon: "設定圖示",
deleteIcon: "刪除圖示",
iconMode: "圖示模式",
mode: "模式",
normal: "普通",
icon: "圖示",
toRelativePath: "轉相對路徑",
toAbsolutePath: "轉絕對路徑",
delayDisplayMS: "延遲顯示[毫秒]",
delayHidingMS: "延遲隱藏[毫秒]",
explorerMenu: "資源管理器菜單",
associatedFolder: "關聯檔案夾",
associatedFolderMessage: "設定關聯檔案夾會清空原分類下所有項目,請謹慎操作,是否確認?",
numberKey: "數字鍵",
none: "無",
ctrlNumberKey: "Ctrl + 數字鍵",
altNumberKey: "Alt + 數字鍵",
fixedClassification: "固定分類",
autoSwitchClassification: "項目列錶滾動到底部或頂部時自動切換分類",
colonKeywordSpace: "冒號 + 關鍵字 + 空格",
keywordSpace: "關鍵字 + 空格",
excludeSearch: "排除搜索",
modifyIcon: "修改圖示",
switchEnglish: "顯示視窗時將輸入法切換為英文模式",
startLocation: "起始位置",
startLocationNote: "無特殊需求為空即可",
batchOperation: "批量操作",
cancelBatchOperation: "取消批量操作",
openNow: "僅剩一項時立即打開",
backgroundImageTransparency: "背景圖透明度",
batchDeleteItemMessage: "是否批量刪除所選項目?",
turnOffMonitor: "關閉顯示器",
showOnlyFiles: "只顯示文件",
showOnlyFolders: "只顯示檔案夾",
description: "描述",
deleteClassificationMessage: "是否刪除當前分類?",
batchMoveItemMessage: "是否批量移動項目?",
batchCopyItemMessage: "是否批量復制項目?",
downloadImageFailedMessage: "下載圖片失敗。",
downloadImageNotImageFormatMessage: "鏈接不是圖片格式。",
refreshIconCurrentClassificationMessage: "是否刷新當前分類下所有項目圖示緩存?",
relativeCurrentClassificationMessage: "是否將當前分類下所有項目都轉為相對路徑?",
absoluteCurrentClassificationMessage: "是否將當前分類下所有項目都轉為絕對路徑?",
batchRefreshIconCache: "批量刷新圖示緩存",
batchConversionRelativePath: "批量轉為相對路徑",
batchConversionAbsolutePath: "批量轉為絕對路徑",
convertRelativePath: "轉為相對路徑",
convertAbsolutePath: "轉為絕對路徑",
deleteItemMessage: "是否刪除項目?",
clearItemMessage: "是否清空當前分類下所有項目?",
modifyDataDirectoryMessage: "是否確定修改數據目錄,點選”確定“後,將修改數據目錄併重啟應用程式。",
backUpData: "備份數據",
restoreData: "恢復數據",
aggregateClassification: "聚合分類",
aggregateClassificationNote: "說明:聚合分類可以將所有分類下的項目聚合到一起,按照設定的排序方式、項目數量進行排序和顯示。",
aggregateClassificationSortNote: "如果選擇按打開次數排序,需要進入設定-項目-打開-記錄打開次數,開啟記錄打開次數功能。",
aggregateClassificationMessage: "設定聚合分類會清空原分類下所有項目,請謹慎操作,是否確認?",
itemNumber: "項目數量",
byLastOpen: "按最近打開",
hiddenItem: "隱藏項",
hiddenItemNote: "輸入需要隱藏文件/檔案夾名稱,多個按英文逗號分割。",
svgCodeIcon: "SVG代碼圖示",
svgCodeIconPlaceholder: "請輸入SVG代碼...",
svgCodeIconValidationNote: "輸入SVG代碼後需要先點選“校驗”按鈕。",
networkIcon: "網路圖示",
networkIconPlaceholder: "請輸入鏈接地址...",
networkIconNote: "支援JPG、JPEG、GIF、PNG、ICO、SVG格式圖片。",
getIcon: "獲取圖示",
localIcon: "在地圖示",
notRefreshIcon: "不參與刷新圖示緩存",
targetNotExist: "目標不存在",
switchClassificationCollapseOtherSubClassification: "切換分類時收起其他子分類",
hideWindowFoldChildClassification: "隱藏視窗時收起子分類",
displayMainInterface: "顯示主界面",
hideThisItem: "隱藏該項",
selectAll: "全選",
emptyRecycleBin: "清空回收站",
matchingConditions: "匹配條件",
historyRecord: "歷史記錄",
openAfterHideQuickSearchWindow: "開啟後隱藏快速搜尋視窗",
subClassification: "子分類",
fontWeight: "字體粗細",
fontLineHeight: "字體行高",
},
},
// 背景图
backgroundImage: null,
// 图标
iconDataMap: null,
},
mutations: {},
actions: {},
modules: {},
});

View File

@ -0,0 +1,87 @@
body {
font-family: system-ui;
}
input:focus,
div:focus,
select {
outline: none !important;
}
input:focus-visible,
div:focus-visible,
select:focus-visible {
outline: none !important;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
}
input[type="number"] {
-moz-appearance: textfield;
}
body {
overflow: hidden;
}
.app-region-drag {
-webkit-app-region: drag;
}
.app-region-no-drag {
-webkit-app-region: no-drag;
}
.popup-header {
cursor: move;
}
.right-menu.visible {
transform: scale(1);
transition: transform ease-in-out;
}
.simplebar-track.simplebar-vertical,
.simplebar-track.simplebar-horizontal {
width: 8px !important;
top: -2px !important;
}
textarea::-webkit-scrollbar {
width: 7px;
}
.item-name-tile-2 {
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
line-clamp: 2;
-webkit-box-orient: vertical;
}
.item-name-tile-2-no-ellipsis {
max-height: 40px;
word-break: break-all;
overflow: hidden;
}
.item-name-tile-1 {
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
}
.item-name-tile-1-no-ellipsis {
max-height: 20px;
word-break: break-all;
overflow: hidden;
}

View File

@ -0,0 +1,3 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@ -0,0 +1,59 @@
<template>
<div
class="text-sm h-full"
style="text-shadow: none"
:style="{
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
color: $store.state.setting.appearance.theme.fontBasic,
borderRadius: $store.state.setting.appearance.backgroundTransparency < 1 && $store.state.setting.appearance.windowRoundedCorners ? '8px' : null,
}"
>
<TopTitleBar :title="$store.state.currentLanguage.about" v-model:show="s"></TopTitleBar>
<div class="px-2">
<img src="../../assets/images/logo-transparent.png" class="w-20 h-20 mx-auto" draggable="false" />
<p class="mt-4">Dawn Launcher {{ version }}</p>
<p class="mt-2">Copyright © 2022-2023 Dawn Launcher. All Rights Reserved</p>
<p class="mt-2 pb-2">
{{ $store.state.currentLanguage.officialWebsite }}<span v-html="$getColon()"></span
><span @click="openUrl" class="cursor-pointer">https://dawnlauncher.com/</span>
</p>
</div>
</div>
</template>
<script>
import TopTitleBar from "@/components/TopTitleBar";
const { ipcRenderer } = window.require("electron");
export default {
name: "About",
components: { TopTitleBar },
props: {
//
show: {
type: Boolean,
},
},
data() {
return {
version: null,
s: null,
};
},
watch: {
s: function (newData) {
this.$emit("update:show", newData);
},
},
created() {
this.s = this.show
this.version = ipcRenderer.sendSync("getVersion");
},
methods: {
openUrl() {
ipcRenderer.send("openUrl", "https://dawnlauncher.com/");
},
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,234 @@
<template>
<div
class="h-full"
style="text-shadow: none"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderRadius: $store.state.setting.appearance.backgroundTransparency < 1 && $store.state.setting.appearance.windowRoundedCorners ? '8px' : null,
}"
>
<TopTitleBar :title="title()" v-model:show="s"></TopTitleBar>
<div class="pt-2 px-2">
<div class="flex items-center justify-center">
<span class="text-sm">{{ $store.state.currentLanguage.name }}<span v-html="$getColon()"></span></span>
<Input id="nameInput" class="flex-1" v-model:value="name"></Input>
</div>
<div class="flex items-center justify-center flex-wrap mt-3 relative">
<span class="text-sm">{{ $store.state.currentLanguage.shortcutKey }}<span v-html="$getColon()"></span></span>
<Input
id="shortcutKeyInput"
v-model:value="shortcutKey"
class="flex-1"
@keydown="tempShortcutKey = shortcutKey = setShortcutKey($event, shortcutKey, true)"
@keyup="checkShortcutKeys($event)"
></Input>
</div>
<div class="flex items-center">
<check-box v-model:value="globalShortcutKey" :label="$store.state.currentLanguage.globalShortcutKey" class="mt-2 ml-auto" />
</div>
</div>
<div class="absolute right-2 bottom-[6px] flex items-center">
<Button
:text="$store.state.currentLanguage.ok"
class="w-20 mr-1"
:class="!strIsEmpty(name) && shortcutKeyCheckMessage == null ? '' : 'cursor-not-allowed'"
@click="addEdit"
type="primary"
></Button>
<Button :text="$store.state.currentLanguage.cancel" class="w-20" type="cancel" @click="close"></Button>
</div>
</div>
</template>
<script>
import TopTitleBar from "@/components/TopTitleBar";
import ClassificationJS from "@/views/classification/js/index.js";
import CommonJS from "@/common/index";
import Button from "@/components/Button";
import Input from "@/components/Input";
import CheckBox from "@/components/CheckBox";
const { ipcRenderer } = window.require("electron");
export default {
name: "ClassificationAddEdit",
components: { Input, Button, TopTitleBar, CheckBox },
props: {
// 0: 1:
type: {
type: Number,
},
// id
id: {
type: Number,
},
// ID
parentId: {
type: Number,
},
//
show: {
type: Boolean,
},
},
data() {
return {
s: null,
//
name: null,
//
oldName: null,
//
shortcutKey: null,
oldShortcutKey: null,
tempShortcutKey: null,
//
globalShortcutKey: false,
//
shortcutKeyCheckMessage: null,
};
},
watch: {
s: function (newData) {
this.$emit("update:show", newData);
},
},
created() {
this.s = this.show;
this.name = "新分类";
// 1
if (this.type == 1) {
let classification = ClassificationJS.getClassificationById(this.parentId != null ? this.parentId : this.id, this.parentId != null ? this.id : null);
this.name = classification.name;
this.shortcutKey = this.oldShortcutKey = classification.shortcutKey;
this.globalShortcutKey = classification.globalShortcutKey == null ? false : classification.globalShortcutKey;
}
this.$watch("shortcutKey", () => {
if (this.tempShortcutKey != this.shortcutKey) {
this.shortcutKey = this.tempShortcutKey;
}
});
},
mounted() {
this.$nextTick(() => {
//
document.getElementById("nameInput").focus();
});
//
window.addEventListener("keydown", this.keydown, true);
},
unmounted() {
window.removeEventListener("keydown", this.keydown, true);
},
methods: {
/**
* 判断字符串是否为空
*/
strIsEmpty: CommonJS.strIsEmpty,
/**
* 标题
*/
title() {
if (this.type == 0) {
if (this.parentId != null) {
return this.$store.state.currentLanguage.newSubClassification;
} else {
return this.$store.state.currentLanguage.newClassification;
}
} else {
if (this.parentId != null) {
return this.$store.state.currentLanguage.editClassification;
} else {
return this.$store.state.currentLanguage.editSubClassification;
}
}
},
/**
* 设置快捷键
*/
setShortcutKey: CommonJS.setShortcutKey,
/**
* 校验快捷键
*/
checkShortcutKeys() {
this.shortcutKeyCheckMessage = null;
if (!this.strIsEmpty(this.shortcutKey)) {
if (!CommonJS.checkShortcutKeys(this.shortcutKey.trim())) {
this.shortcutKeyCheckMessage = this.$store.state.currentLanguage.shortcutKeyIncompleteMessage;
} else {
if (this.oldShortcutKey == null || this.oldShortcutKey.trim() != this.shortcutKey.trim()) {
//
this.shortcutKeyCheckMessage = CommonJS.checkAppShortcutKeysDuplicate(this.shortcutKey.trim(), this.$store.state.appShortcutKeyMap);
if (this.shortcutKeyCheckMessage == null) {
//
this.shortcutKeyCheckMessage = CommonJS.checkSettingShortcutKeysDuplicate(this.shortcutKey.trim(), this.$store.state.setting, null);
}
}
}
}
if (this.shortcutKeyCheckMessage != null) {
ipcRenderer.send("errorMessage", this.shortcutKeyCheckMessage);
this.shortcutKey = null;
this.tempShortcutKey = null;
this.shortcutKeyCheckMessage = null;
}
},
/**
* 添加修改
*/
addEdit() {
if (this.type == 0) {
this.add();
} else {
this.edit();
}
},
/**
* 添加
*/
add() {
if (!this.strIsEmpty(this.name)) {
//
this.checkShortcutKeys();
if (this.shortcutKeyCheckMessage == null) {
this.$emit("update:show", false);
this.$emit("add", this.name, this.shortcutKey, this.globalShortcutKey, this.parentId);
}
}
},
/**
* 编辑
*/
edit() {
if (!this.strIsEmpty(this.name)) {
//
this.checkShortcutKeys();
if (this.shortcutKeyCheckMessage == null) {
this.$emit("update:show", false);
this.$emit("edit", this.id, this.name, this.shortcutKey, this.globalShortcutKey, this.parentId);
}
}
},
/**
* 关闭
*/
close() {
this.$emit("update:show", false);
},
/**
* 监听键盘
* @param e
*/
keydown(e) {
if (e.keyCode == 13) {
if (!(document.activeElement.id != null && document.activeElement.id == "shortcutKeyInput")) {
this.addEdit();
e.preventDefault();
}
}
},
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,146 @@
<template>
<div
class="h-full"
style="text-shadow: none"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderRadius: $store.state.setting.appearance.backgroundTransparency < 1 && $store.state.setting.appearance.windowRoundedCorners ? '8px' : null,
}"
>
<TopTitleBar :title="$store.state.currentLanguage.aggregateClassification" v-model:show="s"></TopTitleBar>
<div class="px-2">
<span class="block text-xs">{{ $store.state.currentLanguage.aggregateClassificationNote }}</span>
<div class="flex items-center mt-2">
<span class="text-sm h-[34px] block flex items-center">{{ $store.state.currentLanguage.sort }}<span v-html="$getColon()"></span></span>
<Select id="sortSelect" :list="sortList" v-model:value="sort" :width="180" :key="sort"></Select>
</div>
<span class="block text-xs mt-2">{{ $store.state.currentLanguage.aggregateClassificationSortNote }}</span>
<div class="flex items-center justify-center mt-2">
<span class="text-sm h-[34px] block flex items-center">{{ $store.state.currentLanguage.itemNumber }}<span v-html="$getColon()"></span></span>
<Input class="flex-1" v-model:value="itemNumber" @blur="setOpenNumber" type="number" />
</div>
</div>
<div class="flex items-center absolute right-2 bottom-[6px]">
<Button :text="$store.state.currentLanguage.ok" class="w-20 mr-1" @click="edit" type="primary"></Button>
<Button :text="$store.state.currentLanguage.cancel" class="w-20" type="cancel" @click="close"></Button>
</div>
</div>
</template>
<script>
import TopTitleBar from "@/components/TopTitleBar";
import Button from "@/components/Button";
import Input from "@/components/Input";
import ClassificationJS from "@/views/classification/js";
import Select from "@/components/Select";
const { ipcRenderer } = window.require("electron");
export default {
name: "Aggregate",
components: { Input, Button, Select, TopTitleBar },
props: {
// id
id: {
type: Number,
},
// ID
parentId: {
type: Number,
},
//
show: {
type: Boolean,
},
},
data() {
return {
//
s: null,
//
sortList: null,
//
sort: "initial",
//
itemNumber: 50,
//
aggregate: false,
};
},
watch: {
s: function (newData) {
this.$emit("update:show", newData);
},
"$store.state.setting.general.language": {
handler() {
this.setSelectList();
},
},
},
created() {
this.s = this.show;
let classification = ClassificationJS.getClassificationById(this.parentId != null ? this.parentId : this.id, this.parentId != null ? this.id : null);
if (classification.type != null && classification.type == 1) {
this.aggregate = true;
if (classification.aggregateSort != null) {
this.sort = classification.aggregateSort;
}
if (classification.aggregateItemNumber != null) {
this.itemNumber = classification.aggregateItemNumber;
}
}
this.setSelectList();
},
mounted() {
this.setSelectList();
},
methods: {
setSelectList() {
this.sortList = [
{
value: "initial",
label: this.$store.state.currentLanguage.byInitial,
},
{
value: "openNumber",
label: this.$store.state.currentLanguage.byOpenNumber,
},
{
value: "lastOpen",
label: this.$store.state.currentLanguage.byLastOpen,
},
];
},
/**
* 关闭
*/
close() {
this.$emit("update:show", false);
},
/**
* 修改
*/
edit() {
let flag = false;
if (this.aggregate) {
flag = true;
} else {
flag = ipcRenderer.sendSync("showMessageBoxSync", this.$store.state.currentLanguage.aggregateClassificationMessage);
}
if (flag) {
this.$emit("update:show", false);
this.$emit("set", this.id, this.parentId, this.sort, this.itemNumber);
}
},
/**
* 设置项目数量
*/
setOpenNumber() {
if (this.itemNumber == null || typeof this.itemNumber == "string") {
this.itemNumber = 0;
}
},
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,165 @@
<template>
<div
class="h-full"
style="text-shadow: none"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderRadius: $store.state.setting.appearance.backgroundTransparency < 1 && $store.state.setting.appearance.windowRoundedCorners ? '8px' : null,
}"
>
<TopTitleBar :title="$store.state.currentLanguage.associatedFolder" v-model:show="s"></TopTitleBar>
<div class="px-2">
<div>
<textarea
rows="2"
class="w-full resize-none border rounded text-sm py-1 px-2 hover:outline-0 focus-visible:outline-0"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderColor: $store.state.setting.appearance.theme.border,
}"
v-model="mapDirectory"
></textarea>
</div>
<div class="flex items-center">
<Button
:text="$store.state.currentLanguage.browse"
class="w-[30px] h-[30px] mr-1 cursor-pointer"
type="cancel"
icon="folder"
@click="chooseDir"
></Button>
<Button
:text="$store.state.currentLanguage.delete"
class="w-[30px] h-[30px] cursor-pointer"
type="cancel"
icon="delete"
@click="mapDirectory = null"
></Button>
</div>
<div>
<span class="text-sm h-[34px] block flex items-center">{{ $store.state.currentLanguage.hiddenItem }}</span>
<textarea
rows="2"
class="block w-full resize-none border rounded text-sm py-1 px-2 hover:outline-0 focus-visible:outline-0"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderColor: $store.state.setting.appearance.theme.border,
}"
v-model="hiddenItem"
></textarea>
<span class="block text-xs mt-2">{{ $store.state.currentLanguage.hiddenItemNote }}</span>
</div>
</div>
<div class="flex items-center absolute right-2 bottom-[6px]">
<Button :text="$store.state.currentLanguage.ok" class="w-20 mr-1" @click="edit" type="primary"></Button>
<Button :text="$store.state.currentLanguage.cancel" class="w-20" type="cancel" @click="close"></Button>
</div>
</div>
</template>
<script>
import TopTitleBar from "@/components/TopTitleBar";
import Button from "@/components/Button";
import Input from "@/components/Input";
import ClassificationJS from "@/views/classification/js";
import CommonJS from "@/common/index";
const { ipcRenderer } = window.require("electron");
export default {
name: "ClassificationAssociatedFolder",
components: { Input, Button, TopTitleBar },
props: {
// 0: 1:
type: {
type: Number,
},
// id
id: {
type: Number,
},
// ID
parentId: {
type: Number,
},
//
show: {
type: Boolean,
},
},
data() {
return {
s: null,
//
mapDirectory: null,
//
hiddenItem: null,
//
associatedFolder: false,
};
},
watch: {
s: function (newData) {
this.$emit("update:show", newData);
},
},
created() {
this.s = this.show;
let classification = ClassificationJS.getClassificationById(this.parentId != null ? this.parentId : this.id, this.parentId != null ? this.id : null);
this.mapDirectory = classification.mapDirectory;
if (!CommonJS.strIsEmpty(this.mapDirectory)) {
this.associatedFolder = true;
}
this.hiddenItem = classification.hiddenItem;
},
methods: {
/**
* 修改
*/
edit() {
if (this.mapDirectory != null && (this.mapDirectory == "" || this.mapDirectory.trim() == "")) {
this.mapDirectory = null;
}
let flag = false;
if (this.associatedFolder) {
flag = true;
} else {
if (!CommonJS.strIsEmpty(this.mapDirectory)) {
flag = ipcRenderer.sendSync("showMessageBoxSync", this.$store.state.currentLanguage.associatedFolderMessage);
} else {
flag = true;
}
}
if (flag) {
this.$emit("update:show", false);
this.$emit("set", this.id, this.parentId, this.mapDirectory, this.hiddenItem);
}
},
/**
* 关闭
*/
close() {
this.$emit("update:show", false);
},
/**
* 选择文件夹
*/
chooseDir() {
let filePath = ipcRenderer.sendSync(
"openDirectory",
JSON.stringify({
window: "mainWindow",
defaultPath: this.mapDirectory,
})
);
if (!CommonJS.strIsEmpty(filePath)) {
this.mapDirectory = filePath;
}
},
},
};
</script>
<style scoped></style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,59 @@
import store from "@/store";
import CommonJS from "@/common";
export default {
/**
* 根据ID获取分类
* @param parentId
* @param childId
* @returns {null|*}
*/
getClassificationById(parentId, childId) {
if (parentId != null) {
let classificationParent;
for (let c of store.state.list) {
if (c.id == parentId) {
classificationParent = c;
break;
}
}
if (classificationParent != null && childId != null) {
if (!CommonJS.arrayIsEmpty(classificationParent.childList)) {
let classificationChild;
for (let c of classificationParent.childList) {
if (c.id == childId) {
classificationChild = c;
break;
}
}
return classificationChild;
} else {
return classificationParent;
}
} else {
return classificationParent;
}
}
return null;
},
/**
* 转换ID
* @param id
* @param parentId
*/
convertClassificationId(id, parentId) {
return { classificationParentId: parentId != null ? parentId : id, classificationChildId: parentId != null ? id : null };
},
/**
* 获取分类图标
* @param c
* @returns {string}
*/
getIcon(c) {
if (c.icon != null) {
return c.icon + " ";
} else {
return "";
}
},
};

View File

@ -0,0 +1,58 @@
<template>
<div
class="text-sm h-full"
style="text-shadow: none"
:style="{
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
color: $store.state.setting.appearance.theme.fontBasic,
borderRadius: $store.state.setting.appearance.backgroundTransparency < 1 && $store.state.setting.appearance.windowRoundedCorners ? '8px' : null,
}"
>
<TopTitleBar :title="$store.state.currentLanguage.backupRestoreData" v-model:show="s"></TopTitleBar>
<div class="pt-2 flex items-center justify-center">
<Button :text="$store.state.currentLanguage.backup" class="w-20 mr-1" type="primary" @click="backup"></Button>
<Button :text="$store.state.currentLanguage.restore" class="w-20 ml-1" type="primary" @click="restore"></Button>
</div>
</div>
</template>
<script>
import TopTitleBar from "@/components/TopTitleBar";
import Button from "@/components/Button";
const { ipcRenderer } = window.require("electron");
export default {
name: "BackupRestore",
components: { TopTitleBar, Button },
props: {
//
show: {
type: Boolean,
},
},
data() {
return {
s: null,
}
},
created() {
this.s = this.show
},
watch: {
s: function (newData) {
this.$emit("update:show", newData);
},
},
methods: {
backup() {
ipcRenderer.send("backup");
},
restore() {
ipcRenderer.send("restore");
},
},
};
</script>
<style scoped></style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
export default {
};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,523 @@
<template>
<div
class="item-list flex flex-wrap pb-1 w-full min-h-[20px]"
:class="classificationChildItem ? 'pt-1' : ''"
:classification-child-id="classificationChildId"
:classification-child-item="classificationChildItem"
:style="{ fontSize: $store.state.setting.item.fontSize + 'px' }"
@click="parentItemRun($event, false)"
@dblclick="parentItemRun($event, true)"
@mouseover="itemMouseover"
@mouseout="itemMouseout"
>
<template v-if="(layout != null && layout == 'tile') || ($store.state.setting.item.layout == 'tile' && (layout == null || layout == 'default'))">
<template v-for="(item, index) of itemList">
<div
class="item"
:style="[{ height: 'auto' }]"
:key="'item-' + item.id + '-' + index"
:item-id="item.id"
:classification-parent-id="item.classificationParentId != null ? item.classificationParentId : item.classificationId"
:classification-child-id="item.classificationParentId != null ? item.classificationId : null"
:aggregate-classification-parent-id="aggregate ? classificationParentId : null"
:aggregate-classification-child-id="aggregate ? classificationChildId : null"
draggable="true"
:title="getItemTitle(item)"
v-if="showOnly == null || showOnly == 'default' || (showOnly == 'file' && item.type != 1) || (showOnly == 'folder' && item.type == 1)"
>
<div
class="rounded mb-[1px] mr-[1px] inner-item"
:class="[`${$store.state.setting.item.hideItemName ? 'py-3' : 'py-2'}`]"
:item-id="item.id"
:classification-parent-id="item.classificationParentId != null ? item.classificationParentId : item.classificationId"
:classification-child-id="item.classificationParentId != null ? item.classificationId : null"
:item-child="itemChild"
:aggregate-classification-parent-id="aggregate ? classificationParentId : null"
:aggregate-classification-child-id="aggregate ? classificationChildId : null"
:style="{ backgroundColor: isMultiItem(item.id) ? $hexToRGBA($store.state.setting.appearance.theme.minorBackground, 0.3) : null }"
>
<template v-if="item.type != 5 || item.useAppxBackgroundColor == null || !item.useAppxBackgroundColor">
<svg
v-if="isInvalidItem(item)"
class="icon mx-auto"
viewBox="0 0 1024 1024"
:style="[
{ width: getIconSize() + 'px' },
{ height: getIconSize() + 'px' },
{ minWidth: getIconSize() + 'px' },
{ minHeight: getIconSize() + 'px' },
{
filter: $store.state.setting.appearance.useFontShadow
? 'drop-shadow(1px 1px 1px ' + $store.state.setting.appearance.fontShadow + ')'
: null,
},
]"
>
<path
d="M969.182003 701.303054C942.98583 761.818425 907.560805 814.394635 862.909139 859.030351 818.273423 903.682017 765.86402 939.121773 705.727336 965.303215 645.576031 991.515341 581.182839 1004.606061 512.545547 1004.606061 443.925315 1004.606061 379.349482 991.515341 318.818158 965.303215 258.302785 939.121773 205.726577 903.682017 161.090861 859.030351 116.439196 814.393527 80.999438 761.818425 54.817996 701.303054 28.605873 640.772838 15.515152 576.197117 15.515152 507.575665 15.515152 438.938373 28.605873 374.545181 54.817996 314.393878 80.999438 254.257304 116.439196 201.8479 161.090861 157.212073 205.726577 112.560409 258.302785 77.135381 318.818158 50.939208 379.349482 24.743034 443.925203 11.636364 512.545547 11.636364 581.182839 11.636364 645.576031 24.742924 705.727336 50.939208 765.8628 77.1366 818.273311 112.560409 862.909139 157.212073 907.560805 201.848897 942.984611 254.258412 969.182003 314.393878 995.378179 374.545181 1008.484848 438.938373 1008.484848 507.575665 1008.484848 576.197117 995.379287 640.772838 969.182003 701.303054L969.182003 701.303054Z"
fill="#d81e06"
data-spm-anchor-id="a313x.7781069.0.i1"
class="selected"
></path>
<path
d="M512 709.220647C472.332325 709.220647 440.203301 741.349668 440.203301 781.017346 440.203301 820.68502 472.332325 852.814044 512 852.814044 551.667675 852.814044 583.796699 820.68502 583.796699 781.017346 583.796699 741.349668 551.667675 709.220647 512 709.220647L512 709.220647Z"
fill="#FFFFFF"
></path>
<path
d="M512 639.709091C492.184111 639.709091 476.101651 606.196596 476.101651 564.820837L440.203301 227.823683C440.203301 186.447921 472.332325 152.935427 512 152.935427 551.667675 152.935427 583.796699 186.447921 583.796699 227.823683L547.898349 564.820837C547.898349 606.196596 531.815889 639.709091 512 639.709091L512 639.709091Z"
fill="#FFFFFF"
></path>
</svg>
<template v-else>
<div
v-if="item.htmlIcon != null"
class="mx-auto"
:style="[
{ width: getIconSize() + 'px' },
{ height: getIconSize() + 'px' },
{ minWidth: getIconSize() + 'px' },
{ minHeight: getIconSize() + 'px' },
{
filter: $store.state.setting.appearance.useFontShadow
? 'drop-shadow(1px 1px 1px ' + $store.state.setting.appearance.fontShadow + ')'
: null,
},
]"
v-html="sanitize(item.htmlIcon)"
></div>
<img
v-else
:src="getIcon(classificationParentId, classificationChildId, item)"
class="mx-auto"
:style="[
{ width: getIconSize() + 'px' },
{ height: getIconSize() + 'px' },
{ minWidth: getIconSize() + 'px' },
{ minHeight: getIconSize() + 'px' },
{
filter: $store.state.setting.appearance.useFontShadow
? 'drop-shadow(1px 1px 1px ' + $store.state.setting.appearance.fontShadow + ')'
: null,
},
]"
/>
</template>
</template>
<template v-else>
<div
class="mx-auto flex items-center justify-center"
style="background-color: rgb(0, 120, 215)"
:style="[
{ width: getIconSize() + 'px' },
{ height: getIconSize() + 'px' },
{ minWidth: getIconSize() + 'px' },
{ minHeight: getIconSize() + 'px' },
{
filter: $store.state.setting.appearance.useFontShadow
? 'drop-shadow(1px 1px 1px ' + $store.state.setting.appearance.fontShadow + ')'
: null,
},
]"
>
<img
:src="getIcon(classificationParentId, classificationChildId, item)"
:style="[{ width: getIconSize() - 8 + 'px' }, { height: getIconSize() - 8 + 'px' }]"
/>
</div>
</template>
<p
class="text-center mt-2 mx-2"
:class="[
`${
$store.state.setting.item.itemNameRowCount == 2
? $store.state.setting.item.hideEllipsis
? 'item-name-tile-2-no-ellipsis'
: 'item-name-tile-2'
: $store.state.setting.item.hideEllipsis
? 'item-name-tile-1-no-ellipsis'
: 'item-name-tile-1'
}`,
]"
:style="{
filter: $store.state.setting.appearance.useFontShadow ? 'drop-shadow(1px 1px 1px ' + $store.state.setting.appearance.fontShadow + ')' : null,
fontWeight: $store.state.setting.item.fontWeight,
lineHeight: $store.state.setting.item.fontLineHeight + 'rem',
}"
v-if="!$store.state.setting.item.hideItemName"
>
<template v-for="(text, tIndex) of item.name.split('\\n')">
{{ text }}
<br v-if="item.name.split('\\n').length > 1 && tIndex < item.name.split('\\n').length - 1" />
</template>
</p>
</div>
</div>
</template>
</template>
<template v-else-if="(layout != null && layout == 'list') || ($store.state.setting.item.layout == 'list' && (layout == null || layout == 'default'))">
<template v-for="(item, index) of itemList">
<div
class="item"
:style="[{ height: !$store.state.setting.item.hideItemName ? 16 + 1 + getIconSize() + 'px' : 'auto' }]"
:key="'item-' + item.id + '-' + index"
:item-id="item.id"
:classification-parent-id="item.classificationParentId != null ? item.classificationParentId : item.classificationId"
:classification-child-id="item.classificationParentId != null ? item.classificationId : null"
:item-child="itemChild"
:aggregate-classification-parent-id="aggregate ? classificationParentId : null"
:aggregate-classification-child-id="aggregate ? classificationChildId : null"
draggable="true"
:title="getItemTitle(item)"
v-if="showOnly == null || showOnly == 'default' || (showOnly == 'file' && item.type != 1) || (showOnly == 'folder' && item.type == 1)"
>
<div
class="rounded flex items-center mb-[1px] mr-[1px] inner-item"
:class="[`${$store.state.setting.item.hideItemName ? 'py-3' : 'p-2'}`]"
:item-id="item.id"
:classification-parent-id="item.classificationParentId != null ? item.classificationParentId : item.classificationId"
:classification-child-id="item.classificationParentId != null ? item.classificationId : null"
:aggregate-classification-parent-id="aggregate ? classificationParentId : null"
:aggregate-classification-child-id="aggregate ? classificationChildId : null"
:style="{ backgroundColor: isMultiItem(item.id) ? $hexToRGBA($store.state.setting.appearance.theme.minorBackground, 0.3) : null }"
>
<template v-if="item.type != 5 || item.useAppxBackgroundColor == null || !item.useAppxBackgroundColor">
<svg
v-if="isInvalidItem(item)"
class="icon"
:class="[`${$store.state.setting.item.hideItemName ? 'mx-auto' : ''}`]"
viewBox="0 0 1024 1024"
:style="[
{ width: getIconSize() + 'px' },
{ height: getIconSize() + 'px' },
{ minWidth: getIconSize() + 'px' },
{ minHeight: getIconSize() + 'px' },
{
filter: $store.state.setting.appearance.useFontShadow
? 'drop-shadow(1px 1px 1px ' + $store.state.setting.appearance.fontShadow + ')'
: null,
},
]"
>
<path
d="M969.182003 701.303054C942.98583 761.818425 907.560805 814.394635 862.909139 859.030351 818.273423 903.682017 765.86402 939.121773 705.727336 965.303215 645.576031 991.515341 581.182839 1004.606061 512.545547 1004.606061 443.925315 1004.606061 379.349482 991.515341 318.818158 965.303215 258.302785 939.121773 205.726577 903.682017 161.090861 859.030351 116.439196 814.393527 80.999438 761.818425 54.817996 701.303054 28.605873 640.772838 15.515152 576.197117 15.515152 507.575665 15.515152 438.938373 28.605873 374.545181 54.817996 314.393878 80.999438 254.257304 116.439196 201.8479 161.090861 157.212073 205.726577 112.560409 258.302785 77.135381 318.818158 50.939208 379.349482 24.743034 443.925203 11.636364 512.545547 11.636364 581.182839 11.636364 645.576031 24.742924 705.727336 50.939208 765.8628 77.1366 818.273311 112.560409 862.909139 157.212073 907.560805 201.848897 942.984611 254.258412 969.182003 314.393878 995.378179 374.545181 1008.484848 438.938373 1008.484848 507.575665 1008.484848 576.197117 995.379287 640.772838 969.182003 701.303054L969.182003 701.303054Z"
fill="#d81e06"
data-spm-anchor-id="a313x.7781069.0.i1"
class="selected"
></path>
<path
d="M512 709.220647C472.332325 709.220647 440.203301 741.349668 440.203301 781.017346 440.203301 820.68502 472.332325 852.814044 512 852.814044 551.667675 852.814044 583.796699 820.68502 583.796699 781.017346 583.796699 741.349668 551.667675 709.220647 512 709.220647L512 709.220647Z"
fill="#FFFFFF"
></path>
<path
d="M512 639.709091C492.184111 639.709091 476.101651 606.196596 476.101651 564.820837L440.203301 227.823683C440.203301 186.447921 472.332325 152.935427 512 152.935427 551.667675 152.935427 583.796699 186.447921 583.796699 227.823683L547.898349 564.820837C547.898349 606.196596 531.815889 639.709091 512 639.709091L512 639.709091Z"
fill="#FFFFFF"
></path>
</svg>
<template v-else>
<div
v-if="item.htmlIcon != null"
:class="[`${$store.state.setting.item.hideItemName ? 'mx-auto' : ''}`]"
:style="[
{ width: getIconSize() + 'px' },
{ height: getIconSize() + 'px' },
{ minWidth: getIconSize() + 'px' },
{ minHeight: getIconSize() + 'px' },
{
filter: $store.state.setting.appearance.useFontShadow
? 'drop-shadow(1px 1px 1px ' + $store.state.setting.appearance.fontShadow + ')'
: null,
},
]"
v-html="sanitize(item.htmlIcon)"
></div>
<img
v-else
:src="getIcon(classificationParentId, classificationChildId, item)"
:class="[`${$store.state.setting.item.hideItemName ? 'mx-auto' : ''}`]"
:style="[
{ width: getIconSize() + 'px' },
{ height: getIconSize() + 'px' },
{ minWidth: getIconSize() + 'px' },
{ minHeight: getIconSize() + 'px' },
{
filter: $store.state.setting.appearance.useFontShadow
? 'drop-shadow(1px 1px 1px ' + $store.state.setting.appearance.fontShadow + ')'
: null,
},
]"
/>
</template>
</template>
<template v-else>
<div
class="flex items-center justify-center"
style="background-color: rgb(0, 120, 215)"
:style="[
{ width: getIconSize() + 'px' },
{ height: getIconSize() + 'px' },
{ minWidth: getIconSize() + 'px' },
{ minHeight: getIconSize() + 'px' },
{
filter: $store.state.setting.appearance.useFontShadow
? 'drop-shadow(1px 1px 1px ' + $store.state.setting.appearance.fontShadow + ')'
: null,
},
]"
:class="[`${$store.state.setting.item.hideItemName ? 'mx-auto' : ''}`]"
>
<img
:src="getIcon(classificationParentId, classificationChildId, item)"
:style="[{ width: getIconSize() - 8 + 'px' }, { height: getIconSize() - 8 + 'px' }]"
/>
</div>
</template>
<p
class="mx-2"
:class="[`${$store.state.setting.item.hideEllipsis ? 'item-name-list-no-ellipsis' : 'item-name-list'}`]"
:style="{
filter: $store.state.setting.appearance.useFontShadow ? 'drop-shadow(1px 1px 1px ' + $store.state.setting.appearance.fontShadow + ')' : null,
fontWeight: $store.state.setting.item.fontWeight,
lineHeight: $store.state.setting.item.fontLineHeight + 'rem',
}"
v-if="!$store.state.setting.item.hideItemName"
>
{{ getItemName(item.name) }}
</p>
</div>
</div>
</template>
</template>
</div>
</template>
<script>
import ItemJS from "@/views/item/js/index.js";
import CommonJS from "@/common";
import ClassificationJS from "@/views/classification/js/index.js";
export default {
name: "ItemList",
props: {
//
itemList: {
type: Array,
},
// ID
classificationParentId: {
type: Number,
},
//
itemChild: {
type: Boolean,
},
// ID
classificationChildId: {
type: Number,
},
//
classificationChildItem: {
type: Boolean,
},
//
layout: {
type: String,
},
//
iconSize: {
type: Number,
},
//
invalidItemList: {
type: Array,
},
//
itemSorting: {
type: Boolean,
},
//
dragOut: {
type: Boolean,
},
//
batchOperation: {
type: Boolean,
},
// Map
itemSortingMap: {
type: Map,
},
// ..
showOnly: {
type: String,
},
},
data() {
return {
aggregate: false,
};
},
created() {
let classification = ClassificationJS.getClassificationById(this.classificationParentId, this.classificationChildId);
if (classification.type != null && classification.type == 1) {
this.aggregate = true;
}
},
methods: {
/**
* 判断数组是否等于空
*/
arrayIsEmpty: CommonJS.arrayIsEmpty,
/**
* 过滤XSS
*/
sanitize: CommonJS.DOMPurify.sanitize,
/**
* 获取图标
*/
getIcon(classificationParentId, classificationChildId, item) {
if (this.aggregate) {
let { classificationParentId, classificationChildId } = ClassificationJS.convertClassificationId(item.classificationId, item.classificationParentId);
return CommonJS.getIcon(classificationParentId, classificationChildId, item.id);
} else {
return CommonJS.getIcon(classificationParentId, classificationChildId, item.id);
}
},
/**
* 获取项目名称
*/
getItemName: ItemJS.getName,
/**
* 父级点击运行
* @param e
* @param dblclick
*/
parentItemRun(e, dblclick) {
// inner-item
let target = this.$getClassElement(e, "inner-item");
if (target != null) {
// ID
let classificationParentId = target.getAttribute("classification-parent-id");
let classificationChildId = target.getAttribute("classification-child-id");
// ID
let itemId = target.getAttribute("item-id");
//
if (classificationParentId != null && itemId != null) {
let classification = ClassificationJS.getClassificationById(classificationParentId, classificationChildId);
if (classification != null && !this.arrayIsEmpty(classification.itemList)) {
let item = ItemJS.getItemById(classification, itemId);
if (item != null) {
this.itemRun(item, dblclick);
}
}
}
}
},
/**
* 运行项目
* @param item
* @param dblclick
*/
itemRun(item, dblclick) {
//
if (this.batchOperation) {
return;
}
if (dblclick) {
if (
this.$store.state.setting != null &&
this.$store.state.setting.item != null &&
this.$store.state.setting.item.doubleClickRunItem != null &&
this.$store.state.setting.item.doubleClickRunItem
) {
//
ItemJS.itemRun(item, false);
}
} else {
if (
this.$store.state.setting == null ||
this.$store.state.setting.general == null ||
this.$store.state.setting.item.doubleClickRunItem == null ||
!this.$store.state.setting.item.doubleClickRunItem
) {
//
ItemJS.itemRun(item, false);
}
}
},
/**
* 获取图标大小
*/
getIconSize() {
return this.iconSize != null ? this.iconSize : this.$store.state.setting.item.iconSize;
},
/**
* 获取项目标题
*/
getItemTitle: ItemJS.getItemTitle,
/**
* 获取是否是无效项目
*/
isInvalidItem(item) {
if (!this.$store.state.setting.item.checkInvalidItem) {
return false;
}
let key;
if (item.classificationParentId != null) {
key = item.classificationParentId + "-" + item.classificationId + "-" + item.id;
} else {
key = item.classificationId + "-" + item.id;
}
if (!this.arrayIsEmpty(this.invalidItemList) && this.invalidItemList.indexOf(key) >= 0) {
return true;
} else {
return false;
}
},
/**
* 项目鼠标悬浮
*/
itemMouseover(e) {
if (!this.itemSorting && !this.dragOut && !this.batchOperation) {
this.$styleMouseover(e, "inner-item", ["background-color"], [this.$hexToRGBA(this.$store.state.setting.appearance.theme.minorBackground, 0.3)]);
}
},
/**
* 项目鼠标移出
* @param e
*/
itemMouseout(e) {
if (!this.batchOperation) {
this.$styleMouseout(e, "inner-item", ["background-color"]);
}
},
/**
* 是否被多选
*/
isMultiItem(id) {
return (
this.batchOperation &&
this.itemSortingMap != null &&
this.itemSortingMap.get(CommonJS.getKey(this.classificationParentId, this.classificationChildId, id)) != null
);
},
},
};
</script>
<style scoped>
.item-name-list {
word-break: break-all;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
line-clamp: 1;
-webkit-box-orient: vertical;
}
.item-name-list-no-ellipsis {
max-height: 20px;
word-break: break-all;
overflow: hidden;
}
</style>

View File

@ -0,0 +1,127 @@
<template>
<div
class="text-sm h-full"
style="text-shadow: none"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderRadius: $store.state.setting.appearance.backgroundTransparency < 1 && $store.state.setting.appearance.windowRoundedCorners ? '8px' : null,
}"
>
<TopTitleBar :title="$store.state.currentLanguage.svgCodeIcon" v-model:show="s"></TopTitleBar>
<div class="px-2">
<div>
<div class="flex items-center">
<div
v-if="strIsEmpty(svgHtml)"
class="w-[40px] h-[40px] min-w-[40px] min-h-[40px] border rounded flex items-center justify-center"
:style="{ borderColor: $store.state.setting.appearance.theme.border }"
></div>
<div v-else class="w-[40px] h-[40px] min-w-[40px] min-h-[40px]" v-html="sanitize(svgHtml)"></div>
<span class="block text-xs ml-2">{{ $store.state.currentLanguage.svgCodeIconValidationNote }}</span>
</div>
<textarea
rows="3"
class="mt-2 block w-full resize-none border rounded text-sm py-1 px-2 hover:outline-0 focus-visible:outline-0"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderColor: $store.state.setting.appearance.theme.border,
}"
:placeholder="$store.state.currentLanguage.svgCodeIconPlaceholder"
v-model="svg"
></textarea>
<Button text="校验" class="w-20 mt-2" type="primary" @click="check"></Button>
</div>
</div>
<div class="flex items-center absolute right-2 bottom-[6px]">
<Button :text="$store.state.currentLanguage.ok" class="w-20 mr-1" type="primary" @click="set"></Button>
<Button :text="$store.state.currentLanguage.cancel" class="w-20" type="cancel" @click="close"></Button>
</div>
</div>
</template>
<script>
import CommonJS from "@/common";
import TopTitleBar from "@/components/TopTitleBar";
import Input from "@/components/Input";
import Button from "@/components/Button";
const { ipcRenderer } = window.require("electron");
export default {
name: "SVGIcon",
components: { Input, Button, TopTitleBar },
props: {
//
show: {
type: Boolean,
},
},
data() {
return {
s: null,
// svg
svg: null,
// svg
svgHtml: null,
};
},
watch: {
s: function (newData) {
this.$emit("update:show", newData);
},
},
created() {
this.s = this.show;
},
methods: {
/**
* 判断字符串是否为空
*/
strIsEmpty: CommonJS.strIsEmpty,
/**
* 过滤XSS
*/
sanitize: CommonJS.DOMPurify.sanitize,
/**
* 关闭
*/
close() {
this.$emit("update:show", false);
},
/**
* 校验
*/
check() {
if (!this.strIsEmpty(this.svg)) {
let svg = this.sanitize(this.svg);
const parser = new DOMParser();
const doc = parser.parseFromString(svg, "image/svg+xml");
const svgElements = doc.getElementsByTagName("svg");
if (svgElements.length == 1) {
const serializer = new XMLSerializer();
let svgElement = svgElements[0];
svgElement.removeAttribute("class");
svgElement.removeAttribute("style");
svgElement.setAttribute("width", "100%");
svgElement.setAttribute("height", "100%");
this.svgHtml = serializer.serializeToString(svgElement);
this.svg = this.svgHtml;
return;
}
}
this.svgHtml = null;
this.svg = null;
},
/**
* 设置图标
*/
set() {
this.$emit("set", this.svgHtml);
this.close();
},
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,150 @@
<template>
<div
class="text-sm h-full"
style="text-shadow: none"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderRadius: $store.state.setting.appearance.backgroundTransparency < 1 && $store.state.setting.appearance.windowRoundedCorners ? '8px' : null,
}"
>
<TopTitleBar :title="$store.state.currentLanguage.networkIcon" v-model:show="s"></TopTitleBar>
<div class="px-2">
<div>
<div class="flex items-center">
<div
v-if="icon == null"
class="w-[40px] h-[40px] min-w-[40px] min-h-[40px] border rounded flex items-center justify-center"
:style="{ borderColor: $store.state.setting.appearance.theme.border }"
></div>
<img v-else :src="icon" class="w-[40px] h-[40px]" />
<span class="block text-xs ml-2">{{ $store.state.currentLanguage.networkIconNote }}</span>
</div>
<textarea
rows="3"
class="mt-2 block w-full resize-none border rounded text-sm py-1 px-2 hover:outline-0 focus-visible:outline-0"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderColor: $store.state.setting.appearance.theme.border,
}"
:placeholder="$store.state.currentLanguage.networkIconPlaceholder"
v-model="url"
></textarea>
<div class="flex mt-2 items-center">
<Button :text="$store.state.currentLanguage.getIcon" class="w-20" type="primary" @click="downloadImage"></Button>
<span class="ml-auto" v-if="urlGetting">{{ urlGettingMessage }}</span>
<span class="ml-auto" v-if="urlError">{{ urlErrorMessage }}</span>
</div>
</div>
</div>
<div class="flex items-center absolute right-2 bottom-[6px]">
<Button :text="$store.state.currentLanguage.ok" class="w-20 mr-1" type="primary" @click="set"></Button>
<Button :text="$store.state.currentLanguage.cancel" class="w-20" type="cancel" @click="close"></Button>
</div>
</div>
</template>
<script>
import CommonJS from "@/common";
import TopTitleBar from "@/components/TopTitleBar";
import Input from "@/components/Input";
import Button from "@/components/Button";
const { ipcRenderer } = window.require("electron");
export default {
name: "URLIcon",
components: { Input, Button, TopTitleBar },
props: {
//
show: {
type: Boolean,
},
},
data() {
return {
s: null,
// url
url: null,
//
icon: null,
//
urlGetting: false,
urlGettingMessage: null,
urlGettingInterval: null,
//
urlError: false,
urlErrorMessage: null,
};
},
watch: {
s: function (newData) {
this.$emit("update:show", newData);
},
},
created() {
this.s = this.show;
},
mounted() {
//
ipcRenderer.on("returnDownloadImage", (event, args) => {
this.urlGetting = false;
let result = JSON.parse(args);
if (result.status) {
this.icon = result.icon;
} else {
this.icon = null;
this.urlError = true;
this.urlErrorMessage = result.message;
}
clearInterval(this.urlGettingInterval);
this.urlGettingMessage = null;
});
},
unmounted() {
clearInterval(this.urlGettingInterval);
},
methods: {
/**
* 判断字符串是否为空
*/
strIsEmpty: CommonJS.strIsEmpty,
/**
* 关闭
*/
close() {
this.$emit("update:show", false);
},
/**
* 下载图标
*/
downloadImage() {
if (!this.strIsEmpty(this.url)) {
this.urlGetting = true;
this.urlError = false;
this.urlErrorMessage = null;
ipcRenderer.send("downloadImage", this.url);
let _this = this;
this.urlGettingMessage = this.$store.state.currentLanguage.gettingUrlInfo + "...";
this.urlGettingInterval = setInterval(() => {
let split = _this.urlGettingMessage.split(".");
if (split.length < 4) {
_this.urlGettingMessage += ".";
} else {
_this.urlGettingMessage = _this.$store.state.currentLanguage.gettingUrlInfo + ".";
}
}, 500);
}
},
/**
* 设置图标
*/
set() {
this.$emit("set", this.icon);
this.close();
},
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,222 @@
import store from "@/store";
import CommonJS from "@/common";
const { ipcRenderer } = window.require("electron");
export default {
/**
* 运行项目
* @param item
* @param location
* @param recordQuickSearch 记录快速搜索数据
*/
itemRun(item, location, recordQuickSearch) {
if (item.type == 4) {
if (!CommonJS.arrayIsEmpty(item.itemList)) {
for (let iItem of item.itemList) {
ipcRenderer.send(
"itemRun",
JSON.stringify({
item: iItem,
location: false,
recordQuickSearch: recordQuickSearch != null ? recordQuickSearch : false,
})
);
}
}
} else {
ipcRenderer.send(
"itemRun",
JSON.stringify({
item: item,
location: location,
recordQuickSearch: recordQuickSearch != null ? recordQuickSearch : false,
})
);
}
if (store.state.setting.item.openAfterHideMainInterface == true) {
ipcRenderer.send("hideMainWindow");
}
},
/**
* 查询项目
* @param classification
* @param id
*/
getItemById(classification, id) {
for (let item of classification.itemList) {
if (item.id == id) {
return item;
}
}
return null;
},
/**
* 设置项目分类ID
* @param item
* @param classificationParentId
* @param classificationChildId
*/
setItemClassificationId(item, classificationParentId, classificationChildId) {
if (classificationChildId != null) {
item.classificationParentId = classificationParentId;
item.classificationId = classificationChildId;
} else {
item.classificationId = classificationParentId;
item.classificationParentId = null;
}
return item;
},
/**
* 获取标题
* @param item
*/
getItemTitle(item) {
let name = this.$store.state.currentLanguage.name + this.$store.state.currentLanguage.colon + item.name.replace(/\\n/g, " ");
if (this.$store.state.setting.item.openNumber) {
name += "\n" + this.$store.state.currentLanguage.openNumber + this.$store.state.currentLanguage.colon + (item.openNumber == null ? 0 : item.openNumber);
}
if (!CommonJS.strIsEmpty(item.remark)) {
name += "\n" + this.$store.state.currentLanguage.remark + this.$store.state.currentLanguage.colon + item.remark;
}
return name;
},
/**
* 设置拼音
*/
setPinyin(item) {
// if (hasChinese(item.name)) {
// // 拼音
// let pinyin = cnchar.spell(item.name, "array", "poly");
// let pinyinList = [];
// if (pinyin != null && pinyin.length > 0) {
// for (let py of pinyin) {
// let newList = [];
// let list = py.replace("(", "").replace(")", "").split("|");
// for (let str of list) {
// if (!newList.some((nStr) => str == nStr)) {
// newList.push(str);
// }
// }
// pinyinList.push(newList);
// }
// }
// if (pinyinList.length > 0) {
// item.pinyin = merge(pinyinList);
// }
// // 首字母
// let pinyinFirst = cnchar.spell(item.name, "array", "poly", "first");
// let pinyinFirstList = [];
// if (pinyinFirst != null && pinyinFirst.length > 0) {
// for (let py of pinyinFirst) {
// let newList = [];
// let list = py.replace("(", "").replace(")", "").split("|");
// for (let str of list) {
// if (!newList.some((nStr) => str == nStr)) {
// newList.push(str);
// }
// }
// pinyinFirstList.push(newList);
// }
// }
// if (pinyinFirstList.length > 0) {
// item.initial = merge(pinyinFirstList);
// }
// }
},
/**
* 设置缩写
* @param item
*/
setAbbr(item) {
if (!CommonJS.strIsEmpty(item.name)) {
let name = item.name.replace(/\\n/g, " ");
let split = name.split(" ");
let abbr = "";
for (let i = 0; i < split.length; i++) {
if (!CommonJS.strIsEmpty(split[i])) {
abbr += split[i].slice(0, 1);
}
}
item.abbr = abbr;
}
},
/**
* 获取名称
*/
getName(name) {
return name.replace(/\\n/g, " ");
},
/**
* 排序
* @param type
* @param key
*/
sort(itemList, type, key) {
if (type == "initial") {
return itemList.sort((x, y) => {
let xn, yn;
if (!CommonJS.strIsEmpty(x.pinyin)) {
xn = x.pinyin.toLowerCase().charAt(0);
} else {
xn = x.name.toLowerCase().charAt(0);
}
if (!CommonJS.strIsEmpty(y.pinyin)) {
yn = y.pinyin.toLowerCase().charAt(0);
} else {
yn = y.name.toLowerCase().charAt(0);
}
// 判断是否是数字
if (isNaN(xn) == false && isNaN(yn) == true) {
// xn是数字而yn不是返回1
return 1;
}
if (isNaN(yn) == false && isNaN(xn) == true) {
// yn是数字而xn不是返回-1
return -1;
}
if (xn < yn) {
return -1;
}
if (xn > yn) {
return 1;
}
if (xn == yn) {
if (x.name < y.name) {
return -1;
}
if (x.name < y.name) {
return 1;
}
}
return 0;
});
} else if (type == "openNumber" || type == "lastOpen") {
return itemList.sort((x, y) => {
let xn = x[key] == null ? 0 : x[key];
let yn = y[key] == null ? 0 : y[key];
if (xn < yn) {
return -1;
}
if (xn > yn) {
return 1;
}
if (xn == yn) {
if (x.name < y.name) {
return -1;
}
if (x.name < y.name) {
return 1;
}
}
return 0;
});
}
},
/**
* 是否是绝对路径
*/
isAbsolutePath(path) {
const regex = /^[a-zA-Z]:\\/;
return regex.test(path);
},
};

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,356 @@
<template>
<div
id="search"
class="top-[34px] fixed z-10 w-4/5 drop-shadow-[0_0_6px_rgba(0,0,0,0.2)]"
style="text-shadow: none"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderRadius: $store.state.setting.appearance.backgroundTransparency < 1 && $store.state.setting.appearance.windowRoundedCorners ? '8px' : null,
}"
>
<div
class="flex items-center h-[34px]"
:class="`${!arrayIsEmpty(resultList) ? 'border-b-[1px] !h-[35px]' : ''}`"
:style="{ borderColor: $store.state.setting.appearance.theme.border }"
>
<div class="mx-2 whitespace-nowrap">
<svg class="w-[18px] h-[18px]" :style="{ color: $store.state.setting.appearance.theme.fontBasic }" viewBox="0 0 24 24" v-if="!sourceSearch">
<path
fill="currentColor"
d="M9.5,3A6.5,6.5 0 0,1 16,9.5C16,11.11 15.41,12.59 14.44,13.73L14.71,14H15.5L20.5,19L19,20.5L14,15.5V14.71L13.73,14.44C12.59,15.41 11.11,16 9.5,16A6.5,6.5 0 0,1 3,9.5A6.5,6.5 0 0,1 9.5,3M9.5,5C7,5 5,7 5,9.5C5,12 7,14 9.5,14C12,14 14,12 14,9.5C14,7 12,5 9.5,5Z"
/>
</svg>
<span class="text-sm block" v-else>{{ sourceSearch.name }}</span>
</div>
<input
type="text"
v-model="name"
ref="searchInput"
class="w-full resize-none text-sm py-1 hover:outline-0 focus-visible:outline-0"
:style="{
color: $store.state.setting.appearance.theme.fontBasic,
backgroundColor: $store.state.setting.appearance.theme.mainBackground,
borderColor: $store.state.setting.appearance.theme.border,
}"
/>
<Close @click="close" :key="'close-' + $store.state.setting.appearance.theme.name"></Close>
</div>
<ul
id="search-result-list"
style="overflow-x: hidden"
@click="parentItemRun($event)"
@mouseover="parentMouseover($event)"
@mouseout="parentMouseout($event)"
data-simplebar
>
<li
v-for="(item, index) of resultList"
class="flex items-center p-2 h-[48px] search-result-item"
:key="'search-item-' + item.id + '-' + index"
:id="'result-' + index"
:item-id="item.id"
:classification-parent-id="String(item.classificationIds).split('-').length == 2 ? item.classificationIds.split('-')[0] : item.classificationId"
:classification-child-id="String(item.classificationIds).split('-').length == 2 ? item.classificationIds.split('-')[1] : null"
:item-child="String(item.classificationIds).split('-').length == 2"
:title="getItemTitle(item)"
:index="index"
:style="{ backgroundColor: selected == index ? $hexToRGBA($store.state.setting.appearance.theme.minorBackground, 0.3) : null }"
>
<template v-if="item.type != 5 || item.useAppxBackgroundColor == null || !item.useAppxBackgroundColor">
<div v-if="item.htmlIcon != null" class="w-8 h-8 min-w-[32px] min-h-[32px]" v-html="sanitize(item.htmlIcon)"></div>
<img v-else :src="getIconByClassification(item)" class="w-8 h-8 min-w-[32px] min-h-[32px]" />
</template>
<div v-else class="flex items-center justify-center w-8 h-8 min-w-[32px] min-h-[32px]" style="background-color: rgb(0, 120, 215)">
<img :src="getIconByClassification(item)" :style="[{ width: 32 - 8 + 'px' }, { height: 32 - 8 + 'px' }]" />
</div>
<span class="text-sm ml-2 overflow-hidden text-ellipsis whitespace-nowrap h-[20px]"
>{{ getItemName(item.name) }}<span class="text-xs ml-2">({{ item.classificationName }})</span></span
>
</li>
</ul>
</div>
</template>
<script>
import "simplebar";
import "simplebar/dist/simplebar.css";
import ItemJS from "@/views/item/js/index.js";
import CommonJS from "@/common";
import Close from "@/components/Close";
import IndexJS from "./js/index.js";
import ClassificationJS from "@/views/classification/js/index.js";
const { ipcRenderer } = window.require("electron");
export default {
name: "SearchIndex",
components: { Close },
props: {
show: {
type: Boolean,
},
},
data() {
return {
//
name: null,
// map
itemMap: null,
//
resultList: null,
//
selected: null,
//
webSearch: false,
sourceSearch: null,
};
},
created() {
this.itemMap = IndexJS.convertToMap(this.$store.state.list);
},
mounted() {
this.$nextTick(() => {
//
this.$refs.searchInput.focus();
});
this.resize();
window.addEventListener("resize", this.resize, true);
window.addEventListener("keydown", this.keydown, true);
},
unmounted() {
window.removeEventListener("resize", this.resize, true);
window.removeEventListener("keydown", this.keydown, true);
},
watch: {
name() {
this.search();
},
},
methods: {
/**
* 判断数组是否等于空
*/
arrayIsEmpty: CommonJS.arrayIsEmpty,
/**
* 判断字符串是否为空
*/
strIsEmpty: CommonJS.strIsEmpty,
/**
* 过滤XSS
*/
sanitize: CommonJS.DOMPurify.sanitize,
/**
* 获取图标根据分类
*/
getIconByClassification: CommonJS.getIconByClassification,
/**
* 获取项目名称
*/
getItemName: ItemJS.getName,
/**
* 重新设置宽高
*/
resize() {
// body
let body = document.querySelector("body");
// search
let search = document.getElementById("search");
// result
let result = document.getElementById("search-result-list");
//
search.style.left = (body.clientWidth - search.clientWidth) / 2 + "px";
//
if (result != null) {
//
result.style.maxHeight = body.clientHeight - 70 * 2 + "px";
}
},
/**
* 搜索
*/
search() {
if (!this.webSearch) {
this.resultList = IndexJS.search(this.name, this.itemMap);
if (!this.arrayIsEmpty(this.resultList)) {
this.selected = 0;
this.$nextTick(() => {
let listEL = document.getElementById("search-result-list");
if (listEL != null) {
let sim = listEL.getElementsByClassName("simplebar-content-wrapper")[0];
if (sim != null) {
sim.scrollTop = 0;
}
}
});
}
}
},
/**
* 父级点击运行
* @param e
*/
parentItemRun(e) {
// search-result-item
let target = this.$getClassElement(e, "search-result-item");
if (target != null) {
// ID
let classificationParentId = target.getAttribute("classification-parent-id");
let classificationChildId = target.getAttribute("classification-child-id");
// ID
let itemId = target.getAttribute("item-id");
//
if (classificationParentId != null && itemId != null) {
let classification = ClassificationJS.getClassificationById(classificationParentId, classificationChildId);
if (classification != null && !this.arrayIsEmpty(classification.itemList)) {
let item = ItemJS.getItemById(classification, itemId);
if (item != null) {
this.itemRun(item);
}
}
}
}
},
/**
* 运行项目
*/
itemRun(item) {
ItemJS.itemRun(item, null);
this.close();
},
/**
* 父级鼠标悬浮
*/
parentMouseover(e) {
this.$styleMouseover(e, "search-result-item", ["background-color"], [this.$hexToRGBA(this.$store.state.setting.appearance.theme.minorBackground, 0.3)]);
},
/**
* 父级鼠标离开
*/
parentMouseout(e) {
//
let target = this.$getClassElement(e, "search-result-item");
if (target != null) {
let index = target.getAttribute("index");
if (index != null) {
if (Number(index) != this.selected) {
this.$styleMouseout(e, "search-result-item", ["background-color"]);
}
}
}
},
/**
* 关闭
*/
close() {
this.webSearch = false;
this.sourceSearch = null;
this.$emit("update:show", false);
},
keydown(e) {
//
let shortcutKey = CommonJS.setShortcutKey(e, null, false);
//
if (this.$store.state.setting != null) {
if (this.$store.state.setting.item != null) {
if (this.$store.state.setting.item.searchShortcutKey != null && shortcutKey == this.$store.state.setting.item.searchShortcutKey) {
this.close();
e.preventDefault();
return;
}
}
}
// 38 40
if (e.keyCode == 38 || e.keyCode == 40) {
e.preventDefault();
if (!this.arrayIsEmpty(this.resultList)) {
if (e.keyCode == 38 && this.selected > 0) {
this.selected--;
this.searchResultDivMoveScroll(0);
return;
}
if (e.keyCode == 40 && this.selected < this.resultList.length - 1) {
this.selected++;
this.searchResultDivMoveScroll(1);
return;
}
}
}
//
if (e.keyCode == 32) {
if (!this.webSearch) {
//
if (!this.strIsEmpty(this.name)) {
let flag = false;
if (this.$store.state.setting.webSearch.mode == 0) {
if (this.name.substring(0, 1) == ":") {
flag = true;
}
} else if (this.$store.state.setting.webSearch.mode == 1) {
flag = true;
}
if (flag) {
let keyword = this.$store.state.setting.webSearch.mode == 0 ? this.name.substring(1) : this.name;
for (let searchSource of this.$store.state.setting.webSearch.searchSourceList) {
if (keyword == searchSource.keyword) {
this.webSearch = true;
this.sourceSearch = searchSource;
this.name = null;
this.resultList = null;
this.selected = null;
e.preventDefault();
}
}
}
}
}
}
// enter
if (e.keyCode == 13) {
e.preventDefault();
if (this.webSearch) {
let URL = this.sourceSearch.URL.replace("{w}", this.name == null ? "" : this.name);
ipcRenderer.send("openUrl", URL);
this.close();
} else {
if (this.selected != null && this.resultList.length - 1 >= this.selected) {
this.itemRun(this.resultList[this.selected]);
}
}
}
// 退
if (e.keyCode == 8) {
if (this.webSearch) {
if (this.name == null || this.name == "") {
this.webSearch = false;
this.sourceSearch = null;
}
}
}
},
/**
* 移动滚动条
* @param type 0 1
*/
searchResultDivMoveScroll(type) {
let itemEl = document.getElementById("result-" + this.selected);
let itemRect = itemEl.getBoundingClientRect();
let listEL = document.getElementById("search-result-list");
let realY = itemRect.y - 34 - 34 - 1 + 48;
if (type == 0) {
if (realY < 48) {
let sim = listEL.getElementsByClassName("simplebar-content-wrapper")[0];
sim.scrollTop = sim.scrollTop - (48 - realY);
}
} else {
if (realY > listEL.clientHeight) {
let sim = listEL.getElementsByClassName("simplebar-content-wrapper")[0];
sim.scrollTop = sim.scrollTop + (realY - listEL.clientHeight);
}
}
},
getItemTitle: ItemJS.getItemTitle,
},
};
</script>
<style scoped></style>

View File

@ -0,0 +1,806 @@
<template>
<div
class="max-h-[525px]"
style="overflow-x: hidden; text-shadow: none"
:style="{ backgroundColor: $store.state.setting.appearance.theme.mainBackground, color: $store.state.setting.appearance.theme.fontBasic }"
>
<div
class="flex items-center h-[44px]"
:class="`${!arrayIsEmpty(resultList) ? 'border-b-[1px] !h-[45px]' : ''}`"
:style="{ borderColor: $store.state.setting.appearance.theme.border }"
>
<div class="mx-2 whitespace-nowrap">
<svg class="w-[24px] h-[24px] app-region-drag" viewBox="0 96 960 960" v-if="!sourceSearch">
<path
fill="currentColor"
d="M779.385 902.154 528.923 651.693q-30 25.538-69 39.538-39 14-78.385 14-96.1 0-162.665-66.529-66.566-66.529-66.566-162.577t66.529-162.702q66.529-66.654 162.577-66.654 96.049 0 162.702 66.565Q610.769 379.899 610.769 476q0 41.692-14.769 80.692-14.769 39-38.769 66.693l250.462 250.461-28.308 28.308ZM381.538 665.231q79.616 0 134.423-54.808Q570.769 555.615 570.769 476q0-79.615-54.808-134.423-54.807-54.808-134.423-54.808-79.615 0-134.423 54.808Q192.308 396.385 192.308 476q0 79.615 54.807 134.423 54.808 54.808 134.423 54.808Z"
/>
</svg>
<span class="text-2xl block app-region-drag" v-else>{{ sourceSearch.name }}</span>
</div>
<input
type="text"
v-model="nameOne"
id="nameOne"
class="w-full resize-none text-2xl pr-2 font-light hover:outline-0 focus-visible:outline-0"
:style="{ backgroundColor: $store.state.setting.appearance.theme.mainBackground, color: $store.state.setting.appearance.theme.fontBasic }"
:placeholder="sourceSearch != null && !strIsEmpty(sourceSearch.description) && webSearch ? sourceSearch.description : 'Dawn Launcher'"
/>
<input
type="text"
v-model="nameTwo"
id="nameTwo"
class="w-full resize-none text-2xl pr-2 font-light hover:outline-0 focus-visible:outline-0 hidden"
:style="{ backgroundColor: $store.state.setting.appearance.theme.mainBackground, color: $store.state.setting.appearance.theme.fontBasic }"
:placeholder="sourceSearch != null && !strIsEmpty(sourceSearch.description) && webSearch ? sourceSearch.description : 'Dawn Launcher'"
/>
</div>
<ul
id="search-result-list"
class="max-h-[480px]"
style="overflow-x: hidden"
v-show="resultList != null && resultList.length > 0"
@click="parentItemRun($event)"
@mouseover="parentMouseover($event)"
@mouseout="parentMouseout($event)"
data-simplebar
v-cloak
>
<li
v-for="(item, index) of resultList"
class="flex items-center p-2 h-[48px] search-result-item"
:key="'search-item-' + item.id + '-' + index"
:id="'result-' + index"
:item-id="item.id"
:classification-parent-id="String(item.classificationIds).split('-').length == 2 ? item.classificationIds.split('-')[0] : item.classificationId"
:classification-child-id="String(item.classificationIds).split('-').length == 2 ? item.classificationIds.split('-')[1] : null"
:item-child="String(item.classificationIds).split('-').length == 2"
:title="getItemTitle(item)"
:index="index"
:style="{ backgroundColor: selected == index ? $hexToRGBA($store.state.setting.appearance.theme.minorBackground, 0.3) : null }"
>
<template v-if="item.type != 5 || item.useAppxBackgroundColor == null || !item.useAppxBackgroundColor">
<div v-if="item.htmlIcon != null" class="w-8 h-8 min-w-[32px] min-h-[32px]" v-html="sanitize(item.htmlIcon)"></div>
<img v-else :src="getIconByClassification(item)" class="w-8 h-8 min-w-[32px] min-h-[32px]" />
</template>
<div v-else class="flex items-center justify-center w-8 h-8 min-w-[32px] min-h-[32px]" style="background-color: rgb(0, 120, 215)">
<img :src="getIconByClassification(item)" :style="[{ width: 32 - 8 + 'px' }, { height: 32 - 8 + 'px' }]" />
</div>
<span class="text-sm ml-2 overflow-hidden text-ellipsis whitespace-nowrap h-[20px] flex-1 pr-[10px]"
>{{ getItemName(item.name) }}<span class="text-xs ml-2">({{ item.classificationName }})</span></span
>
<span
class="ml-auto text-[12px]"
v-if="$store.state.setting.quickSearch.openShortcutKey != 'none' && index <= 9"
:style="{ color: $hexToRGBA($store.state.setting.appearance.theme.fontBasic, 0.5) }"
>{{
showHistory
? "Alt + "
: $store.state.setting.quickSearch.openShortcutKey == "numberKey"
? $store.state.currentLanguage.numberKey
: $store.state.setting.quickSearch.openShortcutKey == "ctrlNumberKey"
? "Ctrl + "
: "Alt + "
}}{{ index + 1 == 10 ? 0 : index + 1 }}</span
>
</li>
</ul>
</div>
</template>
<script>
import "simplebar";
import "simplebar/dist/simplebar.css";
import IndexJS from "./js/index.js";
import ItemJS from "@/views/item/js";
import ClassificationJS from "@/views/classification/js";
import CommonJS from "@/common";
const { ipcRenderer } = window.require("electron");
export default {
name: "searchWindow",
data() {
return {
//
list: [],
//
nameOne: null,
nameTwo: null,
//
setting: null,
// map
itemMap: null,
//
resultList: null,
//
selected: null,
// watch
nameWatch: null,
// 使
nameSwitch: "nameOne",
//
webSearch: false,
sourceSearch: null,
//
showHistory: false,
};
},
created() {
//
this.getData(null, null);
//
this.getIconData();
},
mounted() {
//
ipcRenderer.on("getIconData", () => {
this.getIconData();
});
//
ipcRenderer.on("searchWindowGetData", () => {
this.getData(null, null);
});
//
ipcRenderer.on("hideSearchWindowBefore", () => {
this.hideWindow();
});
//
ipcRenderer.on("hideSearchWindowOperation", () => {
//
document.getElementById("nameOne").style.display = "none";
document.getElementById("nameTwo").style.display = "none";
document.getElementById(this.nameSwitch).style.display = "block";
});
//
ipcRenderer.on("showSearchWindowOperation", (event, args) => {
let params = JSON.parse(args);
//
document.getElementById(this.nameSwitch).focus();
// name
this.nameWatch = this.$watch(this.nameSwitch, () => {
this.search();
});
//
this.getData(params.setting, params.list);
this.$nextTick(() => {
//
let height = this.arrayIsEmpty(this.resultList) ? 0 : this.resultList.length > 9 ? 10 * 48 : this.resultList.length * 48;
//
ipcRenderer.send("searchWindowShow", height + 44 + 1);
});
});
//
ipcRenderer.on("searchWindowUpdateIconData", (event, args) => {
//
let updateIconData = JSON.parse(args);
if (this.$store.state.iconDataMap == null) {
this.$store.state.iconDataMap = new Map();
}
//
if (!this.arrayIsEmpty(updateIconData.delete)) {
for (let del of updateIconData.delete) {
this.$store.state.iconDataMap.delete(CommonJS.getKey(del.classificationParentId, del.classificationChildId, del.itemId));
}
}
//
if (!this.arrayIsEmpty(updateIconData.add)) {
for (let add of updateIconData.add) {
let icon = {
classificationParentId: add.classificationParentId,
classificationChildId: add.classificationChildId,
itemId: add.itemId,
icon: add.icon,
};
this.$store.state.iconDataMap.set(CommonJS.getKey(add.classificationParentId, add.classificationChildId, add.itemId), icon);
}
}
//
if (!this.arrayIsEmpty(updateIconData.update)) {
for (let update of updateIconData.update) {
let icon = this.$store.state.iconDataMap.get(CommonJS.getKey(update.classificationParentId, update.classificationChildId, update.itemId));
if (icon != null) {
icon.icon = update.icon;
this.$store.state.iconDataMap.set(CommonJS.getKey(update.classificationParentId, update.classificationChildId, update.itemId), icon);
} else {
this.$store.state.iconDataMap.set(CommonJS.getKey(update.classificationParentId, update.classificationChildId, update.itemId), update);
}
}
}
});
//
window.addEventListener("keydown", this.keydown, true);
//
window.addEventListener("contextmenu", this.rightMenu, true);
window.addEventListener("dragover", this.dragover, true);
window.addEventListener("drop", this.drop, true);
},
unmounted() {
window.removeEventListener("keydown", this.keydown, true);
window.removeEventListener("dragover", this.dragover, true);
window.removeEventListener("drop", this.drop, true);
},
methods: {
/**
* 判断数组是否等于空
*/
arrayIsEmpty: CommonJS.arrayIsEmpty,
/**
* 判断字符串是否为空
*/
strIsEmpty: CommonJS.strIsEmpty,
/**
* 过滤XSS
*/
sanitize: CommonJS.DOMPurify.sanitize,
/**
* 获取图标根据分类
*/
getIconByClassification: CommonJS.getIconByClassification,
/**
* 获取项目名称
*/
getItemName: ItemJS.getName,
/**
* 拖拽悬停
* @param e
*/
dragover(e) {
if (this.setting.quickSearch.useItemOpen) {
//
let itemList = document.getElementsByClassName("search-result-item");
for (let i = 0; i < itemList.length; i++) {
if (i == this.selected) {
continue;
}
this.$styleMouseout(itemList[i], "search-result-item", ["background-color"]);
}
//
this.$styleMouseover(e, "search-result-item", ["background-color"], [this.$hexToRGBA(this.setting.appearance.theme.minorBackground, 0.3)]);
}
e.preventDefault();
e.stopPropagation();
},
/**
* 拖拽释放
* @param e
*/
drop(e) {
if (this.setting.quickSearch.useItemOpen) {
this.parentItemRun(e);
}
e.preventDefault();
e.stopPropagation();
},
/**
* 获取数据
*/
getData(setting, list) {
//
setting = setting != null ? setting : ipcRenderer.sendSync("getSetting");
this.setting = setting;
this.$store.state.setting = setting;
this.$store.state.currentLanguage = this.$store.state.language[this.$store.state.setting.general.language];
//
list = list != null ? list : ipcRenderer.sendSync("getList");
this.list = list;
this.$store.state.list = list;
// Map
this.itemMap = IndexJS.convertToMap(list);
//
this.createStyle();
//
if (this.setting.quickSearch.showHistory) {
//
let resultList = this.getHistory();
this.resultList = resultList;
if (!this.arrayIsEmpty(resultList)) {
this.resetScroll();
this.showHistory = true;
}
}
},
/**
* 获取历史记录
*/
getHistory() {
//
if (this.setting.quickSearch.showHistory) {
//
let itemList = [];
for (let c of this.list) {
if (c.excludeSearch == null || !c.excludeSearch) {
if (!this.arrayIsEmpty(c.childList)) {
for (let cc of c.childList) {
if (cc.excludeSearch == null || !cc.excludeSearch) {
if (!this.arrayIsEmpty(cc.itemList)) {
for (let item of cc.itemList) {
if (
(this.setting.quickSearch.showHistorySort == "openNumber" && item.quickSearchOpenNumber != null) ||
(this.setting.quickSearch.showHistorySort == "lastOpen" && item.quickSearchLastOpen != null)
) {
item.classificationName = ClassificationJS.getIcon(c) + c.name + "/" + ClassificationJS.getIcon(cc) + cc.name;
item.classificationIds = c.id + "-" + cc.id;
itemList.push(item);
}
}
}
}
}
} else {
if (!this.arrayIsEmpty(c.itemList)) {
for (let item of c.itemList) {
if (
(this.setting.quickSearch.showHistorySort == "openNumber" && item.quickSearchOpenNumber != null) ||
(this.setting.quickSearch.showHistorySort == "lastOpen" && item.quickSearchLastOpen != null)
) {
item.classificationName = ClassificationJS.getIcon(c) + c.name;
item.classificationIds = c.id;
itemList.push(item);
}
}
}
}
}
}
//
if (this.setting.quickSearch.showHistorySort == "openNumber") {
itemList = ItemJS.sort(itemList, this.setting.quickSearch.showHistorySort, "quickSearchOpenNumber");
itemList.reverse();
} else if (this.setting.quickSearch.showHistorySort == "lastOpen") {
itemList = ItemJS.sort(itemList, this.setting.quickSearch.showHistorySort, "quickSearchLastOpen");
itemList.reverse();
}
//
if (itemList.length > 10) {
return itemList.slice(0, 10);
} else {
return itemList;
}
}
return [];
},
/**
* 重置滚动条
*/
resetScroll() {
this.selected = 0;
this.$nextTick(() => {
let listEL = document.getElementById("search-result-list");
if (listEL != null) {
let sim = listEL.getElementsByClassName("simplebar-content-wrapper")[0];
if (sim != null) {
sim.scrollTop = 0;
}
}
});
},
/**
* 设置窗口高度
*/
setWindowHeight() {
let height = this.arrayIsEmpty(this.resultList) ? 0 : this.resultList.length > 9 ? 10 * 48 : this.resultList.length * 48;
ipcRenderer.send("setSearchWindowHeight", height + 44 + 1);
},
/**
* 搜索
*/
search() {
let name = this.nameSwitch == "nameOne" ? this.nameOne : this.nameTwo;
if (!this.webSearch) {
let resultList = IndexJS.search(name, this.itemMap);
if (!this.arrayIsEmpty(resultList)) {
if (this.setting.quickSearch.openNow && resultList.length == 1) {
this.itemRun(resultList[0]);
this.showHistory = false;
return;
} else {
this.resultList = resultList;
this.resetScroll();
this.showHistory = false;
}
} else if (this.strIsEmpty(name)) {
//
if (this.setting.quickSearch.showHistory) {
//
this.resultList = this.getHistory();
if (!this.arrayIsEmpty(this.resultList)) {
this.resetScroll();
this.showHistory = true;
}
} else {
this.resultList = resultList;
this.showHistory = false;
}
} else {
this.resultList = resultList;
this.showHistory = false;
}
this.setWindowHeight();
}
},
/**
* 父级点击运行
* @param e
*/
parentItemRun(e) {
// search-result-item
let target = this.$getClassElement(e, "search-result-item");
if (target != null) {
// ID
let classificationParentId = target.getAttribute("classification-parent-id");
let classificationChildId = target.getAttribute("classification-child-id");
// ID
let itemId = target.getAttribute("item-id");
//
if (classificationParentId != null && itemId != null) {
let classification = ClassificationJS.getClassificationById(classificationParentId, classificationChildId);
if (classification != null && !this.arrayIsEmpty(classification.itemList)) {
let item = ItemJS.getItemById(classification, itemId);
if (item != null) {
this.itemRun(item);
}
}
}
}
},
/**
* 运行项目
*/
itemRun(item) {
if (this.setting.quickSearch.openAfterHideQuickSearchWindow) {
this.hideWindow();
}
ItemJS.itemRun(item, null, this.setting.quickSearch.showHistory);
},
/**
* 父级鼠标悬浮
*/
parentMouseover(e) {
this.$styleMouseover(e, "search-result-item", ["background-color"], [this.$hexToRGBA(this.$store.state.setting.appearance.theme.minorBackground, 0.3)]);
},
/**
* 父级鼠标离开
*/
parentMouseout(e) {
//
let target = this.$getClassElement(e, "search-result-item");
if (target != null) {
let index = target.getAttribute("index");
if (index != null) {
if (Number(index) != this.selected) {
this.$styleMouseout(e, "search-result-item", ["background-color"]);
}
}
}
},
keydown(e) {
// name
let name = this.nameSwitch == "nameOne" ? this.nameOne : this.nameTwo;
// esc
if (e.keyCode == 27) {
this.hideWindow();
return;
}
//
let sk = CommonJS.setShortcutKey(e, null, false);
if (!this.strIsEmpty(sk) && (sk.toLowerCase() == "ctrl + r" || sk.toLowerCase() == "ctrl + shift + r" || sk.toLowerCase() == "f5")) {
e.preventDefault();
return;
}
//
if (!this.strIsEmpty(sk) && sk.toLowerCase() == "ctrl + w") {
e.preventDefault();
return;
}
// 38 40
if (e.keyCode == 38 || e.keyCode == 40) {
e.preventDefault();
if (!this.arrayIsEmpty(this.resultList)) {
if (e.keyCode == 38 && this.selected > 0) {
this.selected--;
this.searchResultDivMoveScroll(0);
return;
}
if (e.keyCode == 40 && this.selected < this.resultList.length - 1) {
this.selected++;
this.searchResultDivMoveScroll(1);
return;
}
}
}
//
if (e.keyCode == 32) {
if (!this.webSearch) {
//
if (!this.strIsEmpty(name)) {
let flag = false;
if (this.$store.state.setting.webSearch.mode == 0) {
if (name.substring(0, 1) == ":") {
flag = true;
}
} else if (this.$store.state.setting.webSearch.mode == 1) {
flag = true;
}
if (flag) {
let keyword = this.$store.state.setting.webSearch.mode == 0 ? name.substring(1) : name;
for (let searchSource of this.$store.state.setting.webSearch.searchSourceList) {
if (keyword == searchSource.keyword) {
this.webSearch = true;
this.sourceSearch = searchSource;
if (this.nameSwitch == "nameOne") {
this.nameOne = null;
} else {
this.nameTwo = null;
}
this.resultList = null;
this.selected = null;
ipcRenderer.send("setSearchWindowHeight", 44 + 1);
e.preventDefault();
}
}
}
}
}
}
// enter
if (e.keyCode == 13) {
e.preventDefault();
if (this.webSearch) {
let URL = this.sourceSearch.URL.replace("{w}", name == null ? "" : name);
ipcRenderer.send("openUrl", URL);
this.hideWindow();
} else {
if (this.selected != null && this.resultList.length - 1 >= this.selected) {
this.itemRun(this.resultList[this.selected]);
}
}
}
// 退
if (e.keyCode == 8) {
if (this.webSearch) {
if (name == null || name == "") {
this.webSearch = false;
this.sourceSearch = null;
}
}
}
if (this.showHistory) {
// 使alt+
if (e.altKey && e.keyCode != 18 && ((e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 96 && e.keyCode <= 105))) {
let index;
if (e.key == 0) {
index = 9;
} else {
index = e.key - 1;
}
if (!this.arrayIsEmpty(this.resultList) && index < this.resultList.length) {
this.itemRun(this.resultList[index]);
e.preventDefault();
}
}
} else {
if (this.$store.state.setting.quickSearch.openShortcutKey != "none") {
let flag = false;
if (this.$store.state.setting.quickSearch.openShortcutKey == "numberKey") {
flag = true;
} else if (this.$store.state.setting.quickSearch.openShortcutKey == "ctrlNumberKey" && e.ctrlKey && e.keyCode != 17) {
flag = true;
} else if (this.$store.state.setting.quickSearch.openShortcutKey == "altNumberKey" && e.altKey && e.keyCode != 18) {
flag = true;
}
if (flag && ((e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 96 && e.keyCode <= 105))) {
// 1
if (this.$store.state.setting.quickSearch.openShortcutKey == "numberKey") {
let resultList = IndexJS.search(name + e.key, this.itemMap);
if (resultList != null && resultList.length > 0) {
flag = false;
}
}
if (flag) {
let index;
if (e.key == 0) {
index = 9;
} else {
index = e.key - 1;
}
if (!this.arrayIsEmpty(this.resultList) && index < this.resultList.length) {
this.itemRun(this.resultList[index]);
e.preventDefault();
}
}
}
}
}
},
/**
* 移动滚动条
* @param type 0 1
*/
searchResultDivMoveScroll(type) {
let itemEl = document.getElementById("result-" + this.selected);
let itemRect = itemEl.getBoundingClientRect();
let listEL = document.getElementById("search-result-list");
let realY = itemRect.y - 44 - 1 + 48;
if (type == 0) {
if (realY < 48) {
let sim = listEL.getElementsByClassName("simplebar-content-wrapper")[0];
sim.scrollTop = sim.scrollTop - (48 - realY);
}
} else {
if (realY > listEL.clientHeight) {
let sim = listEL.getElementsByClassName("simplebar-content-wrapper")[0];
sim.scrollTop = sim.scrollTop + (realY - listEL.clientHeight);
}
}
},
/**
* 右键菜单
*/
rightMenu(e) {
e.preventDefault();
//
if (
(e.target.nodeName != null && e.target.nodeName.toLowerCase() == "input" && e.target.type != null && e.target.type.toLowerCase() == "text") ||
(e.target.nodeName != null && e.target.nodeName.toLowerCase() == "textarea")
) {
ipcRenderer.send("textRightMenu");
return;
}
//
let { element, classificationParentId, classificationChildId, itemId } = this.getRightClickElementInfo(e);
//
if (this.strIsEmpty(element)) {
return;
}
//
let classification = ClassificationJS.getClassificationById(classificationParentId, classificationChildId);
//
if (element == "item" || element == "item-child") {
//
let item = ItemJS.getItemById(classification, itemId);
ipcRenderer.send(
"itemRightMenu",
JSON.stringify({
item: item,
classificationChildId: classificationChildId,
classificationParentId: classificationParentId,
showClearItem: false,
searchWindow: true,
x: e.screenX,
y: e.screenY,
isMapDirectory: !this.strIsEmpty(classification.mapDirectory),
aggregate: classification.type != null && classification.type == 1,
})
);
}
},
/**
* 获取右键元素的属性
*/
getRightClickElementInfo(e) {
let element;
let classificationParentId;
let classificationChildId;
let itemId;
for (let i = 0; i < e.path.length; i++) {
if (e.path[i].attributes == null) {
continue;
}
if (e.path[i].getAttribute("item-child") != null && e.path[i].getAttribute("item-child") == "false") {
//
element = "item";
classificationParentId = e.path[i].getAttribute("classification-parent-id");
itemId = e.path[i].getAttribute("item-id");
break;
} else if (e.path[i].getAttribute("item-child") != null && e.path[i].getAttribute("item-child") == "true") {
//
element = "item-child";
classificationParentId = e.path[i].getAttribute("classification-parent-id");
classificationChildId = e.path[i].getAttribute("classification-child-id");
itemId = e.path[i].getAttribute("item-id");
break;
}
}
return {
element,
classificationParentId,
classificationChildId,
itemId,
};
},
getItemTitle: ItemJS.getItemTitle,
/**
* 隐藏窗口
*/
hideWindow() {
//
if (this.nameWatch != null) {
this.nameWatch();
}
if (this.nameSwitch == "nameOne") {
this.nameSwitch = "nameTwo";
} else {
this.nameSwitch = "nameOne";
}
this.nameOne = null;
this.nameTwo = null;
this.resultList = null;
this.selected = null;
this.webSearch = false;
this.sourceSearch = null;
ipcRenderer.send("hideSearchWindow");
},
/**
* 创建样式
*/
createStyle() {
// style
let styleElement = document.getElementById("placeholder-style");
// style
if (styleElement) {
styleElement.parentNode.removeChild(styleElement);
}
//
let style = document.createElement("style");
style.setAttribute("id", "placeholder-style");
style.type = "text/css";
//
style.innerHTML =
"#nameOne::placeholder {" +
"color: " +
this.$store.state.setting.appearance.theme.fontBasic +
";" +
"}" +
"#nameTwo::placeholder {" +
"color: " +
this.$store.state.setting.appearance.theme.fontBasic +
";" +
"}";
// head
document.head.appendChild(style);
// style
let scrollStyleElement = document.getElementById("scroll-style");
// style
if (scrollStyleElement) {
scrollStyleElement.parentNode.removeChild(scrollStyleElement);
}
//
scrollStyleElement = document.createElement("style");
scrollStyleElement.setAttribute("id", "scroll-style");
scrollStyleElement.type = "text/css";
//
scrollStyleElement.innerHTML =
".simplebar-scrollbar::before {" +
" background-color: " +
this.$store.state.setting.appearance.theme.minorBackground +
";" +
" right: 0;" +
"}" +
"textarea::-webkit-scrollbar-thumb {" +
" background-color: " +
this.$store.state.setting.appearance.theme.minorBackground +
";" +
"border-radius: 7px;" +
"}";
// head
document.head.appendChild(scrollStyleElement);
},
/**
* 获取图标数据
*/
getIconData() {
let iconData = ipcRenderer.sendSync("getIconData");
this.$store.state.iconDataMap = new Map();
for (let icon of iconData) {
this.$store.state.iconDataMap.set(CommonJS.getKey(icon.classificationParentId, icon.classificationChildId, icon.itemId), icon);
}
},
},
};
</script>
<style>
[v-cloak] {
display: none;
}
</style>

View File

@ -0,0 +1,149 @@
import PinyinMatch from "pinyin-match";
import CommonJS from "@/common";
import ItemJS from "@/views/item/js/index";
import ClassificationJS from "@/views/classification/js/index";
import store from "@/store";
/**
* 判断是否有中文
* @param str
* @returns {boolean}
*/
function hasChinese(str) {
const pattern = /[\u4e00-\u9fa5]/; // 中文字符的 Unicode 范围
return pattern.test(str);
}
/**
* 判断是否是URL
* @param str
* @returns {boolean}
*/
function hasURL(str) {
const pattern = /[a-zA-Z]+:\/\/[^\\s]*/;
return pattern.test(str);
}
export default {
convertToMap(list) {
let itemMap = new Map();
// 将数据转为Map
for (let i of list) {
if (i.excludeSearch == null || !i.excludeSearch) {
if (!CommonJS.arrayIsEmpty(i.childList)) {
for (let c of i.childList) {
if (c.excludeSearch == null || !c.excludeSearch) {
this.setMap(itemMap, c.itemList, i.id + "-" + c.id, ClassificationJS.getIcon(i) + i.name + "/" + ClassificationJS.getIcon(c) + c.name);
}
}
} else {
this.setMap(itemMap, i.itemList, i.id, ClassificationJS.getIcon(i) + i.name);
}
}
}
return itemMap;
},
/**
* 放数据
*/
setMap(itemMap, itemList, classificationIds, classificationName) {
if (!CommonJS.arrayIsEmpty(itemList)) {
for (let t of itemList) {
// 设置信息
let item = JSON.parse(JSON.stringify(t));
item.classificationName = classificationName;
item.classificationIds = classificationIds;
// 名称
let name = ItemJS.getName(item.name).toLowerCase();
let list = itemMap.get(name);
if (CommonJS.arrayIsEmpty(list)) {
list = [];
}
list.push(item);
itemMap.set(name, list);
// 缩写
if (!CommonJS.strIsEmpty(t.abbr)) {
let abbr = t.abbr.toLowerCase();
let abbrList = itemMap.get(abbr);
if (CommonJS.arrayIsEmpty(abbrList)) {
abbrList = [];
}
abbrList.push(item);
itemMap.set(abbr, abbrList);
}
// 网址
if (t.type == 2 && !CommonJS.strIsEmpty(t.url)) {
let url = t.url.toLowerCase();
let urlList = itemMap.get(url);
if (CommonJS.arrayIsEmpty(urlList)) {
urlList = [];
}
urlList.push(item);
itemMap.set(url, urlList);
}
// 备注搜索
if (store.state.setting.quickSearch.matchingConditionsRemark) {
if (!CommonJS.strIsEmpty(t.remark)) {
let remark = t.remark.toLowerCase();
let remarkList = itemMap.get(remark);
if (CommonJS.arrayIsEmpty(remarkList)) {
remarkList = [];
}
remarkList.push(item);
itemMap.set(remark, remarkList);
}
}
}
}
},
/**
* 搜索
*/
search(name, itemMap) {
let resultList = [];
if (!CommonJS.strIsEmpty(name)) {
let n = name.toLowerCase();
for (let [key, value] of itemMap.entries()) {
let flag = false;
if (hasURL(key)) {
// 网址
flag = key.indexOf(n) >= 0;
} else if (hasChinese(key)) {
// 包含中文
let match = PinyinMatch.match(key, n);
if (match != null && match.length > 0) {
flag = true;
}
} else {
// 其他情况
flag = key.indexOf(n) >= 0;
}
if (flag) {
for (let v of value) {
let flag = false;
if (v.id != null && v.classificationIds != null) {
for (let r of resultList) {
if (v.classificationIds == r.classificationIds && v.id == r.id) {
flag = true;
break;
}
}
} else {
for (let r of resultList) {
if (v.name == r.name) {
flag = true;
break;
}
}
}
if (!flag) {
resultList.push(v);
}
}
}
}
}
resultList.sort((a, b) => a.name.localeCompare(b.name));
return resultList;
},
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
const { ipcRenderer } = window.require("electron");
export default {
/**
* 重新设置
*/
resetSetting(setting) {
// 开机启动
ipcRenderer.send("setAutoLaunch", setting.general.startup);
// 隐藏托盘图标
ipcRenderer.send("setTray", !setting.general.hideTray);
// 永远置顶
ipcRenderer.send("setAlwaysTop", setting.general.alwaysTop);
// 锁定尺寸
ipcRenderer.send("setResize", !setting.general.lockSize);
// 固定位置
ipcRenderer.send("setFixedPosition", [!setting.general.fixedPosition, setting.general.alwaysCenter]);
// 永远居中
ipcRenderer.send("setAlwaysCenter", [setting.general.alwaysCenter, !setting.general.fixedPosition, setting.general.alwaysCenter]);
// 隐藏任务栏
ipcRenderer.send("setHideTaskbar", setting.general.hideTaskbar);
// 设置透明度
ipcRenderer.send("setOpacity", setting.appearance.transparency);
// 设置快捷键
ipcRenderer.send("setShortcutKey", JSON.stringify(setting));
},
};

14
tailwind.config.js Normal file
View File

@ -0,0 +1,14 @@
module.exports = {
content: [
"./src/**/*.{vue,js,ts,jsx,tsx}",
],
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [require('@tailwindcss/forms')({
strategy: 'class', // only generate classes
})],
}

82
vue.config.js Normal file
View File

@ -0,0 +1,82 @@
const { defineConfig } = require("@vue/cli-service");
const TerserPlugin = require("terser-webpack-plugin");
const path = require("path");
function resolve(dir) {
return path.join(__dirname, dir);
}
module.exports = defineConfig({
transpileDependencies: true,
assetsDir: "public",
productionSourceMap: false,
configureWebpack: {
entry: "./src/renderer/main.js",
resolve: {
extensions: [".js", ".vue", ".json", ".ts", ".less"],
alias: {
"@": resolve("src/renderer"),
},
},
optimization: {
usedExports: true,
splitChunks: {
chunks: "all",
},
minimizer: [
new TerserPlugin({
extractComments: false, // 禁止生成license.txt文件
terserOptions: {
compress: {
drop_console: true, // 删除console.log
drop_debugger: true, // 删除debugger
},
},
}),
],
},
},
pluginOptions: {
electronBuilder: {
builderOptions: {
// 应用名称
productName: "Dawn Launcher",
// 压缩级别
compression: "maximum",
// 包含api目录
extraFiles: ["build"],
// 图片
extraResources: [{ from: "./public/images", to: "images" }],
// windows
win: {
// appId
appId: "com.dawnlauncher.application",
target: [
{
target: "nsis",
// ia32 x64
arch: ["x64"],
},
],
icon: "public/images/logo.ico",
},
nsis: {
// 安装文件名称
artifactName: "${productName}-${version}.${ext}",
// 是否一键安装建议为false可以让用户点击下一步、下一步、下一步的形式安装程序如果为true当用户双击构建好的程序自动安装程序并打开一键安装one-click installer
oneClick: false,
// 允许请求提升如果为false则用户必须使用提升的权限重新启动安装程序。
allowElevation: true,
// 允许修改安装目录建议为true是否允许用户改变安装目录默认是不允许。
allowToChangeInstallationDirectory: true,
// 创建桌面图标
createDesktopShortcut: true,
// 创建开始菜单图标
createStartMenuShortcut: true,
},
},
mainProcessFile: "src/main/main.js",
mainProcessWatch: ["src/main"],
},
},
});

9110
yarn.lock Normal file

File diff suppressed because it is too large Load Diff