This commit is contained in:
2025-04-15 00:52:23 +08:00
parent b76a685988
commit c7763227d0
24 changed files with 748 additions and 384 deletions

View File

@@ -3,12 +3,8 @@ import { version } from "../../package.json";
export const ExtVersion = version;
export const ExtServer = "https://ext.scriptcat.org/";
export const ExtServerApi = ExtServer + "api/v1/";
export const ExternalWhitelist = [
"greasyfork.org",
"scriptcat.org",
"tampermonkey.net.cn",
"openuserjs.org",
];
export const ExternalWhitelist = ["greasyfork.org", "scriptcat.org", "tampermonkey.net.cn", "openuserjs.org"];
export const ExternalMessage = "externalMessage";

View File

@@ -358,5 +358,6 @@
"collapse": "Collapse",
"expand": "Expand",
"menu_expand_num_before": "Menu item more than",
"menu_expand_num_after": "Auto-hide."
"menu_expand_num_after": "Auto-hide.",
"eslint_config_format_error": "ESLint configuration format error"
}

View File

@@ -363,5 +363,6 @@
"expand": "展开",
"menu_expand_num_before": "菜单项超过",
"menu_expand_num_after": "个时,自动隐藏",
"script_name_cannot_be_set_to_empty": "脚本name不可以设置为空"
"script_name_cannot_be_set_to_empty": "脚本name不可以设置为空",
"eslint_config_format_error": "eslint配置格式错误"
}

View File

@@ -1,6 +1,8 @@
import React from "react";
import { Input, Select, Space } from "@arco-design/web-react";
import FileSystemFactory, { FileSystemType } from "@Packages/filesystem/factory";
const fsParams = FileSystemFactory.params();
const fileSystemList: {
key: FileSystemType;
@@ -65,10 +67,7 @@ const FileSystemParams: React.FC<{
<>
<span>{fsParams[fileSystemType][key].title}</span>
<Select
value={
fileSystemParams[key] ||
fsParams[fileSystemType][key].options![0]
}
value={fileSystemParams[key] || fsParams[fileSystemType][key].options![0]}
onChange={(value) => {
onChangeFileSystemParams({
...fileSystemParams,

View File

@@ -1,29 +1,28 @@
import React, { useState } from "react";
import {
Button,
Card,
Collapse,
Link,
Message,
Space,
Typography,
} from "@arco-design/web-react";
import React, { useEffect, useState } from "react";
import { Button, Card, Collapse, Link, Message, Space, Typography } from "@arco-design/web-react";
import { useTranslation } from "react-i18next";
import FileSystemParams from "../FileSystemParams";
import { systemConfig } from "@App/pages/store/global";
import { FileSystemType } from "@Packages/filesystem/factory";
const CollapseItem = Collapse.Item;
const GMApiSetting: React.FC = () => {
const systemConfig = IoC.instance(SystemConfig) as SystemConfig;
const [status, setStatus] = useState(systemConfig.catFileStorage.status);
const [fileSystemType, setFilesystemType] = useState<FileSystemType>(
systemConfig.catFileStorage.filesystem
);
const [status, setStatus] = useState("unset");
const [fileSystemType, setFilesystemType] = useState<FileSystemType>("webdav");
const [fileSystemParams, setFilesystemParam] = useState<{
[key: string]: any;
}>(systemConfig.catFileStorage.params[fileSystemType] || {});
}>({});
const { t } = useTranslation();
useEffect(() => {
systemConfig.getCatFileStorage().then((res) => {
setStatus(res.status);
setFilesystemType(res.filesystem);
setFilesystemParam(res.params[res.filesystem] || {});
});
}, []);
return (
<Card title={t("gm_api")} bordered={false}>
<Collapse bordered={false} defaultActiveKey={["storage"]}>
@@ -48,10 +47,7 @@ const GMApiSetting: React.FC = () => {
type="primary"
onClick={async () => {
try {
await FileSystemFactory.create(
fileSystemType,
fileSystemParams
);
await FileSystemFactory.create(fileSystemType, fileSystemParams);
} catch (e) {
Message.error(`${t("account_validation_failed")}: ${e}`);
return;
@@ -87,10 +83,7 @@ const GMApiSetting: React.FC = () => {
type="secondary"
onClick={async () => {
try {
let fs = await FileSystemFactory.create(
fileSystemType,
fileSystemParams
);
let fs = await FileSystemFactory.create(fileSystemType, fileSystemParams);
fs = await fs.openDir("ScriptCat/app");
window.open(await fs.getDirUrl(), "_black");
} catch (e) {
@@ -110,17 +103,9 @@ const GMApiSetting: React.FC = () => {
setFilesystemParam(params);
}}
/>
{status === "unset" && (
<Typography.Text type="secondary">{t("not_set")}</Typography.Text>
)}
{status === "success" && (
<Typography.Text type="success">{t("in_use")}</Typography.Text>
)}
{status === "error" && (
<Typography.Text type="error">
{t("storage_error")}
</Typography.Text>
)}
{status === "unset" && <Typography.Text type="secondary">{t("not_set")}</Typography.Text>}
{status === "success" && <Typography.Text type="success">{t("in_use")}</Typography.Text>}
{status === "error" && <Typography.Text type="error">{t("storage_error")}</Typography.Text>}
</Space>
</CollapseItem>
</Collapse>

View File

@@ -14,12 +14,11 @@ import { RiPlayFill, RiStopFill } from "react-icons/ri";
import { useTranslation } from "react-i18next";
import { ScriptIcons } from "@App/pages/options/routes/utils";
import { ScriptMenu, ScriptMenuItem } from "@App/app/service/service_worker/popup";
import { selectMenuExpandNum } from "@App/pages/store/features/setting";
import { useAppSelector } from "@App/pages/store/hooks";
import { popupClient, runtimeClient, scriptClient } from "@App/pages/store/features/script";
import { i18nName } from "@App/locales/locales";
import { subscribeScriptRunStatus } from "@App/app/service/queue";
import { messageQueue } from "@App/pages/store/global";
import { messageQueue, systemConfig } from "@App/pages/store/global";
const CollapseItem = Collapse.Item;
@@ -46,7 +45,7 @@ const ScriptMenuList: React.FC<{
[key: string]: boolean;
}>({});
const { t } = useTranslation();
const menuExpandNum = useAppSelector(selectMenuExpandNum);
const [menuExpandNum, setMenuExpandNum] = useState(5);
let url: URL;
try {
@@ -70,6 +69,10 @@ const ScriptMenuList: React.FC<{
return newList;
});
});
// 获取配置
systemConfig.getMenuExpandNum().then((num) => {
setMenuExpandNum(num);
});
return () => {
unsub();
};

View File

@@ -16,7 +16,7 @@ import React, { ReactNode, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import "./index.css";
import { useAppDispatch, useAppSelector } from "@App/pages/store/hooks";
import { selectThemeMode, setDarkMode } from "@App/pages/store/features/setting";
import { selectThemeMode, setDarkMode } from "@App/pages/store/features/config";
import { RiFileCodeLine, RiImportLine, RiPlayListAddLine, RiTerminalBoxLine, RiTimerLine } from "react-icons/ri";
const MainLayout: React.FC<{

View File

@@ -80,7 +80,7 @@ import {
requestStopScript,
requestRunScript,
} from "@App/pages/store/features/script";
import { selectScriptListColumnWidth } from "@App/pages/store/features/setting";
import { systemConfig } from "@App/pages/store/global";
type ListType = Script & { loading?: boolean };
@@ -93,7 +93,6 @@ function ScriptList() {
const [cloudScript, setCloudScript] = useState<Script>();
const dispatch = useAppDispatch();
const scriptList = useAppSelector(selectScripts);
const scriptListColumnWidth = useAppSelector(selectScriptListColumnWidth);
const inputRef = useRef<RefInputType>(null);
const navigate = useNavigate();
const openUserConfig = useSearchParams()[0].get("userConfig") || "";
@@ -557,12 +556,14 @@ function ScriptList() {
});
}
}
setNewColumns(
columns.map((item) => {
item.width = scriptListColumnWidth[item.key!] ?? item.width;
return item;
})
);
systemConfig.getScriptListColumnWidth().then((columnWidth) => {
setNewColumns(
columns.map((item) => {
item.width = columnWidth[item.key!] ?? item.width;
return item;
})
);
});
}, []);
// 处理拖拽排序

View File

@@ -1,13 +1,5 @@
import React, { useState } from "react";
import {
Button,
Card,
Checkbox,
Input,
Message,
Select,
Space,
} from "@arco-design/web-react";
import { useEffect, useState } from "react";
import { Button, Card, Checkbox, Input, Message, Select, Space } from "@arco-design/web-react";
import Title from "@arco-design/web-react/es/Typography/title";
import { IconQuestionCircleFill } from "@arco-design/web-react/icon";
import { format } from "prettier";
@@ -17,22 +9,24 @@ import i18n from "@App/locales/locales";
import { useTranslation } from "react-i18next";
import dayjs from "dayjs";
import Logger from "@App/app/logger/logger";
import { systemConfig } from "@App/pages/store/global";
import { FileSystemType } from "@Packages/filesystem/factory";
import FileSystemParams from "@App/pages/components/FileSystemParams";
function Setting() {
const systemConfig = IoC.instance(SystemConfig) as SystemConfig;
const [syncDelete, setSyncDelete] = useState<boolean>(
systemConfig.cloudSync.syncDelete
);
const [enableCloudSync, setEnableCloudSync] = useState(
systemConfig.cloudSync.enable
);
const [fileSystemType, setFilesystemType] = useState<FileSystemType>(
systemConfig.cloudSync.filesystem
);
const [syncDelete, setSyncDelete] = useState<boolean>();
const [enableCloudSync, setEnableCloudSync] = useState<boolean>();
const [fileSystemType, setFilesystemType] = useState<FileSystemType>("webdav");
const [fileSystemParams, setFilesystemParam] = useState<{
[key: string]: any;
}>(systemConfig.cloudSync.params[fileSystemType] || {});
}>({});
const [language, setLanguage] = useState(i18n.language);
const [menuExpandNum, setMenuExpandNum] = useState(5);
const [checkScriptUpdateCycle, setCheckScriptUpdateCycle] = useState(0);
const [updateDisableScript, setUpdateDisableScript] = useState(false);
const [silenceUpdateScript, setSilenceUpdateScript] = useState(false);
const [enableEslint, setEnableEslint] = useState(false);
const [eslintConfig, setEslintConfig] = useState("");
const languageList: { key: string; title: string }[] = [];
const { t } = useTranslation();
Object.keys(i18n.store.data).forEach((key) => {
@@ -49,6 +43,34 @@ function Setting() {
title: t("help_translate"),
});
useEffect(() => {
const loadConfigs = async () => {
const [cloudSync, menuExpandNum, checkCycle, updateDisabled, silenceUpdate, eslintConfig, enableEslint] =
await Promise.all([
systemConfig.getCloudSync(),
systemConfig.getMenuExpandNum(),
systemConfig.getCheckScriptUpdateCycle(),
systemConfig.getUpdateDisableScript(),
systemConfig.getSilenceUpdateScript(),
systemConfig.getEslintConfig(),
systemConfig.getEnableEslint(),
]);
setSyncDelete(cloudSync.syncDelete);
setEnableCloudSync(cloudSync.enable);
setFilesystemType(cloudSync.filesystem);
setFilesystemParam(cloudSync.params[cloudSync.filesystem] || {});
setMenuExpandNum(menuExpandNum);
setCheckScriptUpdateCycle(checkCycle);
setUpdateDisableScript(updateDisabled);
setSilenceUpdateScript(silenceUpdate);
setEslintConfig(eslintConfig);
setEnableEslint(enableEslint);
};
loadConfigs();
}, []);
return (
<Space
className="setting"
@@ -69,10 +91,7 @@ function Setting() {
className="w-24"
onChange={(value) => {
if (value === "help") {
window.open(
"https://crowdin.com/project/scriptcat",
"_blank"
);
window.open("https://crowdin.com/project/scriptcat", "_blank");
return;
}
setLanguage(value);
@@ -94,9 +113,11 @@ function Setting() {
<Input
style={{ width: "64px" }}
type="number"
defaultValue={systemConfig.menuExpandNum.toString()}
value={menuExpandNum.toString()}
onChange={(val) => {
systemConfig.menuExpandNum = parseInt(val, 10);
const num = parseInt(val, 10);
setMenuExpandNum(num);
systemConfig.setMenuExpandNum(num);
}}
/>
{t("menu_expand_num_after")}
@@ -134,16 +155,9 @@ function Setting() {
if (enableCloudSync) {
Message.info(t("cloud_sync_account_verification")!);
try {
await FileSystemFactory.create(
fileSystemType,
fileSystemParams
);
await FileSystemFactory.create(fileSystemType, fileSystemParams);
} catch (e) {
Message.error(
`${t(
"cloud_sync_verification_failed"
)}: ${JSON.stringify(Logger.E(e))}`
);
Message.error(`${t("cloud_sync_verification_failed")}: ${JSON.stringify(Logger.E(e))}`);
return;
}
}
@@ -177,12 +191,14 @@ function Setting() {
<Space>
<span>{t("script_subscription_check_interval")}:</span>
<Select
defaultValue={systemConfig.checkScriptUpdateCycle.toString()}
value={checkScriptUpdateCycle.toString()}
style={{
width: 120,
}}
onChange={(value) => {
systemConfig.checkScriptUpdateCycle = parseInt(value, 10);
const num = parseInt(value, 10);
setCheckScriptUpdateCycle(num);
systemConfig.setCheckScriptUpdateCycle(num);
}}
>
<Select.Option value="0">{t("never")}</Select.Option>
@@ -194,17 +210,19 @@ function Setting() {
</Space>
<Checkbox
onChange={(checked) => {
systemConfig.updateDisableScript = checked;
setEnableCloudSync(checked);
systemConfig.setUpdateDisableScript(checked);
}}
defaultChecked={systemConfig.updateDisableScript}
checked={updateDisableScript}
>
{t("update_disabled_scripts")}
</Checkbox>
<Checkbox
onChange={(checked) => {
systemConfig.silenceUpdateScript = checked;
setSilenceUpdateScript(checked);
systemConfig.setSilenceUpdateScript(checked);
}}
defaultChecked={systemConfig.silenceUpdateScript}
checked={silenceUpdateScript}
>
{t("silent_update_non_critical_changes")}
</Checkbox>
@@ -215,9 +233,10 @@ function Setting() {
<Space direction="vertical" className="w-full">
<Checkbox
onChange={(checked) => {
systemConfig.enableEslint = checked;
setEnableEslint(checked);
systemConfig.setEnableEslint(checked);
}}
defaultChecked={systemConfig.enableEslint}
checked={enableEslint}
>
{t("enable_eslint")}
</Checkbox>
@@ -246,12 +265,21 @@ function Setting() {
minRows: 4,
maxRows: 8,
}}
defaultValue={format(systemConfig.eslintConfig, {
parser: "json",
plugins: [babel],
})}
value={eslintConfig}
onChange={(v) => {
setEslintConfig(v);
}}
onBlur={(v) => {
systemConfig.eslintConfig = v.target.value;
format(eslintConfig, {
parser: "json",
plugins: [babel],
})
.then((res) => {
systemConfig.setEslintConfig(v.target.value);
})
.catch((e) => {
Message.error(`${t("eslint_config_format_error")}: ${JSON.stringify(Logger.E(e))}`);
});
}}
/>
</Space>

View File

@@ -1,16 +1,5 @@
import React, { useRef, useState } from "react";
import {
Button,
Card,
Checkbox,
Drawer,
Empty,
Input,
List,
Message,
Modal,
Space,
} from "@arco-design/web-react";
import React, { useEffect, useRef, useState } from "react";
import { Button, Card, Checkbox, Drawer, Empty, Input, List, Message, Modal, Space } from "@arco-design/web-react";
import Title from "@arco-design/web-react/es/Typography/title";
import { formatUnixTime } from "@App/pkg/utils/utils";
import FileSystemParams from "@App/pages/components/FileSystemParams";
@@ -18,20 +7,27 @@ import { IconQuestionCircleFill } from "@arco-design/web-react/icon";
import { RefInputType } from "@arco-design/web-react/es/Input/interface";
import { useTranslation } from "react-i18next";
import { FileSystemType } from "@Packages/filesystem/factory";
import { systemConfig } from "@App/pages/store/global";
function Tools() {
const [loading, setLoading] = useState<{ [key: string]: boolean }>({});
const fileRef = useRef<HTMLInputElement>(null);
const [fileSystemType, setFilesystemType] = useState<FileSystemType>(
systemConfig.backup.filesystem
);
const [fileSystemType, setFilesystemType] = useState<FileSystemType>("webdav");
const [fileSystemParams, setFilesystemParam] = useState<{
[key: string]: any;
}>(systemConfig.backup.params[fileSystemType] || {});
}>({});
const [backupFileList, setBackupFileList] = useState<File[]>([]);
const vscodeRef = useRef<RefInputType>(null);
const { t } = useTranslation();
useEffect(() => {
// 获取配置
systemConfig.getBackup().then((backup) => {
setFilesystemType(backup.filesystem);
setFilesystemParam(backup.params[backup.filesystem] || {});
});
}, []);
return (
<Space
className="tools"
@@ -47,12 +43,7 @@ function Tools() {
<Space direction="vertical">
<Title heading={6}>{t("local")}</Title>
<Space>
<input
type="file"
ref={fileRef}
style={{ display: "none" }}
accept=".zip"
/>
<input type="file" ref={fileRef} style={{ display: "none" }} accept=".zip" />
<Button
type="primary"
loading={loading.local}
@@ -96,12 +87,12 @@ function Tools() {
loading={loading.cloud}
onClick={() => {
// Store parameters
const params = { ...systemConfig.backup.params };
const params = { ...fileSystemParams };
params[fileSystemType] = fileSystemParams;
systemConfig.backup = {
systemConfig.setBackup({
filesystem: fileSystemType,
params,
};
});
setLoading((prev) => ({ ...prev, cloud: true }));
Message.info(t("preparing_backup")!);
syncCtrl
@@ -123,10 +114,7 @@ function Tools() {
key="list"
type="primary"
onClick={async () => {
let fs = await FileSystemFactory.create(
fileSystemType,
fileSystemParams
);
let fs = await FileSystemFactory.create(fileSystemType, fileSystemParams);
try {
fs = await fs.openDir("ScriptCat");
let list = await fs.list();
@@ -158,10 +146,7 @@ function Tools() {
type="secondary"
size="mini"
onClick={async () => {
let fs = await FileSystemFactory.create(
fileSystemType,
fileSystemParams
);
let fs = await FileSystemFactory.create(fileSystemType, fileSystemParams);
try {
fs = await fs.openDir("ScriptCat");
const url = await fs.getDirUrl();
@@ -190,20 +175,14 @@ function Tools() {
dataSource={backupFileList}
render={(item: File) => (
<List.Item key={item.name}>
<List.Item.Meta
title={item.name}
description={formatUnixTime(item.updatetime / 1000)}
/>
<List.Item.Meta title={item.name} description={formatUnixTime(item.updatetime / 1000)} />
<Space className="w-full justify-end">
<Button
type="primary"
size="small"
onClick={async () => {
Message.info(t("pulling_data_from_cloud")!);
let fs = await FileSystemFactory.create(
fileSystemType,
fileSystemParams
);
let fs = await FileSystemFactory.create(fileSystemType, fileSystemParams);
let file: FileReader;
let data: Blob;
try {
@@ -237,22 +216,13 @@ function Tools() {
onClick={() => {
Modal.confirm({
title: t("confirm_delete"),
content: `${t("confirm_delete_backup_file")}${
item.name
}?`,
content: `${t("confirm_delete_backup_file")}${item.name}?`,
onOk: async () => {
let fs = await FileSystemFactory.create(
fileSystemType,
fileSystemParams
);
let fs = await FileSystemFactory.create(fileSystemType, fileSystemParams);
try {
fs = await fs.openDir("ScriptCat");
await fs.delete(item.name);
setBackupFileList(
backupFileList.filter(
(i) => i.name !== item.name
)
);
setBackupFileList(backupFileList.filter((i) => i.name !== item.name));
Message.success(t("delete_success")!);
} catch (e) {
Message.error(`${t("delete_failed")}${e}`);

View File

@@ -0,0 +1,51 @@
import { SystemConfig } from "@App/pkg/config/config";
import { createAppSlice } from "../hooks";
import { PayloadAction } from "@reduxjs/toolkit";
import { editor } from "monaco-editor";
function setAutoMode() {
const darkTheme = window.matchMedia("(prefers-color-scheme: dark)");
const isMatch = (match: boolean) => {
if (match) {
document.body.setAttribute("arco-theme", "dark");
editor.setTheme("vs-dark");
} else {
document.body.removeAttribute("arco-theme");
editor.setTheme("vs");
}
};
darkTheme.addEventListener("change", (e) => {
isMatch(e.matches);
});
isMatch(darkTheme.matches);
}
export const configSlice = createAppSlice({
name: "setting",
initialState: {
lightMode: localStorage.lightMode || "auto",
},
reducers: (create) => {
// 初始化黑夜模式
setAutoMode();
return {
setDarkMode: create.reducer((state, action: PayloadAction<"light" | "dark" | "auto">) => {
localStorage.loghtMode = action.payload;
state.lightMode = action.payload;
if (action.payload === "auto") {
setAutoMode();
} else {
document.body.setAttribute("arco-theme", action.payload);
editor.setTheme(action.payload === "dark" ? "vs-dark" : "vs");
}
}),
};
},
selectors: {
selectThemeMode: (state) => state.lightMode,
},
});
export const { setDarkMode } = configSlice.actions;
export const { selectThemeMode } = configSlice.selectors;

View File

@@ -1,82 +0,0 @@
import { createAppSlice } from "../hooks";
import { PayloadAction } from "@reduxjs/toolkit";
import { editor } from "monaco-editor";
function setAutoMode() {
const darkTheme = window.matchMedia("(prefers-color-scheme: dark)");
const isMatch = (match: boolean) => {
if (match) {
document.body.setAttribute("arco-theme", "dark");
editor.setTheme("vs-dark");
} else {
document.body.removeAttribute("arco-theme");
editor.setTheme("vs");
}
};
darkTheme.addEventListener("change", (e) => {
isMatch(e.matches);
});
isMatch(darkTheme.matches);
}
export type SystemConfig = {
lightMode: "light" | "dark" | "auto";
eslint: {
enable: boolean;
config: string;
};
scriptListColumnWidth: { [key: string]: number };
menuExpandNum: number;
};
export const settingSlice = createAppSlice({
name: "setting",
initialState: {
lightMode: localStorage.lightMode || "auto",
eslint: {
enable: true,
config: "",
},
scriptListColumnWidth: {} as { [key: string]: number },
menuExpandNum: 5,
} as SystemConfig,
reducers: (create) => {
// 初始化黑夜模式
setAutoMode();
// 加载配置
chrome.storage.sync.get("systemSetting", (result) => {
const systemSetting = result.systemSetting as SystemConfig;
settingSlice.actions.initSetting(systemSetting);
if (systemSetting) {
localStorage.lightMode = systemSetting.lightMode;
}
});
return {
initSetting: create.reducer((state, action: PayloadAction<SystemConfig>) => {
state.menuExpandNum = action.payload.menuExpandNum;
}),
setDarkMode: create.reducer((state, action: PayloadAction<"light" | "dark" | "auto">) => {
localStorage.loghtMode = action.payload;
state.lightMode = action.payload;
if (action.payload === "auto") {
setAutoMode();
} else {
document.body.setAttribute("arco-theme", action.payload);
editor.setTheme(action.payload === "dark" ? "vs-dark" : "vs");
}
}),
menuExpandNum: create.reducer((state, action: PayloadAction<number>) => {
state.menuExpandNum = action.payload;
}),
};
},
selectors: {
selectThemeMode: (state) => state.lightMode,
selectScriptListColumnWidth: (state) => state.scriptListColumnWidth,
selectMenuExpandNum: (state) => state.menuExpandNum,
},
});
export const { setDarkMode } = settingSlice.actions;
export const { selectThemeMode, selectScriptListColumnWidth, selectMenuExpandNum } = settingSlice.selectors;

View File

@@ -1,5 +1,7 @@
import { SystemConfig } from "@App/pkg/config/config";
import { ExtensionMessage } from "@Packages/message/extension_message";
import { MessageQueue } from "@Packages/message/message_queue";
export const message = new ExtensionMessage();
export const messageQueue = new MessageQueue();
export const systemConfig = new SystemConfig(messageQueue);

View File

@@ -1,12 +1,12 @@
import type { Action, ThunkAction } from "@reduxjs/toolkit";
import { combineSlices, configureStore } from "@reduxjs/toolkit";
import { setupListeners } from "@reduxjs/toolkit/query";
import { settingSlice } from "./features/setting";
import { scriptSlice } from "./features/script";
import { configSlice } from "./features/config";
// `combineSlices` automatically combines the reducers using
// their `reducerPath`s, therefore we no longer need to call `combineReducers`.
const rootReducer = combineSlices(settingSlice, scriptSlice);
const rootReducer = combineSlices(configSlice, scriptSlice);
// Infer the `RootState` type from the root reducer
export type RootState = ReturnType<typeof rootReducer>;

View File

@@ -0,0 +1,58 @@
export default class ChromeStorage {
private prefix: string;
private storage: chrome.storage.StorageArea;
constructor(prefix: string, sync: boolean) {
this.prefix = `${prefix}_`;
this.storage = sync ? chrome.storage.sync : chrome.storage.local;
}
public buildKey(key: string): string {
return this.prefix + key;
}
public get(key: string): Promise<any> {
return new Promise((resolve) => {
key = this.buildKey(key);
this.storage.get(key, (items) => {
resolve(items[key]);
});
});
}
public set(key: string, value: any): Promise<void> {
return new Promise((resolve) => {
const kvp: { [key: string]: any } = {};
kvp[this.buildKey(key)] = value;
this.storage.set(kvp, () => resolve());
});
}
public remove(key: string): Promise<void> {
return new Promise((resolve) => {
this.storage.remove(this.buildKey(key), () => resolve());
});
}
public removeAll(): Promise<void> {
return new Promise((resolve) => {
this.storage.clear(() => resolve());
});
}
public keys(): Promise<{ [key: string]: any }> {
return new Promise((resolve) => {
const ret: { [key: string]: any } = {};
const prefix = this.buildKey("");
this.storage.get((items) => {
Object.keys(items).forEach((key) => {
if (key.startsWith(prefix)) {
ret[key.substring(prefix.length)] = items[key];
}
});
resolve(ret);
});
});
}
}

217
src/pkg/config/config.ts Normal file
View File

@@ -0,0 +1,217 @@
import { Message } from "@arco-design/web-react";
import ChromeStorage from "./chrome_storage";
import { defaultConfig } from "../../../eslint/linter-config";
import { FileSystemType } from "@Packages/filesystem/factory";
import { MessageQueue } from "@Packages/message/message_queue";
export const SystamConfigChange = "systemConfigChange";
export type CloudSyncConfig = {
enable: boolean;
syncDelete: boolean;
filesystem: FileSystemType;
params: { [key: string]: any };
};
export type CATFileStorage = {
filesystem: FileSystemType;
params: { [key: string]: any };
status: "unset" | "success" | "error";
};
export class SystemConfig {
public cache = new Map<string, any>();
public storage = new ChromeStorage("system", true);
constructor(private mq: MessageQueue) {
this.mq.subscribe("systemConfigChange", (msg) => {
const { key, value } = msg;
this.cache.set(key, value);
});
}
async getAll(): Promise<{ [key: string]: any }> {
const ret: { [key: string]: any } = {};
const list = await this.storage.keys();
Object.keys(list).forEach((key) => {
this.cache.set(key, list[key]);
ret[key] = list[key];
});
return ret;
}
get<T>(key: string, defaultValue: T): Promise<T> {
if (this.cache.has(key)) {
return Promise.resolve(this.cache.get(key));
}
return this.storage.get(key).then((val) => {
if (val === undefined) {
return defaultValue;
}
this.cache.set(key, val);
return val;
});
}
public set(key: string, val: any) {
this.cache.set(key, val);
this.storage.set(key, val);
// 发送消息通知更新
this.mq.publish(SystamConfigChange, {
key,
value: val,
});
}
public getChangetime() {
return this.get("changetime", 0);
}
public setChangetime(n: number) {
this.set("changetime", 0);
}
// 检查更新周期,单位为秒
public getCheckScriptUpdateCycle() {
return this.get("check_script_update_cycle", 86400);
}
public setCheckScriptUpdateCycle(n: number) {
this.set("check_script_update_cycle", n);
}
public getSilenceUpdateScript() {
return this.get("silence_update_script", false);
}
public setSilenceUpdateScript(val: boolean) {
this.set("silence_update_script", val);
}
public getEnableAutoSync() {
return this.get("enable_auto_sync", true);
}
public setEnableAutoSync(enable: boolean) {
this.set("enable_auto_sync", enable);
}
// 更新已经禁用的脚本
public getUpdateDisableScript() {
return this.get("update_disable_script", true);
}
public setUpdateDisableScript(enable: boolean) {
this.set("update_disable_script", enable);
}
public getVscodeUrl() {
return this.get("vscode_url", "ws://localhost:8642");
}
public setVscodeUrl(val: string) {
this.set("vscode_url", val);
}
public getVscodeReconnect() {
return this.get("vscode_reconnect", false);
}
public setVscodeReconnect(val: boolean) {
this.set("vscode_reconnect", val);
}
public getBackup(): Promise<{
filesystem: FileSystemType;
params: { [key: string]: any };
}> {
return this.get("backup", {
filesystem: "webdav",
params: {},
});
}
public setBackup(data: { filesystem: FileSystemType; params: { [key: string]: any } }) {
this.set("backup", data);
}
getCloudSync(): Promise<CloudSyncConfig> {
return this.get("cloud_sync", {
enable: false,
syncDelete: true,
filesystem: "webdav",
params: {},
});
}
setCloudSync(data: CloudSyncConfig) {
this.set("cloud_sync", data);
}
getCatFileStorage(): Promise<CATFileStorage> {
return this.get("cat_file_storage", {
status: "unset",
filesystem: "webdav",
params: {},
});
}
setCatFileStorage(data: CATFileStorage | undefined) {
this.set("cat_file_storage", data);
}
getEnableEslint() {
return this.get("enable_eslint", true);
}
setEnableEslint(val: boolean) {
this.set("enable_eslint", val);
}
getEslintConfig() {
return this.get("eslint_config", defaultConfig);
}
setEslintConfig(v: string) {
if (v === "") {
this.set("eslint_config", v);
Message.success("ESLint规则已重置");
return;
}
try {
JSON.parse(v);
this.set("eslint_config", v);
Message.success("ESLint规则已保存");
} catch (err: any) {
Message.error(err.toString());
}
}
// 日志清理周期
getLogCleanCycle() {
return this.get("log_clean_cycle", 7);
}
setLogCleanCycle(val: number) {
this.set("log_clean_cycle", val);
}
// 设置脚本列表列宽度
getScriptListColumnWidth() {
return this.get<{ [key: string]: number }>("script_list_column_width", {});
}
setScriptListColumnWidth(val: { [key: string]: number }) {
this.set("script_list_column_width", val);
}
// 展开菜单数
getMenuExpandNum() {
return this.get("menu_expand_num", 5);
}
setMenuExpandNum(val: number) {
this.set("menu_expand_num", val);
}
}