mirror of
https://github.com/bolucat/Archive.git
synced 2026-04-22 16:07:49 +08:00
Update On Fri Feb 6 20:02:30 CET 2026
This commit is contained in:
@@ -1263,3 +1263,4 @@ Update On Sun Feb 1 19:47:34 CET 2026
|
||||
Update On Tue Feb 3 20:07:51 CET 2026
|
||||
Update On Wed Feb 4 20:03:26 CET 2026
|
||||
Update On Thu Feb 5 20:02:24 CET 2026
|
||||
Update On Fri Feb 6 20:02:22 CET 2026
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export default {
|
||||
'*.{js,cjs,.mjs,jsx}': (filenames) => {
|
||||
const configFiles = [
|
||||
'eslint.config.js',
|
||||
'.oxlintrc.json',
|
||||
'.lintstagedrc.js',
|
||||
'commitlint.config.js',
|
||||
]
|
||||
@@ -9,26 +9,26 @@ export default {
|
||||
(file) => !configFiles.some((config) => file.endsWith(config)),
|
||||
)
|
||||
if (filtered.length === 0) return []
|
||||
return ['prettier --write', 'eslint --cache --fix']
|
||||
return ['prettier --write', 'oxlint --fix']
|
||||
},
|
||||
'scripts/**/*.{ts,tsx}': [
|
||||
'prettier --write',
|
||||
'node ./node_modules/eslint/bin/eslint.js --cache --fix',
|
||||
'oxlint --fix',
|
||||
() => 'tsc -p scripts/tsconfig.json --noEmit',
|
||||
],
|
||||
'frontend/interface/**/*.{ts,tsx}': [
|
||||
'prettier --write',
|
||||
'node ./node_modules/eslint/bin/eslint.js --cache --fix',
|
||||
'oxlint --fix',
|
||||
() => 'tsc -p frontend/interface/tsconfig.json --noEmit',
|
||||
],
|
||||
'frontend/ui/**/*.{ts,tsx}': [
|
||||
'prettier --write',
|
||||
'node ./node_modules/eslint/bin/eslint.js --cache --fix',
|
||||
'oxlint --fix',
|
||||
() => 'tsc -p frontend/ui/tsconfig.json --noEmit',
|
||||
],
|
||||
'frontend/nyanpasu/**/*.{ts,tsx}': [
|
||||
'prettier --write',
|
||||
'node ./node_modules/eslint/bin/eslint.js --cache --fix',
|
||||
'oxlint --fix',
|
||||
() => 'tsc -p frontend/nyanpasu/tsconfig.json --noEmit',
|
||||
],
|
||||
'backend/**/*.{rs,toml}': [
|
||||
|
||||
@@ -0,0 +1,651 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": [],
|
||||
"categories": {
|
||||
"correctness": "off"
|
||||
},
|
||||
"env": {
|
||||
"builtin": true
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"**/node_modules",
|
||||
"**/.DS_Store",
|
||||
"**/dist",
|
||||
"**/*.local",
|
||||
"**/update.json",
|
||||
"scripts/_env.sh",
|
||||
"**/.eslintcache",
|
||||
"**/.stylelintcache",
|
||||
"**/tauri.nightly.conf.json",
|
||||
"**/tauri.preview.conf.json",
|
||||
"**/.idea",
|
||||
"**/*.tsbuildinfo",
|
||||
"manifest/site/updater/*",
|
||||
"!manifest/site/updater/.gitkeep",
|
||||
"backend/tauri/gen/",
|
||||
"**/index.html",
|
||||
"**/node_modules/",
|
||||
"node_modules/",
|
||||
"backend/",
|
||||
"backend/**/target",
|
||||
"scripts/deno/**",
|
||||
".lintstagedrc.js",
|
||||
"commitlint.config.js"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.{jsx,mjsx,tsx,mtsx}"],
|
||||
"rules": {
|
||||
"react/display-name": "error",
|
||||
"react/jsx-key": "error",
|
||||
"react/jsx-no-comment-textnodes": "error",
|
||||
"react/jsx-no-duplicate-props": "error",
|
||||
"react/jsx-no-target-blank": "error",
|
||||
// "react/jsx-no-undef": "error",
|
||||
"react/no-children-prop": "error",
|
||||
"react/no-danger-with-children": "error",
|
||||
"react/no-direct-mutation-state": "error",
|
||||
"react/no-find-dom-node": "error",
|
||||
"react/no-is-mounted": "error",
|
||||
"react/no-render-return-value": "error",
|
||||
"react/no-string-refs": "error",
|
||||
"react/no-unknown-property": "error",
|
||||
"react/no-unsafe": "off",
|
||||
"react/react-in-jsx-scope": "error"
|
||||
},
|
||||
"plugins": ["react"]
|
||||
},
|
||||
{
|
||||
"files": ["**/*.{jsx,mjsx,tsx,mtsx}"],
|
||||
"rules": {
|
||||
"react-hooks/rules-of-hooks": "error",
|
||||
"react-hooks/exhaustive-deps": "warn"
|
||||
},
|
||||
"plugins": ["react"]
|
||||
},
|
||||
{
|
||||
"files": ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"],
|
||||
"rules": {
|
||||
"no-var": "warn",
|
||||
"accessor-pairs": [
|
||||
"error",
|
||||
{
|
||||
"setWithoutGet": true,
|
||||
"enforceForClassMembers": true
|
||||
}
|
||||
],
|
||||
"array-callback-return": [
|
||||
"error",
|
||||
{
|
||||
"allowImplicit": false,
|
||||
"checkForEach": false
|
||||
}
|
||||
],
|
||||
"constructor-super": "error",
|
||||
"curly": ["error", "multi-line"],
|
||||
"default-case-last": "error",
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
"null": "ignore"
|
||||
}
|
||||
],
|
||||
"new-cap": [
|
||||
"error",
|
||||
{
|
||||
"newIsCap": true,
|
||||
"capIsNew": false,
|
||||
"properties": true
|
||||
}
|
||||
],
|
||||
"no-array-constructor": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"no-caller": "error",
|
||||
"no-case-declarations": "error",
|
||||
"no-class-assign": "error",
|
||||
"no-compare-neg-zero": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-const-assign": "error",
|
||||
"no-constant-condition": [
|
||||
"error",
|
||||
{
|
||||
"checkLoops": false
|
||||
}
|
||||
],
|
||||
"no-control-regex": "error",
|
||||
"no-debugger": "error",
|
||||
"no-delete-var": "error",
|
||||
"no-dupe-class-members": "error",
|
||||
"no-dupe-keys": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"no-empty": [
|
||||
"error",
|
||||
{
|
||||
"allowEmptyCatch": true
|
||||
}
|
||||
],
|
||||
"no-empty-character-class": "error",
|
||||
"no-empty-pattern": "error",
|
||||
"no-eval": "error",
|
||||
"no-ex-assign": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-extra-boolean-cast": "error",
|
||||
"no-fallthrough": "error",
|
||||
"no-func-assign": "error",
|
||||
"no-global-assign": "error",
|
||||
"no-import-assign": "error",
|
||||
"no-invalid-regexp": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-iterator": "error",
|
||||
"no-labels": [
|
||||
"error",
|
||||
{
|
||||
"allowLoop": false,
|
||||
"allowSwitch": false
|
||||
}
|
||||
],
|
||||
"no-lone-blocks": "error",
|
||||
"no-loss-of-precision": "error",
|
||||
"no-prototype-builtins": "error",
|
||||
"no-useless-catch": "error",
|
||||
"no-multi-str": "error",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-object-constructor": "error",
|
||||
"no-new-native-nonconstructor": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-obj-calls": "error",
|
||||
"no-proto": "error",
|
||||
"no-redeclare": [
|
||||
"error",
|
||||
{
|
||||
"builtinGlobals": false
|
||||
}
|
||||
],
|
||||
"no-regex-spaces": "error",
|
||||
"no-return-assign": ["error", "except-parens"],
|
||||
"no-self-assign": [
|
||||
"error",
|
||||
{
|
||||
"props": true
|
||||
}
|
||||
],
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-sparse-arrays": "error",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-this-before-super": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-unneeded-ternary": [
|
||||
"error",
|
||||
{
|
||||
"defaultAssignment": false
|
||||
}
|
||||
],
|
||||
"no-unsafe-finally": "error",
|
||||
"no-unsafe-negation": "error",
|
||||
"no-unused-expressions": [
|
||||
"error",
|
||||
{
|
||||
"allowShortCircuit": true,
|
||||
"allowTernary": true,
|
||||
"allowTaggedTemplates": true
|
||||
}
|
||||
],
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"args": "none",
|
||||
"caughtErrors": "none",
|
||||
"ignoreRestSiblings": true,
|
||||
"vars": "all"
|
||||
}
|
||||
],
|
||||
"no-useless-call": "error",
|
||||
"no-useless-computed-key": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-useless-escape": "error",
|
||||
"no-useless-rename": "error",
|
||||
"no-useless-return": "error",
|
||||
"no-void": "error",
|
||||
"no-with": "error",
|
||||
"prefer-const": [
|
||||
"error",
|
||||
{
|
||||
"destructuring": "all"
|
||||
}
|
||||
],
|
||||
"prefer-promise-reject-errors": "error",
|
||||
"symbol-description": "error",
|
||||
"unicode-bom": ["error", "never"],
|
||||
"use-isnan": [
|
||||
"error",
|
||||
{
|
||||
"enforceForSwitchCase": true,
|
||||
"enforceForIndexOf": true
|
||||
}
|
||||
],
|
||||
"valid-typeof": [
|
||||
"error",
|
||||
{
|
||||
"requireStringLiterals": true
|
||||
}
|
||||
],
|
||||
"yoda": ["error", "never"],
|
||||
"import-x/first": "error",
|
||||
"import-x/no-absolute-path": [
|
||||
"error",
|
||||
{
|
||||
"esmodule": true,
|
||||
"commonjs": true,
|
||||
"amd": false
|
||||
}
|
||||
],
|
||||
"import-x/no-duplicates": "error",
|
||||
"import-x/no-named-default": "error",
|
||||
"import-x/no-webpack-loader-syntax": "error",
|
||||
"promise/param-names": "error",
|
||||
"node/no-exports-assign": "error",
|
||||
"node/no-new-require": "error"
|
||||
},
|
||||
"globals": {
|
||||
"__dirname": "readonly",
|
||||
"__filename": "readonly",
|
||||
"AbortController": "readonly",
|
||||
"AbortSignal": "readonly",
|
||||
"atob": "readonly",
|
||||
"Blob": "readonly",
|
||||
"BroadcastChannel": "readonly",
|
||||
"btoa": "readonly",
|
||||
"Buffer": "readonly",
|
||||
"ByteLengthQueuingStrategy": "readonly",
|
||||
"clearImmediate": "readonly",
|
||||
"clearInterval": "readonly",
|
||||
"clearTimeout": "readonly",
|
||||
"CloseEvent": "readonly",
|
||||
"CompressionStream": "readonly",
|
||||
"console": "readonly",
|
||||
"CountQueuingStrategy": "readonly",
|
||||
"crypto": "readonly",
|
||||
"Crypto": "readonly",
|
||||
"CryptoKey": "readonly",
|
||||
"CustomEvent": "readonly",
|
||||
"DecompressionStream": "readonly",
|
||||
"DOMException": "readonly",
|
||||
"Event": "readonly",
|
||||
"EventTarget": "readonly",
|
||||
"fetch": "readonly",
|
||||
"File": "readonly",
|
||||
"FormData": "readonly",
|
||||
"Headers": "readonly",
|
||||
"MessageChannel": "readonly",
|
||||
"MessageEvent": "readonly",
|
||||
"MessagePort": "readonly",
|
||||
"navigator": "readonly",
|
||||
"Navigator": "readonly",
|
||||
"performance": "readonly",
|
||||
"Performance": "readonly",
|
||||
"PerformanceEntry": "readonly",
|
||||
"PerformanceMark": "readonly",
|
||||
"PerformanceMeasure": "readonly",
|
||||
"PerformanceObserver": "readonly",
|
||||
"PerformanceObserverEntryList": "readonly",
|
||||
"PerformanceResourceTiming": "readonly",
|
||||
"process": "readonly",
|
||||
"queueMicrotask": "readonly",
|
||||
"ReadableByteStreamController": "readonly",
|
||||
"ReadableStream": "readonly",
|
||||
"ReadableStreamBYOBReader": "readonly",
|
||||
"ReadableStreamBYOBRequest": "readonly",
|
||||
"ReadableStreamDefaultController": "readonly",
|
||||
"ReadableStreamDefaultReader": "readonly",
|
||||
"Request": "readonly",
|
||||
"Response": "readonly",
|
||||
"setImmediate": "readonly",
|
||||
"setInterval": "readonly",
|
||||
"setTimeout": "readonly",
|
||||
"structuredClone": "readonly",
|
||||
"SubtleCrypto": "readonly",
|
||||
"TextDecoder": "readonly",
|
||||
"TextDecoderStream": "readonly",
|
||||
"TextEncoder": "readonly",
|
||||
"TextEncoderStream": "readonly",
|
||||
"TransformStream": "readonly",
|
||||
"TransformStreamDefaultController": "readonly",
|
||||
"URL": "readonly",
|
||||
"URLSearchParams": "readonly",
|
||||
"WebAssembly": "readonly",
|
||||
"WebSocket": "readonly",
|
||||
"WritableStream": "readonly",
|
||||
"WritableStreamDefaultController": "readonly",
|
||||
"WritableStreamDefaultWriter": "readonly",
|
||||
"document": "readonly",
|
||||
"window": "readonly"
|
||||
},
|
||||
"env": {
|
||||
"commonjs": true,
|
||||
"es2024": true
|
||||
},
|
||||
"plugins": ["import", "node", "promise"]
|
||||
},
|
||||
{
|
||||
"files": ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"],
|
||||
"rules": {
|
||||
"react/jsx-boolean-value": "error",
|
||||
"react/jsx-fragments": ["error", "syntax"],
|
||||
"react/jsx-handler-names": "error",
|
||||
"react/jsx-key": [
|
||||
"error",
|
||||
{
|
||||
"checkFragmentShorthand": true
|
||||
}
|
||||
],
|
||||
"react/jsx-no-comment-textnodes": "error",
|
||||
"react/jsx-no-duplicate-props": "error",
|
||||
"react/jsx-no-target-blank": [
|
||||
"error",
|
||||
{
|
||||
"enforceDynamicLinks": "always"
|
||||
}
|
||||
],
|
||||
// "react/jsx-no-undef": [
|
||||
// "error",
|
||||
// {
|
||||
// "allowGlobals": true
|
||||
// }
|
||||
// ],
|
||||
"react/no-children-prop": "error",
|
||||
"react/no-danger-with-children": "error",
|
||||
"react/no-direct-mutation-state": "error",
|
||||
"react/no-find-dom-node": "error",
|
||||
"react/no-is-mounted": "error",
|
||||
"react/no-string-refs": [
|
||||
"error",
|
||||
{
|
||||
"noTemplateLiterals": true
|
||||
}
|
||||
],
|
||||
"react/no-unescaped-entities": "off",
|
||||
"react/no-render-return-value": "error",
|
||||
"react/self-closing-comp": "error"
|
||||
},
|
||||
"plugins": ["react"]
|
||||
},
|
||||
{
|
||||
"files": ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"],
|
||||
"rules": {
|
||||
"constructor-super": "off",
|
||||
"no-const-assign": "off",
|
||||
"no-dupe-class-members": "error",
|
||||
"no-dupe-keys": "off",
|
||||
"no-func-assign": "off",
|
||||
"no-import-assign": "off",
|
||||
"no-new-native-nonconstructor": "off",
|
||||
"no-obj-calls": "off",
|
||||
"no-redeclare": [
|
||||
"error",
|
||||
{
|
||||
"builtinGlobals": false
|
||||
}
|
||||
],
|
||||
"no-this-before-super": "off",
|
||||
"no-unsafe-negation": "off",
|
||||
"no-array-constructor": "error",
|
||||
"no-loss-of-precision": "error",
|
||||
"no-unused-expressions": [
|
||||
"error",
|
||||
{
|
||||
"allowShortCircuit": true,
|
||||
"allowTernary": true,
|
||||
"allowTaggedTemplates": true
|
||||
}
|
||||
],
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"args": "none",
|
||||
"caughtErrors": "none",
|
||||
"ignoreRestSiblings": true,
|
||||
"vars": "all"
|
||||
}
|
||||
],
|
||||
"no-useless-constructor": "error"
|
||||
},
|
||||
"plugins": ["typescript"]
|
||||
},
|
||||
{
|
||||
"files": ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"],
|
||||
"rules": {
|
||||
"curly": "off",
|
||||
"no-unexpected-multiline": "off",
|
||||
"unicorn/empty-brace-spaces": "off",
|
||||
"unicorn/no-nested-ternary": "off",
|
||||
"unicorn/number-literal-case": "off"
|
||||
},
|
||||
"plugins": ["unicorn"]
|
||||
},
|
||||
{
|
||||
"files": ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"],
|
||||
"rules": {
|
||||
"curly": "off",
|
||||
"no-unexpected-multiline": "off",
|
||||
"unicorn/empty-brace-spaces": "off",
|
||||
"unicorn/no-nested-ternary": "off",
|
||||
"unicorn/number-literal-case": "off",
|
||||
"arrow-body-style": "off"
|
||||
},
|
||||
"plugins": ["unicorn"]
|
||||
},
|
||||
{
|
||||
"files": ["**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}"],
|
||||
"rules": {
|
||||
"no-console": "off",
|
||||
"no-debugger": "off",
|
||||
"no-unused-vars": "warn",
|
||||
"react/react-in-jsx-scope": "off"
|
||||
},
|
||||
"plugins": ["typescript", "react"]
|
||||
},
|
||||
{
|
||||
"files": ["**/*.{ts,tsx,mtsx}"],
|
||||
"rules": {
|
||||
"constructor-super": "off",
|
||||
"no-class-assign": "off",
|
||||
"no-const-assign": "off",
|
||||
"no-dupe-class-members": "off",
|
||||
"no-dupe-keys": "off",
|
||||
"no-func-assign": "off",
|
||||
"no-import-assign": "off",
|
||||
"no-new-native-nonconstructor": "off",
|
||||
"no-obj-calls": "off",
|
||||
"no-redeclare": "off",
|
||||
"no-setter-return": "off",
|
||||
"no-this-before-super": "off",
|
||||
"no-unsafe-negation": "off",
|
||||
"no-var": "error",
|
||||
"no-with": "off",
|
||||
"prefer-const": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["**/*.{ts,tsx,mtsx}"],
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-ts-comment": "error",
|
||||
"no-array-constructor": "error",
|
||||
"@typescript-eslint/no-duplicate-enum-values": "error",
|
||||
"@typescript-eslint/no-empty-object-type": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/no-extra-non-null-assertion": "error",
|
||||
"@typescript-eslint/no-misused-new": "error",
|
||||
"@typescript-eslint/no-namespace": "error",
|
||||
"@typescript-eslint/no-non-null-asserted-optional-chain": "error",
|
||||
"@typescript-eslint/no-require-imports": "error",
|
||||
"@typescript-eslint/no-this-alias": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-constraint": "error",
|
||||
"@typescript-eslint/no-unsafe-declaration-merging": "error",
|
||||
"@typescript-eslint/no-unsafe-function-type": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-unused-vars": "error",
|
||||
"@typescript-eslint/no-wrapper-object-types": "error",
|
||||
"@typescript-eslint/prefer-as-const": "error",
|
||||
"@typescript-eslint/prefer-namespace-keyword": "error",
|
||||
"@typescript-eslint/triple-slash-reference": "error"
|
||||
},
|
||||
"plugins": ["typescript"]
|
||||
},
|
||||
{
|
||||
"files": ["**/*.{ts,tsx,mtsx}"],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"no-unused-vars": "warn"
|
||||
},
|
||||
"plugins": ["typescript"]
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"frontend/nyanpasu/vite.config.ts",
|
||||
"frontend/nyanpasu/tailwind.config.ts"
|
||||
],
|
||||
"rules": {
|
||||
"constructor-super": "off",
|
||||
"no-class-assign": "off",
|
||||
"no-const-assign": "off",
|
||||
"no-dupe-class-members": "off",
|
||||
"no-dupe-keys": "off",
|
||||
"no-func-assign": "off",
|
||||
"no-import-assign": "off",
|
||||
"no-new-native-nonconstructor": "off",
|
||||
"no-obj-calls": "off",
|
||||
"no-redeclare": "off",
|
||||
"no-setter-return": "off",
|
||||
"no-this-before-super": "off",
|
||||
"no-unsafe-negation": "off",
|
||||
"no-var": "error",
|
||||
"no-with": "off",
|
||||
"prefer-const": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"frontend/nyanpasu/vite.config.ts",
|
||||
"frontend/nyanpasu/tailwind.config.ts"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-ts-comment": "error",
|
||||
"no-array-constructor": "error",
|
||||
"@typescript-eslint/no-duplicate-enum-values": "error",
|
||||
"@typescript-eslint/no-empty-object-type": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/no-extra-non-null-assertion": "error",
|
||||
"@typescript-eslint/no-misused-new": "error",
|
||||
"@typescript-eslint/no-namespace": "error",
|
||||
"@typescript-eslint/no-non-null-asserted-optional-chain": "error",
|
||||
"@typescript-eslint/no-require-imports": "error",
|
||||
"@typescript-eslint/no-this-alias": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-constraint": "error",
|
||||
"@typescript-eslint/no-unsafe-declaration-merging": "error",
|
||||
"@typescript-eslint/no-unsafe-function-type": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-unused-vars": "error",
|
||||
"@typescript-eslint/no-wrapper-object-types": "error",
|
||||
"@typescript-eslint/prefer-as-const": "error",
|
||||
"@typescript-eslint/prefer-namespace-keyword": "error",
|
||||
"@typescript-eslint/triple-slash-reference": "error"
|
||||
},
|
||||
"plugins": ["typescript"]
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"frontend/nyanpasu/vite.config.ts",
|
||||
"frontend/nyanpasu/tailwind.config.ts"
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"no-unused-vars": "warn"
|
||||
},
|
||||
"plugins": ["typescript"]
|
||||
},
|
||||
{
|
||||
"files": ["frontend/ui/vite.config.ts"],
|
||||
"rules": {
|
||||
"constructor-super": "off",
|
||||
"no-class-assign": "off",
|
||||
"no-const-assign": "off",
|
||||
"no-dupe-class-members": "off",
|
||||
"no-dupe-keys": "off",
|
||||
"no-func-assign": "off",
|
||||
"no-import-assign": "off",
|
||||
"no-new-native-nonconstructor": "off",
|
||||
"no-obj-calls": "off",
|
||||
"no-redeclare": "off",
|
||||
"no-setter-return": "off",
|
||||
"no-this-before-super": "off",
|
||||
"no-unsafe-negation": "off",
|
||||
"no-var": "error",
|
||||
"no-with": "off",
|
||||
"prefer-const": "error",
|
||||
"prefer-rest-params": "error",
|
||||
"prefer-spread": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["frontend/ui/vite.config.ts"],
|
||||
"rules": {
|
||||
"@typescript-eslint/ban-ts-comment": "error",
|
||||
"no-array-constructor": "error",
|
||||
"@typescript-eslint/no-duplicate-enum-values": "error",
|
||||
"@typescript-eslint/no-empty-object-type": "error",
|
||||
"@typescript-eslint/no-explicit-any": "error",
|
||||
"@typescript-eslint/no-extra-non-null-assertion": "error",
|
||||
"@typescript-eslint/no-misused-new": "error",
|
||||
"@typescript-eslint/no-namespace": "error",
|
||||
"@typescript-eslint/no-non-null-asserted-optional-chain": "error",
|
||||
"@typescript-eslint/no-require-imports": "error",
|
||||
"@typescript-eslint/no-this-alias": "error",
|
||||
"@typescript-eslint/no-unnecessary-type-constraint": "error",
|
||||
"@typescript-eslint/no-unsafe-declaration-merging": "error",
|
||||
"@typescript-eslint/no-unsafe-function-type": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-unused-vars": "error",
|
||||
"@typescript-eslint/no-wrapper-object-types": "error",
|
||||
"@typescript-eslint/prefer-as-const": "error",
|
||||
"@typescript-eslint/prefer-namespace-keyword": "error",
|
||||
"@typescript-eslint/triple-slash-reference": "error"
|
||||
},
|
||||
"plugins": ["typescript"]
|
||||
},
|
||||
{
|
||||
"files": ["frontend/ui/vite.config.ts"],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "warn",
|
||||
"no-unused-vars": "warn"
|
||||
},
|
||||
"plugins": ["typescript"]
|
||||
},
|
||||
{
|
||||
"files": ["**/*.{jsx,mjsx,tsx,mtsx}"],
|
||||
"globals": {
|
||||
"AudioWorkletGlobalScope": "readonly",
|
||||
"AudioWorkletProcessor": "readonly",
|
||||
"currentFrame": "readonly",
|
||||
"currentTime": "readonly",
|
||||
"registerProcessor": "readonly",
|
||||
"sampleRate": "readonly",
|
||||
"WorkletGlobalScope": "readonly"
|
||||
},
|
||||
"env": {
|
||||
"browser": true,
|
||||
"serviceworker": true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
+2
-2
@@ -4,13 +4,13 @@
|
||||
"editorconfig.editorconfig",
|
||||
"vadimcn.vscode-lldb",
|
||||
"bungcip.better-toml",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"denoland.vscode-deno",
|
||||
"esbenp.prettier-vscode",
|
||||
"yoavbls.pretty-ts-errors",
|
||||
"rust-lang.rust-analyzer",
|
||||
"syler.sass-indented",
|
||||
"stylelint.vscode-stylelint",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"oxc.oxc-vscode"
|
||||
]
|
||||
}
|
||||
|
||||
Generated
+8
-8
@@ -355,7 +355,7 @@ dependencies = [
|
||||
"objc2-foundation 0.3.2",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
"wl-clipboard-rs",
|
||||
"x11rb",
|
||||
]
|
||||
@@ -2859,7 +2859,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "778e2ac28f6c47af28e4907f13ffd1e1ddbd400980a9abd7c8df189bf578a5ad"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4456,9 +4456,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "interprocess"
|
||||
version = "2.2.3"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d"
|
||||
checksum = "7b00d05442c2106c75b7410f820b152f61ec0edc7befcb9b381b673a20314753"
|
||||
dependencies = [
|
||||
"doctest-file",
|
||||
"futures-core",
|
||||
@@ -7223,7 +7223,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"socket2",
|
||||
"tracing",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7786,7 +7786,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -7799,7 +7799,7 @@ dependencies = [
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.11.0",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -9473,7 +9473,7 @@ dependencies = [
|
||||
"getrandom 0.3.3",
|
||||
"once_cell",
|
||||
"rustix 1.1.3",
|
||||
"windows-sys 0.52.0",
|
||||
"windows-sys 0.60.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
@@ -307,7 +307,7 @@ pub fn run() -> std::io::Result<()> {
|
||||
.map_err(io::Error::other)
|
||||
})
|
||||
.bigint(BigIntExportBehavior::Number)
|
||||
.header("/* eslint-disable */\n// @ts-nocheck"),
|
||||
.header("/* oxlint-disable */\n// @ts-nocheck"),
|
||||
SPECTA_BINDINGS_PATH,
|
||||
) {
|
||||
Ok(_) => {
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
// @ts-check
|
||||
import path from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import eslintConfigPrettier from 'eslint-config-prettier'
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||
import react from 'eslint-plugin-react'
|
||||
import pluginReactCompiler from 'eslint-plugin-react-compiler'
|
||||
import pluginReactHooks from 'eslint-plugin-react-hooks'
|
||||
import globals from 'globals'
|
||||
import neostandard from 'neostandard'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import { includeIgnoreFile } from '@eslint/compat'
|
||||
import { FlatCompat } from '@eslint/eslintrc'
|
||||
|
||||
// import ImportX from "eslint-plugin-import-x";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = path.dirname(__filename)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const compat = new FlatCompat({
|
||||
// import.meta.dirname is available after Node.js v20.11.0
|
||||
baseDirectory: __dirname,
|
||||
})
|
||||
const gitignorePath = path.resolve(__dirname, '.gitignore')
|
||||
|
||||
const ignores = [
|
||||
path.resolve(__dirname, 'index.html'),
|
||||
'**/node_modules/',
|
||||
'node_modules/',
|
||||
'**/dist/',
|
||||
'dist/',
|
||||
'backend/',
|
||||
'backend/**/target',
|
||||
'scripts/deno/**',
|
||||
'eslint.config.js',
|
||||
'.lintstagedrc.js',
|
||||
'commitlint.config.js',
|
||||
]
|
||||
|
||||
export default tseslint.config(
|
||||
includeIgnoreFile(gitignorePath),
|
||||
{
|
||||
ignores,
|
||||
},
|
||||
{
|
||||
files: ['**/*.{jsx,mjsx,tsx,mtsx}'],
|
||||
extends: [react.configs.flat.recommended],
|
||||
plugins: {
|
||||
'react-hooks': pluginReactHooks,
|
||||
'react-compiler': pluginReactCompiler,
|
||||
},
|
||||
settings: {
|
||||
react: {
|
||||
version: 'detect',
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
'react-hooks/rules-of-hooks': 'error',
|
||||
'react-hooks/exhaustive-deps': 'warn',
|
||||
'react-compiler/react-compiler': 'warn',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
|
||||
extends: [
|
||||
...neostandard({ ts: true, semi: true, noStyle: true }),
|
||||
eslintConfigPrettier,
|
||||
eslintPluginPrettierRecommended,
|
||||
],
|
||||
rules: {
|
||||
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'react/react-in-jsx-scope': 'off',
|
||||
'prettier/prettier': [
|
||||
'error',
|
||||
{
|
||||
singleQuote: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.{ts,tsx,mtsx}'],
|
||||
extends: [...tseslint.configs.recommended],
|
||||
ignores: [
|
||||
...ignores,
|
||||
'frontend/nyanpasu/vite.config.ts',
|
||||
'frontend/nyanpasu/tailwind.config.ts',
|
||||
'frontend/ui/vite.config.ts',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
},
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: true,
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'frontend/nyanpasu/vite.config.ts',
|
||||
'frontend/nyanpasu/tailwind.config.ts',
|
||||
],
|
||||
extends: [...tseslint.configs.recommended],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
},
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: './frontend/nyanpasu/tsconfig.node.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['frontend/ui/vite.config.ts'],
|
||||
extends: [...tseslint.configs.recommended],
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
},
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: './frontend/ui/tsconfig.json',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.{jsx,mjsx,tsx,mtsx}'],
|
||||
languageOptions: {
|
||||
...react.configs.flat?.recommended.languageOptions,
|
||||
globals: {
|
||||
...globals.serviceworker,
|
||||
...globals.browser,
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -22,6 +22,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/react": "19.2.11"
|
||||
"@types/react": "19.2.13"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import * as TAURI_API_EVENT from '@tauri-apps/api/event'
|
||||
import { type WebviewWindow as __WebviewWindow__ } from '@tauri-apps/api/webviewWindow'
|
||||
|
||||
/* eslint-disable */
|
||||
/* oxlint-disable */
|
||||
// @ts-nocheck
|
||||
// This file was generated by [tauri-specta](https://github.com/oscartbeaumont/tauri-specta). Do not edit this file manually.
|
||||
|
||||
|
||||
@@ -3,6 +3,16 @@
|
||||
*/
|
||||
export const NYANPASU_BACKEND_EVENT_NAME = 'nyanpasu://mutation'
|
||||
|
||||
/**
|
||||
* Service prompt query key, used by useServicePrompt hook
|
||||
*/
|
||||
export const SERVICE_PROMPT_QUERY_KEY = 'service-prompt'
|
||||
|
||||
/**
|
||||
* Core dir query key, used by useCoreDir hook
|
||||
*/
|
||||
export const CORE_DIR_QUERY_KEY = 'core-dir'
|
||||
|
||||
/**
|
||||
* Server port query key, used by useServerPort hook
|
||||
*/
|
||||
|
||||
@@ -22,6 +22,8 @@ export * from './use-runtime-profile'
|
||||
export * from './use-settings'
|
||||
export * from './use-system-proxy'
|
||||
export * from './use-system-service'
|
||||
export * from './use-service-prompt'
|
||||
export * from './use-core-dir'
|
||||
|
||||
export { commands } from './bindings'
|
||||
export type * from './bindings'
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { unwrapResult } from '@/utils'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { commands } from './bindings'
|
||||
import { CORE_DIR_QUERY_KEY } from './consts'
|
||||
|
||||
export const useCoreDir = () => {
|
||||
const query = useQuery({
|
||||
queryKey: [CORE_DIR_QUERY_KEY],
|
||||
queryFn: async () => {
|
||||
return unwrapResult(await commands.getCoreDir())
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
...query,
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ export const useProxyMode = () => {
|
||||
}
|
||||
|
||||
return modes
|
||||
}, [clashConfig.query.data])
|
||||
}, [clashConfig.query.data, clashCore.value])
|
||||
|
||||
const upsert = async (mode: string) => {
|
||||
await deleteConnections()
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import { unwrapResult } from '@/utils'
|
||||
import { useQuery } from '@tanstack/react-query'
|
||||
import { commands } from './bindings'
|
||||
import { SERVICE_PROMPT_QUERY_KEY } from './consts'
|
||||
|
||||
export const useServicePrompt = () => {
|
||||
const query = useQuery({
|
||||
queryKey: [SERVICE_PROMPT_QUERY_KEY],
|
||||
queryFn: async () => {
|
||||
return unwrapResult(await commands.getServiceInstallPrompt())
|
||||
},
|
||||
})
|
||||
|
||||
return {
|
||||
...query,
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
// oxlint-disable typescript/no-explicit-any
|
||||
import { IPSBResponse } from '@/openapi'
|
||||
import { invoke } from '@tauri-apps/api/core'
|
||||
import type {
|
||||
|
||||
@@ -9,6 +9,15 @@
|
||||
"navbar_label_rules": "Rules",
|
||||
"navbar_label_settings": "Settings",
|
||||
"navbar_label_providers": "Providers",
|
||||
"header_help_action_title": "Help",
|
||||
"header_help_action_wiki": "Official Wiki",
|
||||
"header_help_action_issues": "Report Issue",
|
||||
"header_help_action_about": "About",
|
||||
"header_settings_action_title": "Settings",
|
||||
"header_settings_action_language": "Language",
|
||||
"header_settings_action_theme_mode": "Theme Mode",
|
||||
"header_file_action_title": "File",
|
||||
"header_file_action_import_local_profile": "Import Local Profile",
|
||||
"settings_system_proxy_title": "System Settings",
|
||||
"settings_system_proxy_system_proxy_label": "System Proxy",
|
||||
"settings_system_proxy_tun_mode_label": "TUN Mode",
|
||||
@@ -16,6 +25,19 @@
|
||||
"settings_system_proxy_proxy_guard_interval_label": "System Proxy Guard Interval",
|
||||
"settings_system_proxy_proxy_bypass_label": "System Proxy Bypass",
|
||||
"settings_system_proxy_current_system_proxy_label": "Current System Proxy",
|
||||
"settings_system_proxy_service_mode_label": "Service Mode",
|
||||
"settings_system_proxy_service_mode_disabled_tooltip": "To enable service mode, make sure the Clash Nyanpasu service is installed and started",
|
||||
"settings_system_proxy_system_service_ctrl_label": "System Service",
|
||||
"settings_system_proxy_system_service_ctrl_detail": "Service Detail",
|
||||
"settings_system_proxy_system_service_ctrl_install": "Install",
|
||||
"settings_system_proxy_system_service_ctrl_uninstall": "Uninstall",
|
||||
"settings_system_proxy_system_service_ctrl_failed_install": "Install failed",
|
||||
"settings_system_proxy_system_service_ctrl_failed_uninstall": "Uninstall failed",
|
||||
"settings_system_proxy_system_service_ctrl_prompt": "Service Prompt",
|
||||
"settings_system_proxy_system_service_ctrl_manual_prompt": "Service Manual Tips",
|
||||
"settings_system_proxy_system_service_ctrl_manual_operation_prompt": "Unable to control the service automatically. Please navigate to the core directory, run PowerShell as administrator on Windows or a terminal emulator on macOS/Linux, and execute the following commands:",
|
||||
"settings_system_proxy_system_service_ctrl_start": "Start",
|
||||
"settings_system_proxy_system_service_ctrl_stop": "Stop",
|
||||
"settings_user_interface_title": "User Interface",
|
||||
"settings_user_interface_language_label": "Language",
|
||||
"settings_user_interface_theme_mode_label": "Theme Mode",
|
||||
@@ -23,6 +45,7 @@
|
||||
"settings_user_interface_theme_mode_dark": "Dark",
|
||||
"settings_user_interface_theme_mode_system": "System",
|
||||
"settings_user_interface_theme_color_label": "Theme Color",
|
||||
"settings_user_interface_theme_color_custom": "Custom",
|
||||
"settings_clash_settings_title": "Clash Settings",
|
||||
"settings_clash_settings_allow_lan_label": "Allow LAN",
|
||||
"settings_clash_settings_ipv6_label": "Enable IPv6",
|
||||
@@ -32,11 +55,26 @@
|
||||
"settings_clash_settings_random_port_label": "Random Port",
|
||||
"settings_clash_settings_random_port_enabled": "Random port enabled, after restart to take effect.",
|
||||
"settings_clash_settings_random_port_disabled": "Random port disabled, after restart to take effect.",
|
||||
"settings_label_system": "System Settings",
|
||||
"settings_label_system_description": "Proxy mode, proxy bypass, auto start, silent start and more.",
|
||||
"settings_label_user_interface": "User Interface",
|
||||
"settings_label_user_interface_description": "Language, theme mode, theme color and more.",
|
||||
"settings_label_clash_settings": "Clash Settings",
|
||||
"settings_label_clash_settings_description": "Clash configuration, log level, mixed port, random port and more.",
|
||||
"settings_label_external_controll": "Clash External Control",
|
||||
"settings_label_external_controll_description": "Web UI address, port strategy, API key and more.",
|
||||
"settings_label_nyanpasu": "Nyanpasu Settings",
|
||||
"settings_label_nyanpasu_description": "Nyanpasu specific settings",
|
||||
"settings_label_debug": "Debug Tools",
|
||||
"settings_label_debug_description": "Debug tools and more.",
|
||||
"settings_label_about": "About",
|
||||
"settings_label_about_description": "About Clash Nyanpasu",
|
||||
"profile_subscription_title": "Subscription",
|
||||
"profile_subscription_updated_at": "{updated} updated",
|
||||
"profile_subscription_next_update_at": "Next update at {next} update",
|
||||
"profile_subscription_expires_in": "{expires} expires",
|
||||
"profile_subscription_update": "Update",
|
||||
"profile_base_info_title": "Basic Info",
|
||||
"profile_name_editor_title": "Edit Name",
|
||||
"profile_name_label": "Profile Name",
|
||||
"profile_update_option_edit": "Sub. Opts",
|
||||
@@ -92,5 +130,7 @@
|
||||
"common_apply": "Apply",
|
||||
"common_reset": "Reset",
|
||||
"common_save": "Save",
|
||||
"common_validate": "Validate"
|
||||
"common_validate": "Validate",
|
||||
"common_close": "Close",
|
||||
"common_copy": "Copy"
|
||||
}
|
||||
|
||||
@@ -9,6 +9,15 @@
|
||||
"navbar_label_rules": "Правила",
|
||||
"navbar_label_settings": "Настройки",
|
||||
"navbar_label_providers": "Поставщики",
|
||||
"header_help_action_title": "Помощь",
|
||||
"header_help_action_wiki": "Онлайн документация",
|
||||
"header_help_action_issues": "Сообщить о проблеме",
|
||||
"header_help_action_about": "О программе",
|
||||
"header_settings_action_title": "Настройки",
|
||||
"header_settings_action_language": "Язык",
|
||||
"header_settings_action_theme_mode": "Режим темы",
|
||||
"header_file_action_title": "Файл",
|
||||
"header_file_action_import_local_profile": "Импорт локального профиля",
|
||||
"settings_system_proxy_title": "Системный прокси",
|
||||
"settings_system_proxy_system_proxy_label": "Системный прокси",
|
||||
"settings_system_proxy_tun_mode_label": "TUN режим",
|
||||
@@ -16,6 +25,19 @@
|
||||
"settings_system_proxy_proxy_guard_interval_label": "Интервал охраны прокси",
|
||||
"settings_system_proxy_proxy_bypass_label": "Обход прокси",
|
||||
"settings_system_proxy_current_system_proxy_label": "Текущий системный прокси",
|
||||
"settings_system_proxy_service_mode_label": "Режим службы",
|
||||
"settings_system_proxy_service_mode_disabled_tooltip": "Чтобы включить режим службы, убедитесь, что служба Clash Nyanpasu установлена и запущена",
|
||||
"settings_system_proxy_system_service_ctrl_label": "Системный сервис",
|
||||
"settings_system_proxy_system_service_ctrl_detail": "Подробности сервиса",
|
||||
"settings_system_proxy_system_service_ctrl_install": "Установить",
|
||||
"settings_system_proxy_system_service_ctrl_uninstall": "Удалить",
|
||||
"settings_system_proxy_system_service_ctrl_failed_install": "Установка не удалась",
|
||||
"settings_system_proxy_system_service_ctrl_failed_uninstall": "Удаление не удалось",
|
||||
"settings_system_proxy_system_service_ctrl_prompt": "Подсказка по сервису",
|
||||
"settings_system_proxy_system_service_ctrl_manual_prompt": "Подсказка по сервису",
|
||||
"settings_system_proxy_system_service_ctrl_manual_operation_prompt": "Не удалось автоматически управлять сервисом. Пожалуйста, перейдите в каталог ядра, запустите PowerShell как администратор на Windows или эмулятор терминала на macOS/Linux и выполните следующие команды:",
|
||||
"settings_system_proxy_system_service_ctrl_start": "Запустить",
|
||||
"settings_system_proxy_system_service_ctrl_stop": "Остановить",
|
||||
"settings_user_interface_title": "Интерфейс пользователя",
|
||||
"settings_user_interface_language_label": "Язык",
|
||||
"settings_user_interface_theme_mode_label": "Режим темы",
|
||||
@@ -23,6 +45,7 @@
|
||||
"settings_user_interface_theme_mode_dark": "Темный",
|
||||
"settings_user_interface_theme_mode_system": "Системный",
|
||||
"settings_user_interface_theme_color_label": "Цвет темы",
|
||||
"settings_user_interface_theme_color_custom": "Пользовательский",
|
||||
"settings_clash_settings_title": "Настройки Clash",
|
||||
"settings_clash_settings_allow_lan_label": "Разрешить LAN",
|
||||
"settings_clash_settings_ipv6_label": "Включить IPv6",
|
||||
@@ -32,11 +55,26 @@
|
||||
"settings_clash_settings_random_port_label": "Случайный порт",
|
||||
"settings_clash_settings_random_port_enabled": "Случайный порт включен, после перезапуска для вступления в силу.",
|
||||
"settings_clash_settings_random_port_disabled": "Случайный порт отключен, после перезапуска для вступления в силу.",
|
||||
"settings_label_system": "Системные настройки",
|
||||
"settings_label_system_description": "Режим прокси, обход прокси, автозапуск, старт без отображения окна и т.д.",
|
||||
"settings_label_user_interface": "Интерфейс пользователя",
|
||||
"settings_label_user_interface_description": "Язык, режим темы, цвет темы и т.д.",
|
||||
"settings_label_clash_settings": "Настройки Clash",
|
||||
"settings_label_clash_settings_description": "Конфигурация Clash, уровень логирования, смешанный порт, случайный порт и т.д.",
|
||||
"settings_label_external_controll": "Внешнее управление Clash",
|
||||
"settings_label_external_controll_description": "Адрес Web UI, стратегия порта, API ключ и т.д.",
|
||||
"settings_label_nyanpasu": "Настройки Nyanpasu",
|
||||
"settings_label_nyanpasu_description": "Специфические настройки Nyanpasu",
|
||||
"settings_label_debug": "Отладочные инструменты",
|
||||
"settings_label_debug_description": "Отладочные инструменты и т.д.",
|
||||
"settings_label_about": "О программе",
|
||||
"settings_label_about_description": "О Clash Nyanpasu",
|
||||
"profile_subscription_title": "Информация о подписке",
|
||||
"profile_subscription_updated_at": "{updated} обновлено",
|
||||
"profile_subscription_next_update_at": "Следующее обновление через {next} обновление",
|
||||
"profile_subscription_expires_in": "{expires} истекает",
|
||||
"profile_subscription_update": "Обновить",
|
||||
"profile_base_info_title": "Основная информация",
|
||||
"profile_name_editor_title": "Редактировать название",
|
||||
"profile_name_label": "Название профиля",
|
||||
"profile_update_option_edit": "Опции подписки",
|
||||
@@ -92,5 +130,7 @@
|
||||
"common_apply": "Применить",
|
||||
"common_reset": "Сбросить",
|
||||
"common_save": "Сохранить",
|
||||
"common_validate": "Проверить"
|
||||
"common_validate": "Проверить",
|
||||
"common_close": "Закрыть",
|
||||
"common_copy": "Копировать"
|
||||
}
|
||||
|
||||
@@ -9,6 +9,15 @@
|
||||
"navbar_label_rules": "规则",
|
||||
"navbar_label_settings": "设置",
|
||||
"navbar_label_providers": "资源",
|
||||
"header_help_action_title": "帮助",
|
||||
"header_help_action_wiki": "在线文档",
|
||||
"header_help_action_issues": "报告问题",
|
||||
"header_help_action_about": "关于",
|
||||
"header_settings_action_title": "设置",
|
||||
"header_settings_action_language": "语言",
|
||||
"header_settings_action_theme_mode": "主题模式",
|
||||
"header_file_action_title": "文件",
|
||||
"header_file_action_import_local_profile": "导入本地配置",
|
||||
"settings_system_proxy_title": "系统设置",
|
||||
"settings_system_proxy_system_proxy_label": "系统代理",
|
||||
"settings_system_proxy_tun_mode_label": "TUN 模式",
|
||||
@@ -16,6 +25,19 @@
|
||||
"settings_system_proxy_proxy_guard_interval_label": "系统代理守卫间隔",
|
||||
"settings_system_proxy_proxy_bypass_label": "系统代理绕过",
|
||||
"settings_system_proxy_current_system_proxy_label": "当前系统代理",
|
||||
"settings_system_proxy_service_mode_label": "服务模式",
|
||||
"settings_system_proxy_service_mode_disabled_tooltip": "要启用服务模式,请确保 Clash Nyanpasu 服务已安装并启动",
|
||||
"settings_system_proxy_system_service_ctrl_label": "系统服务",
|
||||
"settings_system_proxy_system_service_ctrl_detail": "服务详情",
|
||||
"settings_system_proxy_system_service_ctrl_install": "安装",
|
||||
"settings_system_proxy_system_service_ctrl_uninstall": "卸载",
|
||||
"settings_system_proxy_system_service_ctrl_failed_install": "安装失败",
|
||||
"settings_system_proxy_system_service_ctrl_failed_uninstall": "卸载失败",
|
||||
"settings_system_proxy_system_service_ctrl_prompt": "服务提示",
|
||||
"settings_system_proxy_system_service_ctrl_manual_prompt": "手动操作服务提示",
|
||||
"settings_system_proxy_system_service_ctrl_manual_operation_prompt": "无法自动操作服务。请导航到内核所在目录,在 Windows 上以管理员身份打开 PowerShell 或在 macOS/Linux 上打开终端仿真器,然后执行以下命令:",
|
||||
"settings_system_proxy_system_service_ctrl_start": "启动",
|
||||
"settings_system_proxy_system_service_ctrl_stop": "停止",
|
||||
"settings_user_interface_title": "用户界面",
|
||||
"settings_user_interface_language_label": "语言",
|
||||
"settings_user_interface_theme_mode_label": "主题模式",
|
||||
@@ -23,6 +45,7 @@
|
||||
"settings_user_interface_theme_mode_dark": "深色",
|
||||
"settings_user_interface_theme_mode_system": "跟随系统",
|
||||
"settings_user_interface_theme_color_label": "主题颜色",
|
||||
"settings_user_interface_theme_color_custom": "自定义",
|
||||
"settings_clash_settings_title": "Clash 设置",
|
||||
"settings_clash_settings_allow_lan_label": "允许局域网连接",
|
||||
"settings_clash_settings_ipv6_label": "启用 IPv6",
|
||||
@@ -32,6 +55,20 @@
|
||||
"settings_clash_settings_random_port_label": "随机端口",
|
||||
"settings_clash_settings_random_port_enabled": "随机端口已启用,重启后生效。",
|
||||
"settings_clash_settings_random_port_disabled": "随机端口已禁用,重启后生效。",
|
||||
"settings_label_system": "系统设置",
|
||||
"settings_label_system_description": "代理模式、代理绕过、开机自启、静默启动等设置",
|
||||
"settings_label_user_interface": "用户界面",
|
||||
"settings_label_user_interface_description": "语言、主题模式、主题颜色等设置",
|
||||
"settings_label_clash_settings": "Clash 设置",
|
||||
"settings_label_clash_settings_description": "Clash 配置、日志级别、混合端口、随机端口等设置",
|
||||
"settings_label_external_controll": "Web UI 与外部控制",
|
||||
"settings_label_external_controll_description": "Web UI 地址、端口策略、API 密钥等设置",
|
||||
"settings_label_nyanpasu": "Nyanpasu 配置",
|
||||
"settings_label_nyanpasu_description": "Nyanpasu 特性配置",
|
||||
"settings_label_debug": "调试工具",
|
||||
"settings_label_debug_description": "调试工具配置",
|
||||
"settings_label_about": "关于",
|
||||
"settings_label_about_description": "关于 Clash Nyanpasu",
|
||||
"profile_subscription_title": "订阅信息",
|
||||
"profile_subscription_updated_at": "{updated}更新",
|
||||
"profile_subscription_next_update_at": "下次更新于 {next} 更新",
|
||||
@@ -93,5 +130,7 @@
|
||||
"common_apply": "应用",
|
||||
"common_reset": "重置",
|
||||
"common_save": "保存",
|
||||
"common_validate": "验证"
|
||||
"common_validate": "验证",
|
||||
"common_close": "关闭",
|
||||
"common_copy": "复制"
|
||||
}
|
||||
|
||||
@@ -9,6 +9,15 @@
|
||||
"navbar_label_rules": "規則",
|
||||
"navbar_label_settings": "設置",
|
||||
"navbar_label_providers": "資源",
|
||||
"header_help_action_title": "幫助",
|
||||
"header_help_action_wiki": "線上文檔",
|
||||
"header_help_action_issues": "報告問題",
|
||||
"header_help_action_about": "關於",
|
||||
"header_settings_action_title": "設置",
|
||||
"header_settings_action_language": "語言",
|
||||
"header_settings_action_theme_mode": "主題模式",
|
||||
"header_file_action_title": "文件",
|
||||
"header_file_action_import_local_profile": "導入本地配置",
|
||||
"settings_system_proxy_title": "系統設置",
|
||||
"settings_system_proxy_system_proxy_label": "系統代理",
|
||||
"settings_system_proxy_tun_mode_label": "TUN 模式",
|
||||
@@ -16,6 +25,19 @@
|
||||
"settings_system_proxy_proxy_guard_interval_label": "系統代理守衛間隔",
|
||||
"settings_system_proxy_proxy_bypass_label": "系統代理繞過",
|
||||
"settings_system_proxy_current_system_proxy_label": "當前系統代理",
|
||||
"settings_system_proxy_service_mode_label": "服務模式",
|
||||
"settings_system_proxy_service_mode_disabled_tooltip": "要啟用服務模式,請確保 Clash Nyanpasu 服務已安裝並啟動",
|
||||
"settings_system_proxy_system_service_ctrl_label": "系統服務",
|
||||
"settings_system_proxy_system_service_ctrl_detail": "服務詳情",
|
||||
"settings_system_proxy_system_service_ctrl_install": "安裝",
|
||||
"settings_system_proxy_system_service_ctrl_uninstall": "卸載",
|
||||
"settings_system_proxy_system_service_ctrl_failed_install": "安裝失敗",
|
||||
"settings_system_proxy_system_service_ctrl_failed_uninstall": "卸載失敗",
|
||||
"settings_system_proxy_system_service_ctrl_prompt": "服務提示",
|
||||
"settings_system_proxy_system_service_ctrl_manual_prompt": "手動操作服務提示",
|
||||
"settings_system_proxy_system_service_ctrl_manual_operation_prompt": "無法自動操作服務。請開啟核心所在目錄,在 Windows 上以管理員身分開啟 PowerShell 或在 macOS/Linux 上開啟終端模擬器,然後執行以下指令:",
|
||||
"settings_system_proxy_system_service_ctrl_start": "啟動",
|
||||
"settings_system_proxy_system_service_ctrl_stop": "停止",
|
||||
"settings_user_interface_title": "使用者介面",
|
||||
"settings_user_interface_language_label": "語言",
|
||||
"settings_user_interface_theme_mode_label": "主題模式",
|
||||
@@ -23,6 +45,7 @@
|
||||
"settings_user_interface_theme_mode_dark": "深色",
|
||||
"settings_user_interface_theme_mode_system": "跟隨系統",
|
||||
"settings_user_interface_theme_color_label": "主題顏色",
|
||||
"settings_user_interface_theme_color_custom": "自定義",
|
||||
"settings_clash_settings_title": "Clash 設置",
|
||||
"settings_clash_settings_allow_lan_label": "允許區域網路連線",
|
||||
"settings_clash_settings_ipv6_label": "啟用 IPv6",
|
||||
@@ -32,11 +55,26 @@
|
||||
"settings_clash_settings_random_port_label": "隨機端口",
|
||||
"settings_clash_settings_random_port_enabled": "隨機端口已啟用,重啟後生效。",
|
||||
"settings_clash_settings_random_port_disabled": "隨機端口已禁用,重啟後生效。",
|
||||
"settings_label_system": "系統設置",
|
||||
"settings_label_system_description": "代理模式、代理繞過、開機自啟、靜默啟動等設定",
|
||||
"settings_label_user_interface": "使用者介面",
|
||||
"settings_label_user_interface_description": "語言、主題模式、主題顏色等設定",
|
||||
"settings_label_clash_settings": "Clash 設置",
|
||||
"settings_label_clash_settings_description": "Clash 配置、日誌級別、混合端口、隨機端口等設定",
|
||||
"settings_label_external_controll": "Web UI 與外部控制",
|
||||
"settings_label_external_controll_description": "Web UI 位址、端口策略、API 密鑰等設定",
|
||||
"settings_label_nyanpasu": "Nyanpasu 設置",
|
||||
"settings_label_nyanpasu_description": "Nyanpasu 特性設定",
|
||||
"settings_label_debug": "調試工具",
|
||||
"settings_label_debug_description": "調試工具設定",
|
||||
"settings_label_about": "關於",
|
||||
"settings_label_about_description": "關於 Clash Nyanpasu",
|
||||
"profile_subscription_title": "訂閱信息",
|
||||
"profile_subscription_updated_at": "{updated}更新",
|
||||
"profile_subscription_next_update_at": "下次更新於 {next} 更新",
|
||||
"profile_subscription_expires_in": "{expires}到期",
|
||||
"profile_subscription_update": "更新",
|
||||
"profile_base_info_title": "基本資訊",
|
||||
"profile_name_editor_title": "編輯名稱",
|
||||
"profile_name_label": "配置名稱",
|
||||
"profile_update_option_edit": "訂閱選項",
|
||||
@@ -92,5 +130,7 @@
|
||||
"common_apply": "應用",
|
||||
"common_reset": "重置",
|
||||
"common_save": "保存",
|
||||
"common_validate": "驗證"
|
||||
"common_validate": "驗證",
|
||||
"common_close": "關閉",
|
||||
"common_copy": "複製"
|
||||
}
|
||||
|
||||
@@ -47,9 +47,9 @@
|
||||
"country-code-emoji": "2.3.0",
|
||||
"country-emoji": "1.5.6",
|
||||
"dayjs": "1.11.19",
|
||||
"framer-motion": "12.31.0",
|
||||
"framer-motion": "12.33.0",
|
||||
"i18next": "25.7.3",
|
||||
"jotai": "2.16.1",
|
||||
"jotai": "2.17.1",
|
||||
"json-schema": "0.4.0",
|
||||
"material-react-table": "3.2.1",
|
||||
"monaco-editor": "0.54.0",
|
||||
@@ -58,7 +58,7 @@
|
||||
"react-dom": "19.2.4",
|
||||
"react-error-boundary": "6.0.0",
|
||||
"react-fast-marquee": "1.6.5",
|
||||
"react-hook-form": "7.69.0",
|
||||
"react-hook-form": "7.71.1",
|
||||
"react-hook-form-mui": "8.2.0",
|
||||
"react-i18next": "15.7.4",
|
||||
"react-markdown": "10.1.0",
|
||||
@@ -73,7 +73,7 @@
|
||||
"@csstools/normalize.css": "12.1.1",
|
||||
"@emotion/babel-plugin": "11.13.5",
|
||||
"@emotion/react": "11.14.0",
|
||||
"@iconify/json": "2.2.435",
|
||||
"@iconify/json": "2.2.436",
|
||||
"@monaco-editor/react": "4.7.0",
|
||||
"@tanstack/react-query": "5.90.20",
|
||||
"@tanstack/react-router": "1.158.1",
|
||||
@@ -87,7 +87,7 @@
|
||||
"@tauri-apps/plugin-process": "2.3.0",
|
||||
"@tauri-apps/plugin-shell": "2.3.1",
|
||||
"@tauri-apps/plugin-updater": "2.9.0",
|
||||
"@types/react": "19.2.11",
|
||||
"@types/react": "19.2.13",
|
||||
"@types/react-dom": "19.2.3",
|
||||
"@types/validator": "13.15.10",
|
||||
"@vitejs/plugin-legacy": "7.2.1",
|
||||
|
||||
@@ -54,8 +54,8 @@ export const AppContainer = ({
|
||||
square
|
||||
elevation={0}
|
||||
className={styles.layout}
|
||||
onPointerDown={(e: any) => {
|
||||
if (e.target?.dataset?.windrag) {
|
||||
onPointerDown={(e) => {
|
||||
if ((e.target as HTMLElement)?.dataset?.windrag) {
|
||||
appWindow.startDragging()
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -5,7 +5,7 @@ import { SvgIconComponent } from '@mui/icons-material'
|
||||
import { Box, ListItemButton, ListItemIcon, Tooltip } from '@mui/material'
|
||||
import { useSetting } from '@nyanpasu/interface'
|
||||
import { alpha, cn } from '@nyanpasu/ui'
|
||||
import { useLocation, useMatch, useNavigate } from '@tanstack/react-router'
|
||||
import { useLocation, useNavigate } from '@tanstack/react-router'
|
||||
|
||||
export const RouteListItem = ({
|
||||
name,
|
||||
|
||||
@@ -18,6 +18,8 @@ const NoticeInner = (props: InnerProps) => {
|
||||
setVisible(false)
|
||||
onClose()
|
||||
}
|
||||
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
const onAutoClose = (_e: any, reason: string) => {
|
||||
if (reason !== 'clickaway') onBtnClose()
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ const MAX_WIDTH = 'calc(100% - 48px - 16px)'
|
||||
export const IPASNPanel = ({ refreshCount }: { refreshCount: number }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const { data, mutate, isValidating, isLoading } = useIPSB()
|
||||
const { data, mutate, isValidating } = useIPSB()
|
||||
|
||||
const handleRefreshIP = () => {
|
||||
mutate()
|
||||
|
||||
@@ -75,10 +75,13 @@ export const ProxyShortcuts = () => {
|
||||
try {
|
||||
await systemProxy.upsert(!systemProxy.value)
|
||||
} catch (error) {
|
||||
message(`Activation System Proxy failed!`, {
|
||||
title: t('Error'),
|
||||
kind: 'error',
|
||||
})
|
||||
message(
|
||||
`Activation System Proxy failed!\n Error: ${formatError(error)}`,
|
||||
{
|
||||
title: t('Error'),
|
||||
kind: 'error',
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ export const LayoutControl = ({ className }: { className?: string }) => {
|
||||
|
||||
<CtrlButton
|
||||
onClick={() => {
|
||||
appWindow.toggleMaximize().then((isMaximized) => {
|
||||
appWindow.toggleMaximize().then(() => {
|
||||
queryClient.invalidateQueries({ queryKey: ['isMaximized'] })
|
||||
})
|
||||
}}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// oxlint-disable no-unsafe-optional-chaining
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { notification, NotificationType } from '@/utils/notification'
|
||||
|
||||
@@ -63,7 +63,7 @@ const ThemeInner = ({ children }: PropsWithChildren) => {
|
||||
|
||||
const setThemeMode = useSetAtom(themeModeAtom)
|
||||
|
||||
const { mode, setMode } = useColorScheme()
|
||||
const { setMode } = useColorScheme()
|
||||
|
||||
useEffect(() => {
|
||||
if (themeMode === 'system') {
|
||||
|
||||
@@ -23,8 +23,6 @@ import { formatError } from '@/utils'
|
||||
import { message } from '@/utils/notification'
|
||||
import { Divider, InputAdornment } from '@mui/material'
|
||||
import {
|
||||
LocalProfile,
|
||||
ProfileBuilder,
|
||||
ProfileQueryResultItem,
|
||||
ProfileTemplate,
|
||||
RemoteProfile,
|
||||
|
||||
@@ -254,10 +254,15 @@ export const ProfileItem = memo(function ProfileItem({
|
||||
className="flex h-6 w-30 items-center"
|
||||
nodes={[
|
||||
!!item.updated && (
|
||||
<TimeSpan ts={item.updated!} k="Subscription Updated At" />
|
||||
<TimeSpan
|
||||
key="updated"
|
||||
ts={item.updated!}
|
||||
k="Subscription Updated At"
|
||||
/>
|
||||
),
|
||||
!!(item as RemoteProfile).extra?.expire && (
|
||||
<TimeSpan
|
||||
key="expire"
|
||||
ts={(item as RemoteProfile).extra!.expire!}
|
||||
k="Subscription Expires In"
|
||||
/>
|
||||
|
||||
@@ -62,7 +62,7 @@ export const beforeEditorMount = (monaco: Monaco) => {
|
||||
// Register link provider for all supported languages
|
||||
const registerLinkProvider = (language: string) => {
|
||||
monaco.languages.registerLinkProvider(language, {
|
||||
provideLinks: (model, token) => {
|
||||
provideLinks: (model) => {
|
||||
const links = []
|
||||
// More robust URL regex pattern
|
||||
const urlRegex = /\b(?:https?:\/\/|www\.)[^\s<>"']*[^<>\s"',.!?]/gi
|
||||
@@ -131,7 +131,7 @@ export default function ProfileMonacoViewer({
|
||||
)
|
||||
|
||||
const handleEditorDidMount = useCallback(
|
||||
(editor: editor.IStandaloneCodeEditor, monaco: Monaco) => {
|
||||
(editor: editor.IStandaloneCodeEditor) => {
|
||||
editorRef.current = editor
|
||||
|
||||
// Enable URL detection and handling
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { isEqual } from 'lodash-es'
|
||||
import type { Profile, ProfileBuilder } from '@nyanpasu/interface'
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,7 +27,7 @@ export const UpdateProviders = () => {
|
||||
setLoading(true)
|
||||
|
||||
await Promise.all(
|
||||
Object.entries(rulesProvider.data).map(([name, provider]) => {
|
||||
Object.values(rulesProvider.data).map((provider) => {
|
||||
return provider.mutate()
|
||||
}),
|
||||
)
|
||||
|
||||
@@ -164,7 +164,7 @@ export const NodeList = forwardRef(function NodeList(
|
||||
)
|
||||
|
||||
return disableMotion ? (
|
||||
<div className="relative overflow-hidden">
|
||||
<div key={render.name} className="relative overflow-hidden">
|
||||
<Card />
|
||||
</div>
|
||||
) : (
|
||||
|
||||
+1
-1
@@ -8,7 +8,7 @@ import { serviceManualPromptDialogAtom } from '@/store/service'
|
||||
import { notification } from '@/utils/notification'
|
||||
import { getShikiSingleton } from '@/utils/shiki'
|
||||
import ContentPasteIcon from '@mui/icons-material/ContentPaste'
|
||||
import { IconButton, Tooltip, useTheme } from '@mui/material'
|
||||
import { IconButton, Tooltip } from '@mui/material'
|
||||
import { useColorScheme } from '@mui/material/styles'
|
||||
import { getCoreDir, getServiceInstallPrompt } from '@nyanpasu/interface'
|
||||
import { BaseDialog, BaseDialogProps, cn } from '@nyanpasu/ui'
|
||||
|
||||
@@ -91,10 +91,8 @@ export const SettingClashField = () => {
|
||||
|
||||
const mergeFields = useMemo(
|
||||
() => [
|
||||
...[
|
||||
...Object.keys(CLASH_FIELD.default),
|
||||
...Object.keys(CLASH_FIELD.handle),
|
||||
],
|
||||
...Object.keys(CLASH_FIELD.default),
|
||||
...Object.keys(CLASH_FIELD.handle),
|
||||
...(query.data?.valid ?? []),
|
||||
],
|
||||
[query.data],
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
// oxlint-disable typescript/no-explicit-any
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSetting } from '@nyanpasu/interface'
|
||||
import { SwitchItem } from '@nyanpasu/ui'
|
||||
|
||||
@@ -50,10 +50,13 @@ const SystemProxyButton = () => {
|
||||
try {
|
||||
await systemProxy.upsert(!systemProxy.value)
|
||||
} catch (error) {
|
||||
message(`Activation System Proxy failed!`, {
|
||||
title: t('Error'),
|
||||
kind: 'error',
|
||||
})
|
||||
message(
|
||||
`Activation System Proxy failed!\n Error: ${formatError(error)}`,
|
||||
{
|
||||
title: t('Error'),
|
||||
kind: 'error',
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -75,7 +78,7 @@ const ProxyGuardSwitch = () => {
|
||||
try {
|
||||
await proxyGuard.upsert(!proxyGuard.value)
|
||||
} catch (error) {
|
||||
message(`Activation Proxy Guard failed!`, {
|
||||
message(`Activation Proxy Guard failed!\n Error: ${formatError(error)}`, {
|
||||
title: t('Error'),
|
||||
kind: 'error',
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import ArrowRight from '~icons/material-symbols/arrow-right-rounded'
|
||||
import Check from '~icons/material-symbols/check-rounded'
|
||||
import RadioChecked from '~icons/material-symbols/radio-button-checked'
|
||||
import Radio from '~icons/material-symbols/radio-button-unchecked'
|
||||
@@ -7,6 +8,50 @@ import { cn } from '@nyanpasu/ui'
|
||||
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
|
||||
import { useControllableState } from '@radix-ui/react-use-controllable-state'
|
||||
|
||||
const MotionContent = ({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: ComponentProps<typeof motion.div>) => {
|
||||
return (
|
||||
<motion.div
|
||||
className={cn(
|
||||
'relative z-50 w-full overflow-auto rounded',
|
||||
'dark:text-on-surface',
|
||||
'bg-inverse-on-surface dark:bg-surface',
|
||||
'shadow shadow-zinc-300 dark:shadow-zinc-900',
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
maxHeight: 'var(--radix-popper-available-height)',
|
||||
}}
|
||||
initial={{
|
||||
opacity: 0,
|
||||
scaleY: 0.9,
|
||||
transformOrigin: 'top',
|
||||
}}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
scaleY: 1,
|
||||
transformOrigin: 'top',
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
scaleY: 0.9,
|
||||
transformOrigin: 'top',
|
||||
}}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
bounce: 0,
|
||||
duration: 0.35,
|
||||
}}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
const DropdownMenuContext = createContext<{
|
||||
open: boolean
|
||||
} | null>(null)
|
||||
@@ -52,7 +97,92 @@ export const DropdownMenuGroup = DropdownMenuPrimitive.Group
|
||||
|
||||
export const DropdownMenuPortal = DropdownMenuPrimitive.Portal
|
||||
|
||||
export const DropdownMenuSub = DropdownMenuPrimitive.Sub
|
||||
const DropdownMenuSubContext = createContext<{
|
||||
open: boolean
|
||||
} | null>(null)
|
||||
|
||||
const useDropdownMenuSubContext = () => {
|
||||
const context = useContext(DropdownMenuSubContext)
|
||||
|
||||
if (context === null) {
|
||||
throw new Error(
|
||||
'DropdownMenuSub compound components cannot be rendered outside the DropdownMenuSub component',
|
||||
)
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
export const DropdownMenuSub = ({
|
||||
open: inputOpen,
|
||||
defaultOpen,
|
||||
onOpenChange,
|
||||
children,
|
||||
...props
|
||||
}: ComponentProps<typeof DropdownMenuPrimitive.Sub>) => {
|
||||
const [open, setOpen] = useControllableState({
|
||||
prop: inputOpen,
|
||||
defaultProp: defaultOpen ?? false,
|
||||
onChange: onOpenChange,
|
||||
})
|
||||
|
||||
return (
|
||||
<DropdownMenuSubContext.Provider
|
||||
value={{
|
||||
open,
|
||||
}}
|
||||
>
|
||||
<DropdownMenuPrimitive.Sub {...props} open={open} onOpenChange={setOpen}>
|
||||
{children}
|
||||
</DropdownMenuPrimitive.Sub>
|
||||
</DropdownMenuSubContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export function DropdownMenuSubTrigger({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: ComponentProps<typeof DropdownMenuPrimitive.SubTrigger>) {
|
||||
return (
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
className={cn(
|
||||
'flex h-12 cursor-default items-center justify-between gap-2 p-4 pr-2 outline-hidden',
|
||||
'cursor-pointer',
|
||||
'hover:bg-surface-variant',
|
||||
'dark:hover:bg-surface-variant',
|
||||
'data-[state=open]:bg-surface-variant/30',
|
||||
'dark:data-[state=open]:bg-surface-variant/30',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
|
||||
<ArrowRight className="text-outline-variant dark:text-outline size-6" />
|
||||
</DropdownMenuPrimitive.SubTrigger>
|
||||
)
|
||||
}
|
||||
|
||||
export function DropdownMenuSubContent({
|
||||
children,
|
||||
className,
|
||||
...props
|
||||
}: ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
|
||||
const { open } = useDropdownMenuSubContext()
|
||||
|
||||
return (
|
||||
<AnimatePresence initial={false}>
|
||||
{open && (
|
||||
<DropdownMenuPortal forceMount>
|
||||
<DropdownMenuPrimitive.SubContent {...props} asChild>
|
||||
<MotionContent className={className}>{children}</MotionContent>
|
||||
</DropdownMenuPrimitive.SubContent>
|
||||
</DropdownMenuPortal>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
)
|
||||
}
|
||||
|
||||
const DropdownMenuRadioGroupContext = createContext<{
|
||||
value: string | null
|
||||
@@ -93,10 +223,6 @@ export const DropdownMenuRadioGroup = ({
|
||||
)
|
||||
}
|
||||
|
||||
export const DropdownMenuSubTrigger = DropdownMenuPrimitive.SubTrigger
|
||||
|
||||
export const DropdownMenuSubContent = DropdownMenuPrimitive.SubContent
|
||||
|
||||
export const DropdownMenuContent = ({
|
||||
children,
|
||||
className,
|
||||
@@ -109,39 +235,7 @@ export const DropdownMenuContent = ({
|
||||
{open && (
|
||||
<DropdownMenuPrimitive.Portal forceMount>
|
||||
<DropdownMenuPrimitive.Content {...props} asChild>
|
||||
<motion.div
|
||||
className={cn(
|
||||
'shadow-container relative z-50 w-full overflow-auto rounded',
|
||||
'dark:text-on-surface',
|
||||
'bg-inverse-on-surface dark:bg-surface',
|
||||
className,
|
||||
)}
|
||||
style={{
|
||||
maxHeight: 'var(--radix-popper-available-height)',
|
||||
}}
|
||||
initial={{
|
||||
opacity: 0,
|
||||
scaleY: 0.9,
|
||||
transformOrigin: 'top',
|
||||
}}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
scaleY: 1,
|
||||
transformOrigin: 'top',
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
scaleY: 0.9,
|
||||
transformOrigin: 'top',
|
||||
}}
|
||||
transition={{
|
||||
type: 'spring',
|
||||
bounce: 0,
|
||||
duration: 0.35,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
<MotionContent className={className}>{children}</MotionContent>
|
||||
</DropdownMenuPrimitive.Content>
|
||||
</DropdownMenuPrimitive.Portal>
|
||||
)}
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
ComponentProps,
|
||||
createContext,
|
||||
DragEvent,
|
||||
ReactNode,
|
||||
RefObject,
|
||||
useContext,
|
||||
useEffect,
|
||||
@@ -103,7 +102,6 @@ export interface FileDropZoneProps
|
||||
onFileRead?: (content: string) => void
|
||||
accept: string[]
|
||||
disabled?: boolean
|
||||
fileSelected?: (fileName: string) => ReactNode
|
||||
}
|
||||
|
||||
export function FileDropZone({
|
||||
@@ -114,7 +112,6 @@ export function FileDropZone({
|
||||
className,
|
||||
disabled = false,
|
||||
variant,
|
||||
fileSelected,
|
||||
children,
|
||||
...props
|
||||
}: FileDropZoneProps) {
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
useState,
|
||||
} from 'react'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import { Slot } from '@radix-ui/react-slot'
|
||||
|
||||
export const inputContainerVariants = cva(
|
||||
[
|
||||
@@ -402,6 +401,7 @@ export const InputLabel = ({
|
||||
variant,
|
||||
focus: haveValue,
|
||||
}),
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
@@ -420,7 +420,6 @@ export type NumericInputProps = Omit<
|
||||
label?: string
|
||||
min?: number
|
||||
max?: number
|
||||
step?: number
|
||||
decimalScale?: number
|
||||
allowNegative?: boolean
|
||||
} & InputContainerVariants
|
||||
@@ -434,7 +433,6 @@ export const NumericInput = ({
|
||||
defaultValue,
|
||||
min,
|
||||
max,
|
||||
step = 1,
|
||||
decimalScale,
|
||||
allowNegative = true,
|
||||
...props
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
import {
|
||||
ComponentProps,
|
||||
createContext,
|
||||
PropsWithChildren,
|
||||
useContext,
|
||||
} from 'react'
|
||||
import { ComponentProps, createContext, useContext } from 'react'
|
||||
import useIsMobile from '@/hooks/use-is-moblie'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import { AppContentScrollArea } from './scroll-area'
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
import { motion, useAnimationControls } from 'framer-motion'
|
||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||
import { sleep } from '@/utils'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
|
||||
export default function TextMarquee({
|
||||
children,
|
||||
className,
|
||||
speed = 30,
|
||||
gap = 32,
|
||||
pauseDuration = 1,
|
||||
// pauseOnHover = true,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
className?: string
|
||||
speed?: number
|
||||
gap?: number
|
||||
pauseDuration?: number
|
||||
// pauseOnHover?: boolean
|
||||
}) {
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const textRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const [shouldAnimate, setShouldAnimate] = useState(false)
|
||||
|
||||
const [textWidth, setTextWidth] = useState(0)
|
||||
|
||||
const controls = useAnimationControls()
|
||||
|
||||
const isHoveredRef = useRef(false)
|
||||
|
||||
// Check if text overflows container
|
||||
const checkOverflow = useCallback(() => {
|
||||
if (!containerRef.current || !textRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
const container = containerRef.current
|
||||
const text = textRef.current
|
||||
|
||||
const containerW = container.offsetWidth
|
||||
const textW = text.scrollWidth
|
||||
|
||||
setTextWidth(textW)
|
||||
setShouldAnimate(textW > containerW)
|
||||
}, [])
|
||||
|
||||
// Observe container size changes
|
||||
useEffect(() => {
|
||||
checkOverflow()
|
||||
|
||||
const resizeObserver = new ResizeObserver(() => {
|
||||
checkOverflow()
|
||||
})
|
||||
|
||||
if (containerRef.current) {
|
||||
resizeObserver.observe(containerRef.current)
|
||||
}
|
||||
|
||||
return () => {
|
||||
resizeObserver.disconnect()
|
||||
}
|
||||
}, [checkOverflow, children])
|
||||
|
||||
// Animate when shouldAnimate changes
|
||||
useEffect(() => {
|
||||
if (!shouldAnimate) {
|
||||
controls.set({ x: 0 })
|
||||
return
|
||||
}
|
||||
|
||||
const totalDistance = textWidth + gap
|
||||
const animDuration = totalDistance / speed
|
||||
|
||||
const cancelledRef = { current: false }
|
||||
|
||||
const runAnimationLoop = async () => {
|
||||
// Wait at start position
|
||||
await sleep(pauseDuration * 1000)
|
||||
|
||||
if (cancelledRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if hovered, wait and retry
|
||||
if (isHoveredRef.current) {
|
||||
await sleep(100)
|
||||
|
||||
if (!cancelledRef.current) {
|
||||
runAnimationLoop()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Animate to end
|
||||
await controls.start({
|
||||
x: -totalDistance,
|
||||
transition: {
|
||||
duration: animDuration,
|
||||
ease: 'linear',
|
||||
},
|
||||
})
|
||||
|
||||
if (cancelledRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
// Reset to start position instantly and loop
|
||||
controls.set({ x: 0 })
|
||||
|
||||
if (!cancelledRef.current) {
|
||||
runAnimationLoop()
|
||||
}
|
||||
}
|
||||
|
||||
runAnimationLoop()
|
||||
|
||||
return () => {
|
||||
cancelledRef.current = true
|
||||
controls.stop()
|
||||
}
|
||||
}, [shouldAnimate, textWidth, gap, speed, pauseDuration, controls])
|
||||
|
||||
// const handleMouseEnter = () => {
|
||||
// if (!pauseOnHover) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// isHoveredRef.current = true
|
||||
// controls.stop()
|
||||
// }
|
||||
|
||||
// const handleMouseLeave = () => {
|
||||
// if (!pauseOnHover || !shouldAnimate) {
|
||||
// return
|
||||
// }
|
||||
|
||||
// isHoveredRef.current = false
|
||||
|
||||
// resumeAnimation()
|
||||
// }
|
||||
|
||||
// const resumeAnimation = () => {
|
||||
// const totalDistance = textWidth + gap
|
||||
|
||||
// // Resume animation
|
||||
// const marqueeContent = containerRef.current?.querySelector<HTMLDivElement>(
|
||||
// '[data-marquee-content]',
|
||||
// )
|
||||
|
||||
// if (marqueeContent) {
|
||||
// const transform = window.getComputedStyle(marqueeContent).transform
|
||||
// const matrix = new DOMMatrix(transform)
|
||||
// const currentPosition = matrix.m41
|
||||
|
||||
// const remainingDistance = -totalDistance - currentPosition
|
||||
// const remainingDuration = Math.abs(remainingDistance) / speed
|
||||
|
||||
// controls.start({
|
||||
// x: -totalDistance,
|
||||
// transition: {
|
||||
// duration: remainingDuration,
|
||||
// ease: 'linear',
|
||||
// },
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className={cn('overflow-hidden', className)}
|
||||
data-slot="text-marquee"
|
||||
>
|
||||
{shouldAnimate ? (
|
||||
<motion.div
|
||||
className="flex whitespace-nowrap"
|
||||
animate={controls}
|
||||
data-slot="text-marquee-content"
|
||||
>
|
||||
<span
|
||||
ref={textRef}
|
||||
data-slot="text-marquee-content-item"
|
||||
data-index="0"
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
|
||||
<span
|
||||
style={{
|
||||
paddingLeft: gap,
|
||||
}}
|
||||
data-slot="text-marquee-content-item"
|
||||
data-index="1"
|
||||
>
|
||||
{children}
|
||||
</span>
|
||||
</motion.div>
|
||||
) : (
|
||||
<div
|
||||
ref={textRef}
|
||||
className="truncate"
|
||||
data-slot="text-marquee-content"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -3,7 +3,7 @@ import { isAppImage } from '@nyanpasu/interface'
|
||||
|
||||
export const useIsAppImage = (config?: Partial<SWRConfiguration>) => {
|
||||
return useSWR<boolean>('/api/is_appimage', isAppImage, {
|
||||
...(config || {}),
|
||||
...config,
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
refreshInterval: 0,
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import ClashRs from '@/assets/image/core/clash-rs.png'
|
||||
import ClashMeta from '@/assets/image/core/clash.meta.png'
|
||||
import Clash from '@/assets/image/core/clash.png'
|
||||
import { useSetting } from '@nyanpasu/interface'
|
||||
|
||||
export default function useCurrentCoreIcon() {
|
||||
const { value: currentCore } = useSetting('clash_core')
|
||||
|
||||
switch (currentCore) {
|
||||
case 'clash':
|
||||
return Clash
|
||||
case 'clash-rs':
|
||||
case 'clash-rs-alpha':
|
||||
return ClashRs
|
||||
case 'mihomo':
|
||||
case 'mihomo-alpha':
|
||||
return ClashMeta
|
||||
// sync from backend
|
||||
default:
|
||||
return ClashMeta
|
||||
}
|
||||
}
|
||||
@@ -57,7 +57,7 @@ export const beforeEditorMount = (monaco: Monaco) => {
|
||||
// Register link provider for all supported languages
|
||||
const registerLinkProvider = (language: string) => {
|
||||
monaco.languages.registerLinkProvider(language, {
|
||||
provideLinks: (model, token) => {
|
||||
provideLinks: (model) => {
|
||||
const links = []
|
||||
// More robust URL regex pattern
|
||||
const urlRegex = /\b(?:https?:\/\/|www\.)[^\s<>"']*[^<>\s"',.!?]/gi
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useExperimentalThemeContext } from '@/components/providers/theme-provid
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { useLockFn } from '@/hooks/use-lock-fn'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import MonacoEditor, { Monaco } from '@monaco-editor/react'
|
||||
import MonacoEditor from '@monaco-editor/react'
|
||||
import { openThat, useProfileContent } from '@nyanpasu/interface'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
@@ -112,7 +112,7 @@ function RouteComponent() {
|
||||
})
|
||||
|
||||
const handleEditorDidMount = useCallback(
|
||||
(editor: editor.IStandaloneCodeEditor, monaco: Monaco) => {
|
||||
(editor: editor.IStandaloneCodeEditor) => {
|
||||
editorRef.current = editor
|
||||
|
||||
// Enable URL detection and handling
|
||||
|
||||
@@ -33,7 +33,7 @@ function Connections() {
|
||||
const [mountTable, setMountTable] = useState(true)
|
||||
const deferredMountTable = useDeferredValue(mountTable)
|
||||
const { proceed } = useBlocker({
|
||||
shouldBlockFn: (args) => {
|
||||
shouldBlockFn: () => {
|
||||
setMountTable(false)
|
||||
return !mountTable
|
||||
},
|
||||
|
||||
@@ -7,7 +7,7 @@ import TrayIconDialog from '@/components/setting/modules/tray-icon-dialog'
|
||||
import { formatEnvInfos } from '@/utils'
|
||||
import { Feedback, GitHub, Keyboard } from '@mui/icons-material'
|
||||
import { IconButton } from '@mui/material'
|
||||
import { collectEnvs, openThat } from '@nyanpasu/interface'
|
||||
import { commands, openThat } from '@nyanpasu/interface'
|
||||
import { BasePage } from '@nyanpasu/ui'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
@@ -38,9 +38,14 @@ function SettingPage() {
|
||||
|
||||
const FeedbackIcon = () => {
|
||||
const toFeedback = useLockFn(async () => {
|
||||
const envs = await collectEnvs()
|
||||
const envs = await commands.collectEnvs()
|
||||
|
||||
if (envs.status !== 'ok') {
|
||||
return
|
||||
}
|
||||
|
||||
const formattedEnv = encodeURIComponent(
|
||||
formatEnvInfos(envs)
|
||||
formatEnvInfos(envs.data)
|
||||
.split('\n')
|
||||
.map((v) => `> ${v}`)
|
||||
.join('\n'),
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import { PropsWithChildren } from 'react'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
import { ProfileType } from '../main/profiles/_modules/consts'
|
||||
import { Action } from '../main/profiles/$type/index'
|
||||
|
||||
export default function HeaderFileAction({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem asChild>
|
||||
<Link
|
||||
to="/main/profiles/$type"
|
||||
params={{
|
||||
type: ProfileType.Profile,
|
||||
}}
|
||||
search={{
|
||||
action: Action.ImportLocalProfile,
|
||||
}}
|
||||
>
|
||||
{m.header_file_action_import_local_profile()}
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { PropsWithChildren } from 'react'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { useLockFn } from '@/hooks/use-lock-fn'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { formatEnvInfos } from '@/utils'
|
||||
import { commands } from '@nyanpasu/interface'
|
||||
import { Link } from '@tanstack/react-router'
|
||||
|
||||
const WikiItem = () => {
|
||||
const handleClick = useLockFn(async () => {
|
||||
await commands.openThat('https://nyanpasu.elaina.moe')
|
||||
})
|
||||
|
||||
return (
|
||||
<DropdownMenuItem onClick={handleClick}>
|
||||
{m.header_help_action_wiki()}
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
const IssuesItem = () => {
|
||||
const handleClick = useLockFn(async () => {
|
||||
const envs = await commands.collectEnvs()
|
||||
|
||||
if (envs.status !== 'ok') {
|
||||
return
|
||||
}
|
||||
|
||||
const formattedEnv = encodeURIComponent(
|
||||
formatEnvInfos(envs.data)
|
||||
.split('\n')
|
||||
.map((v) => `> ${v}`)
|
||||
.join('\n'),
|
||||
)
|
||||
|
||||
const params = new URLSearchParams({
|
||||
assignees: '',
|
||||
labels: 'T%3A+Bug%2CS%3A+Untriaged',
|
||||
projects: '',
|
||||
template: 'bug_report.yaml',
|
||||
})
|
||||
|
||||
return commands.openThat(
|
||||
'https://github.com/libnyanpasu/clash-nyanpasu/issues/new?' +
|
||||
params.toString() +
|
||||
// envs can't be serialized
|
||||
'&env_infos=' +
|
||||
formattedEnv,
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<DropdownMenuItem onClick={handleClick}>
|
||||
{m.header_help_action_issues()}
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
}
|
||||
|
||||
export default function HeaderHelpAction({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
<WikiItem />
|
||||
|
||||
<IssuesItem />
|
||||
|
||||
<DropdownMenuItem asChild>
|
||||
<Link to="/main/settings/about">{m.header_help_action_about()}</Link>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +1,17 @@
|
||||
import { ComponentProps } from 'react'
|
||||
import { Button, ButtonProps } from '@/components/ui/button'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import HeaderFileAction from './header-file-action'
|
||||
import HeaderHelpAction from './header-help-action'
|
||||
import HeaderSettingsAction from './header-settings-action'
|
||||
|
||||
const MenuButton = ({ className, ...props }: ButtonProps) => {
|
||||
return (
|
||||
<Button
|
||||
className={cn(
|
||||
'hover:bg-primary-container dark:hover:bg-on-primary h-8 min-w-0 px-3',
|
||||
'data-[state=open]:bg-primary-container dark:data-[state=open]:bg-on-primary',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -25,10 +30,17 @@ export default function HeaderMenu({
|
||||
{...props}
|
||||
data-tauri-drag-region
|
||||
>
|
||||
<MenuButton>Files</MenuButton>
|
||||
<MenuButton>Actions</MenuButton>
|
||||
<MenuButton>Settings</MenuButton>
|
||||
<MenuButton>Help</MenuButton>
|
||||
<HeaderFileAction>
|
||||
<MenuButton>{m.header_file_action_title()}</MenuButton>
|
||||
</HeaderFileAction>
|
||||
|
||||
<HeaderSettingsAction>
|
||||
<MenuButton>{m.header_settings_action_title()}</MenuButton>
|
||||
</HeaderSettingsAction>
|
||||
|
||||
<HeaderHelpAction>
|
||||
<MenuButton>{m.header_help_action_title()}</MenuButton>
|
||||
</HeaderHelpAction>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
import { PropsWithChildren } from 'react'
|
||||
import { useLanguage } from '@/components/providers/language-provider'
|
||||
import {
|
||||
ThemeMode,
|
||||
useExperimentalThemeContext,
|
||||
} from '@/components/providers/theme-provider'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuSub,
|
||||
DropdownMenuSubContent,
|
||||
DropdownMenuSubTrigger,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { Locale, locales } from '@/paraglide/runtime'
|
||||
|
||||
const LanguageSelector = () => {
|
||||
const { language, setLanguage } = useLanguage()
|
||||
|
||||
const handleLanguageChange = (value: string) => {
|
||||
setLanguage(value as Locale)
|
||||
}
|
||||
|
||||
return (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
{m.header_settings_action_language()}
|
||||
</DropdownMenuSubTrigger>
|
||||
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuRadioGroup
|
||||
value={language}
|
||||
onValueChange={handleLanguageChange}
|
||||
>
|
||||
{Object.entries(locales).map(([key, value]) => (
|
||||
<DropdownMenuRadioItem key={key} value={value}>
|
||||
{m.language(key, { locale: value })}
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
)
|
||||
}
|
||||
|
||||
const ThemeModeSelector = () => {
|
||||
const { themeMode, setThemeMode } = useExperimentalThemeContext()
|
||||
|
||||
const handleThemeModeChange = (value: string) => {
|
||||
setThemeMode(value as ThemeMode)
|
||||
}
|
||||
|
||||
const messages = {
|
||||
[ThemeMode.LIGHT]: m.settings_user_interface_theme_mode_light(),
|
||||
[ThemeMode.DARK]: m.settings_user_interface_theme_mode_dark(),
|
||||
[ThemeMode.SYSTEM]: m.settings_user_interface_theme_mode_system(),
|
||||
} satisfies Record<ThemeMode, string>
|
||||
|
||||
return (
|
||||
<DropdownMenuSub>
|
||||
<DropdownMenuSubTrigger>
|
||||
{m.header_settings_action_theme_mode()}
|
||||
</DropdownMenuSubTrigger>
|
||||
|
||||
<DropdownMenuSubContent>
|
||||
<DropdownMenuRadioGroup
|
||||
value={themeMode}
|
||||
onValueChange={handleThemeModeChange}
|
||||
>
|
||||
{Object.entries(messages).map(([key, value]) => (
|
||||
<DropdownMenuRadioItem key={key} value={key}>
|
||||
{value}
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuSubContent>
|
||||
</DropdownMenuSub>
|
||||
)
|
||||
}
|
||||
|
||||
export default function HeaderSettingsAction({ children }: PropsWithChildren) {
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
<LanguageSelector />
|
||||
|
||||
<ThemeModeSelector />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
@@ -103,6 +103,7 @@ const ProxiesGroupButton = () => {
|
||||
return (
|
||||
<NavbarButton
|
||||
to="/main/proxies/group/$name"
|
||||
mobileTo="/main/proxies"
|
||||
params={{ name: fristGroup } as never}
|
||||
icon={<Public className="size-5" />}
|
||||
label={m.navbar_label_proxies()}
|
||||
@@ -111,8 +112,6 @@ const ProxiesGroupButton = () => {
|
||||
}
|
||||
|
||||
export default function Navbar({ className, ...props }: ComponentProps<'div'>) {
|
||||
const isMobile = useIsMobile()
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
@@ -159,7 +158,8 @@ export default function Navbar({ className, ...props }: ComponentProps<'div'>) {
|
||||
/>
|
||||
|
||||
<NavbarButton
|
||||
to={isMobile ? '/main/settings' : '/main/settings/system-proxy'}
|
||||
to="/main/settings/system"
|
||||
mobileTo="/main/settings"
|
||||
icon={<SettingsRounded className="size-5" />}
|
||||
label={m.navbar_label_settings()}
|
||||
/>
|
||||
|
||||
+10
-1
@@ -13,7 +13,7 @@ import {
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import { ProfileType } from '../../_modules/consts'
|
||||
import { Route as IndexRoute } from '../index'
|
||||
import { Action, Route as IndexRoute } from '../index'
|
||||
import ChainProfileImport from './chain-profile-import'
|
||||
import LocalProfileButton from './local-profile-button'
|
||||
import RemoteProfileButton from './remote-profile-button'
|
||||
@@ -52,8 +52,17 @@ const SelectButton = ({
|
||||
const ProxyProfileImport = () => {
|
||||
const { isScrolling } = useScrollArea()
|
||||
|
||||
const { action } = IndexRoute.useSearch()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
// for animation duration to open the modal
|
||||
if (action === Action.ImportLocalProfile) {
|
||||
setOpen(true)
|
||||
}
|
||||
}, [action])
|
||||
|
||||
const handleToggle = () => {
|
||||
setOpen(!open)
|
||||
}
|
||||
|
||||
+34
-1
@@ -2,7 +2,7 @@ import UploadFileRounded from '~icons/material-symbols/upload-file-rounded'
|
||||
import dayjs from 'dayjs'
|
||||
import { filesize } from 'filesize'
|
||||
import { AnimatePresence } from 'framer-motion'
|
||||
import { PropsWithChildren, useState } from 'react'
|
||||
import { PropsWithChildren, useEffect, useState } from 'react'
|
||||
import { Controller, useForm } from 'react-hook-form'
|
||||
import z from 'zod'
|
||||
import { useBlockTask } from '@/components/providers/block-task-provider'
|
||||
@@ -29,7 +29,9 @@ import { formatError } from '@/utils'
|
||||
import { message } from '@/utils/notification'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { LocalProfileBuilder, useProfile } from '@nyanpasu/interface'
|
||||
import { useLocation } from '@tanstack/react-router'
|
||||
import AnimatedErrorItem from '../../_modules/error-item'
|
||||
import { Action, Route as IndexRoute } from '../index'
|
||||
|
||||
const formSchema = z.object({
|
||||
uid: z.string().nullable(),
|
||||
@@ -56,10 +58,35 @@ const getDefaultValues = () => {
|
||||
}
|
||||
|
||||
export default function LocalProfileButton({ children }: PropsWithChildren) {
|
||||
const { action } = IndexRoute.useSearch()
|
||||
|
||||
const navigate = IndexRoute.useNavigate()
|
||||
|
||||
const { pathname } = useLocation()
|
||||
|
||||
const { create } = useProfile()
|
||||
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (action === Action.ImportLocalProfile) {
|
||||
// if the current path is the index page, open the modal immediately
|
||||
if (pathname === '/main/profiles/$type') {
|
||||
setOpen(true)
|
||||
return
|
||||
}
|
||||
|
||||
// else, wait animation duration to open the modal
|
||||
const timeout = setTimeout(() => {
|
||||
setOpen(true)
|
||||
}, 150)
|
||||
|
||||
return () => {
|
||||
clearTimeout(timeout)
|
||||
}
|
||||
}
|
||||
}, [action])
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: getDefaultValues(),
|
||||
@@ -97,6 +124,12 @@ export default function LocalProfileButton({ children }: PropsWithChildren) {
|
||||
|
||||
setOpen(value)
|
||||
|
||||
navigate({
|
||||
search: {
|
||||
action: null,
|
||||
},
|
||||
})
|
||||
|
||||
if (value) {
|
||||
form.reset(getDefaultValues())
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import z from 'zod'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import ImportButton from './_modules/import-button'
|
||||
import ProfilesHeader from './_modules/profiles-header'
|
||||
import ProfilesList from './_modules/profiles-list'
|
||||
|
||||
export enum Action {
|
||||
ImportLocalProfile,
|
||||
}
|
||||
|
||||
export const Route = createFileRoute('/(main)/main/profiles/$type/')({
|
||||
component: RouteComponent,
|
||||
validateSearch: z.object({
|
||||
action: z.enum(Action).optional().nullable(),
|
||||
}),
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
|
||||
+185
-121
@@ -1,142 +1,206 @@
|
||||
import ComputerOutlineRounded from '~icons/material-symbols/computer-outline-rounded'
|
||||
import DisplayExternalInput from '~icons/material-symbols/display-external-input-rounded'
|
||||
import FrameBugOutlineRounded from '~icons/material-symbols/frame-bug-outline-rounded'
|
||||
import ListsRounded from '~icons/material-symbols/lists-rounded'
|
||||
import NetworkNode from '~icons/material-symbols/network-node'
|
||||
import SettingsBoltRounded from '~icons/material-symbols/settings-b-roll-rounded'
|
||||
import SettingsEthernet from '~icons/material-symbols/settings-ethernet-rounded'
|
||||
import ShareWindows from '~icons/material-symbols/share-windows-rounded'
|
||||
import SettingsRounded from '~icons/material-symbols/settings-rounded'
|
||||
import ViewQuilt from '~icons/material-symbols/view-quilt-rounded'
|
||||
import ClashMeta from '@/assets/image/core/clash.meta.png'
|
||||
import { ComponentProps, ReactNode } from 'react'
|
||||
import LogoSvg from '@/assets/image/logo.svg?react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import TextMarquee from '@/components/ui/text-marquee'
|
||||
import useCurrentCoreIcon from '@/hooks/use-current-core-icon'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import { Link, useLocation } from '@tanstack/react-router'
|
||||
|
||||
const StyleClashMeta = () => {
|
||||
return (
|
||||
<img src={ClashMeta} alt="Clash Meta" className="size-8 grayscale-50" />
|
||||
)
|
||||
}
|
||||
|
||||
const StyleLogo = () => {
|
||||
const NyanpasuLogo = () => {
|
||||
return (
|
||||
<LogoSvg className="[&_#element]:fill-primary [&_#bg]:fill-surface size-8" />
|
||||
)
|
||||
}
|
||||
|
||||
const ROUTES = [
|
||||
{
|
||||
label: 'System Proxy',
|
||||
description: 'Configure the system proxy',
|
||||
href: '/main/settings/system-proxy',
|
||||
icon: SettingsEthernet,
|
||||
},
|
||||
{
|
||||
label: 'User Interface',
|
||||
description: 'Configure the user interface',
|
||||
href: '/main/settings/user-interface',
|
||||
icon: ViewQuilt,
|
||||
},
|
||||
{
|
||||
label: 'Clash Settings',
|
||||
description: 'Configure the clash settings',
|
||||
href: '/main/settings/clash-settings',
|
||||
icon: SettingsBoltRounded,
|
||||
},
|
||||
{
|
||||
label: 'Clash External Controll',
|
||||
description: 'Configure the clash external controll',
|
||||
href: '/main/settings/clash-external-controll',
|
||||
icon: DisplayExternalInput,
|
||||
},
|
||||
{
|
||||
label: 'Web UI',
|
||||
description: 'Configure the web ui',
|
||||
href: '/main/settings/web-ui',
|
||||
icon: ShareWindows,
|
||||
},
|
||||
{
|
||||
label: 'Clash Core',
|
||||
description: 'Configure the clash core',
|
||||
href: '/main/settings/clash-core',
|
||||
icon: StyleClashMeta,
|
||||
},
|
||||
{
|
||||
label: 'Clash Filed',
|
||||
description: 'Configure the clash filed',
|
||||
href: '/main/settings/clash-filed',
|
||||
icon: ListsRounded,
|
||||
},
|
||||
{
|
||||
label: 'System Behavior',
|
||||
description: 'Configure the system behavior',
|
||||
href: '/main/settings/system-behavior',
|
||||
icon: ComputerOutlineRounded,
|
||||
},
|
||||
{
|
||||
label: 'System Service',
|
||||
description: 'Configure the system service',
|
||||
href: '/main/settings/system-service',
|
||||
icon: NetworkNode,
|
||||
},
|
||||
{
|
||||
label: 'Nyanpasu Config',
|
||||
description: 'Configure the nyanpasu config',
|
||||
href: '/main/settings/nyanpasu-config',
|
||||
icon: StyleLogo,
|
||||
},
|
||||
{
|
||||
label: 'Debug Utils',
|
||||
description: 'Configure the debug utils',
|
||||
href: '/main/settings/debug-utils',
|
||||
icon: FrameBugOutlineRounded,
|
||||
},
|
||||
{
|
||||
label: 'About',
|
||||
description: 'About the nyanpasu',
|
||||
href: '/main/settings/about',
|
||||
icon: StyleLogo,
|
||||
},
|
||||
] as const
|
||||
|
||||
export default function SettingsNavigate() {
|
||||
const location = useLocation()
|
||||
const CurrentCoreIcon = ({
|
||||
className,
|
||||
...props
|
||||
}: Omit<ComponentProps<'img'>, 'src'>) => {
|
||||
const currentCoreIconUrl = useCurrentCoreIcon()
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-2 p-2">
|
||||
{ROUTES.map((route) => (
|
||||
<Button
|
||||
key={route.href}
|
||||
variant="fab"
|
||||
data-active={String(location.pathname === route.href)}
|
||||
asChild
|
||||
>
|
||||
<Link
|
||||
className={cn(
|
||||
'h-16',
|
||||
'flex items-center gap-2',
|
||||
'data-[active=true]:bg-surface-variant/80',
|
||||
'data-[active=false]:bg-transparent',
|
||||
'data-[active=false]:shadow-none',
|
||||
'data-[active=false]:hover:shadow-none',
|
||||
'data-[active=false]:hover:bg-surface-variant/30',
|
||||
)}
|
||||
to={route.href}
|
||||
>
|
||||
<div className="flex items-center gap-2.5">
|
||||
<div className="size-8">
|
||||
<route.icon className="size-8" />
|
||||
</div>
|
||||
<img
|
||||
src={currentCoreIconUrl}
|
||||
className={cn('size-full', className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="text-sm font-medium">{route.label}</div>
|
||||
<div className="text-xs text-zinc-500">{route.description}</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</Button>
|
||||
))}
|
||||
const NavigateButton = ({
|
||||
icon,
|
||||
label,
|
||||
description,
|
||||
className,
|
||||
...props
|
||||
}: ComponentProps<typeof Link> & {
|
||||
icon: ReactNode
|
||||
label: string
|
||||
description: string
|
||||
}) => {
|
||||
const location = useLocation()
|
||||
|
||||
const isActive = location.pathname === props.to
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="fab"
|
||||
data-active={String(isActive)}
|
||||
className={cn(
|
||||
'h-16',
|
||||
'flex items-center gap-2',
|
||||
'data-[active=true]:bg-surface-variant/80',
|
||||
'data-[active=false]:bg-transparent',
|
||||
'data-[active=false]:shadow-none',
|
||||
'data-[active=false]:hover:shadow-none',
|
||||
'data-[active=false]:hover:bg-surface-variant/30',
|
||||
className,
|
||||
)}
|
||||
asChild
|
||||
>
|
||||
<Link {...props}>
|
||||
<div className="flex max-w-full items-center gap-3">
|
||||
<div className="size-8">{icon}</div>
|
||||
|
||||
<div className="flex min-w-0 flex-1 flex-col gap-1">
|
||||
<div className="text-sm font-medium">{label}</div>
|
||||
|
||||
<TextMarquee className="text-xs text-zinc-500">
|
||||
{description}
|
||||
</TextMarquee>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
const SystemButton = () => {
|
||||
return (
|
||||
<NavigateButton
|
||||
icon={<SettingsEthernet className="size-8" />}
|
||||
label={m.settings_label_system()}
|
||||
description={m.settings_label_system_description()}
|
||||
to="/main/settings/system"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const UserInterfaceButton = () => {
|
||||
return (
|
||||
<NavigateButton
|
||||
icon={<ViewQuilt className="size-8" />}
|
||||
label={m.settings_label_user_interface()}
|
||||
description={m.settings_label_user_interface_description()}
|
||||
to="/main/settings/user-interface"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ClashButton = () => {
|
||||
return (
|
||||
<NavigateButton
|
||||
icon={<SettingsBoltRounded className="size-8" />}
|
||||
label={m.settings_label_clash_settings()}
|
||||
description={m.settings_label_clash_settings_description()}
|
||||
to="/main/settings/clash"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const ExternalControllButton = () => {
|
||||
return (
|
||||
<NavigateButton
|
||||
icon={
|
||||
<div className="relative size-8">
|
||||
<CurrentCoreIcon className="size-7.5" />
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'absolute -right-1 -bottom-1 size-4 p-0.5',
|
||||
'text-primary bg-surface-variant rounded-full shadow-sm',
|
||||
)}
|
||||
>
|
||||
<DisplayExternalInput className="size-3" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
label={m.settings_label_external_controll()}
|
||||
description={m.settings_label_external_controll_description()}
|
||||
to="/main/settings/web-ui"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const NyanpasuButton = () => {
|
||||
return (
|
||||
<NavigateButton
|
||||
icon={
|
||||
<div className="relative size-8">
|
||||
<NyanpasuLogo />
|
||||
|
||||
<div
|
||||
className={cn(
|
||||
'absolute -right-1 -bottom-1 size-4 p-0.5',
|
||||
'text-primary bg-surface-variant rounded-full shadow-sm',
|
||||
)}
|
||||
>
|
||||
<SettingsRounded className="text-primary size-3" />
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
label={m.settings_label_nyanpasu()}
|
||||
description={m.settings_label_nyanpasu_description()}
|
||||
to="/main/settings/nyanpasu"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const DebugButton = () => {
|
||||
return (
|
||||
<NavigateButton
|
||||
icon={<FrameBugOutlineRounded className="size-8" />}
|
||||
label={m.settings_label_debug()}
|
||||
description={m.settings_label_debug_description()}
|
||||
to="/main/settings/debug"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const AboutButton = () => {
|
||||
return (
|
||||
<NavigateButton
|
||||
icon={<NyanpasuLogo />}
|
||||
label={m.settings_label_about()}
|
||||
description={m.settings_label_about_description()}
|
||||
to="/main/settings/about"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default function SettingsNavigate() {
|
||||
return (
|
||||
<div className="flex flex-col gap-2 p-2">
|
||||
<SystemButton />
|
||||
|
||||
<UserInterfaceButton />
|
||||
|
||||
<ClashButton />
|
||||
|
||||
<ExternalControllButton />
|
||||
|
||||
<NyanpasuButton />
|
||||
|
||||
<DebugButton />
|
||||
|
||||
<AboutButton />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/(main)/main/settings/clash-core')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/(main)/main/settings/clash-core"!</div>
|
||||
}
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute(
|
||||
'/(main)/main/settings/clash-external-controll',
|
||||
)({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/(main)/main/settings/clash-external-controll"!</div>
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/(main)/main/settings/clash-filed')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/(main)/main/settings/clash-filed"!</div>
|
||||
}
|
||||
+2
-1
@@ -2,6 +2,7 @@ import { useMemo } from 'react'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { useLockFn } from '@/hooks/use-lock-fn'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { formatError } from '@/utils'
|
||||
import { message } from '@/utils/notification'
|
||||
import { useClashConfig } from '@nyanpasu/interface'
|
||||
import { SettingsCard, SettingsCardContent } from '../../_modules/settings-card'
|
||||
@@ -17,7 +18,7 @@ export default function AllowLanSwitch() {
|
||||
'allow-lan': input,
|
||||
})
|
||||
} catch (error) {
|
||||
message(`Activation Allow LAN failed!`, {
|
||||
message(`Activation Allow LAN failed!\n Error: ${formatError(error)}`, {
|
||||
title: 'Error',
|
||||
kind: 'error',
|
||||
})
|
||||
+2
-1
@@ -2,6 +2,7 @@ import { useMemo } from 'react'
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { useLockFn } from '@/hooks/use-lock-fn'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { formatError } from '@/utils'
|
||||
import { message } from '@/utils/notification'
|
||||
import { useClashConfig } from '@nyanpasu/interface'
|
||||
import { SettingsCard, SettingsCardContent } from '../../_modules/settings-card'
|
||||
@@ -17,7 +18,7 @@ export default function IPv6Switch() {
|
||||
ipv6: input,
|
||||
})
|
||||
} catch (error) {
|
||||
message(`Activation IPv6 failed!`, {
|
||||
message(`Activation IPv6 failed!\n Error: ${formatError(error)}`, {
|
||||
title: 'Error',
|
||||
kind: 'error',
|
||||
})
|
||||
+1
-1
@@ -12,7 +12,7 @@ import MixedPortConfig from './_modules/mixed-port-config'
|
||||
import RandomPortSwitch from './_modules/random-port-switch'
|
||||
import TunStackSelector from './_modules/tun-stack-selector'
|
||||
|
||||
export const Route = createFileRoute('/(main)/main/settings/clash-settings')({
|
||||
export const Route = createFileRoute('/(main)/main/settings/clash')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import WindowDebug from './_modules/window-debug'
|
||||
|
||||
export const Route = createFileRoute('/(main)/main/settings/debug-utils')({
|
||||
export const Route = createFileRoute('/(main)/main/settings/debug')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/(main)/main/settings/nyanpasu-config')({
|
||||
export const Route = createFileRoute('/(main)/main/settings/nyanpasu')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
@@ -13,7 +13,7 @@ function RouteComponent() {
|
||||
return (
|
||||
<Sidebar data-slot="settings-container">
|
||||
<SidebarContent
|
||||
className="bg-surface-variant/10"
|
||||
className="bg-surface-variant/10 [&>div>div]:block!"
|
||||
data-slot="settings-sidebar-scroll-area"
|
||||
>
|
||||
<SettingsNavigate />
|
||||
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/(main)/main/settings/system-behavior')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/(main)/main/settings/system-behavior"!</div>
|
||||
}
|
||||
-9
@@ -1,9 +0,0 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/(main)/main/settings/system-service')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello "/(main)/main/settings/system-service"!</div>
|
||||
}
|
||||
+2
-2
@@ -40,10 +40,10 @@ export default function ProxyBypassConfig() {
|
||||
|
||||
const handleSubmit = form.handleSubmit(async (data) => {
|
||||
try {
|
||||
await systemProxyBypass.upsert(data.systemProxyBypass ?? DEFAULT_BYPASS)
|
||||
await systemProxyBypass.upsert(data.systemProxyBypass || DEFAULT_BYPASS)
|
||||
|
||||
form.reset({
|
||||
systemProxyBypass: data.systemProxyBypass ?? DEFAULT_BYPASS,
|
||||
systemProxyBypass: data.systemProxyBypass || DEFAULT_BYPASS,
|
||||
})
|
||||
} catch (error) {
|
||||
message(formatError(error), {
|
||||
+2
-1
@@ -1,6 +1,7 @@
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import { useLockFn } from '@/hooks/use-lock-fn'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { formatError } from '@/utils'
|
||||
import { message } from '@/utils/notification'
|
||||
import { useSetting } from '@nyanpasu/interface'
|
||||
import { SettingsCard, SettingsCardContent } from '../../_modules/settings-card'
|
||||
@@ -12,7 +13,7 @@ export default function ProxyGuardSwitch() {
|
||||
try {
|
||||
await proxyGuard.upsert(!proxyGuard.value)
|
||||
} catch (error) {
|
||||
message(`Activation Proxy Guard failed!`, {
|
||||
message(`Activation Proxy Guard failed!\n Error: ${formatError(error)}`, {
|
||||
title: 'Error',
|
||||
kind: 'error',
|
||||
})
|
||||
+306
@@ -0,0 +1,306 @@
|
||||
import { startCase } from 'lodash-es'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card'
|
||||
import {
|
||||
Modal,
|
||||
ModalClose,
|
||||
ModalContent,
|
||||
ModalTitle,
|
||||
ModalTrigger,
|
||||
} from '@/components/ui/modal'
|
||||
import { OS } from '@/consts'
|
||||
import { useLockFn } from '@/hooks/use-lock-fn'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { formatError } from '@/utils'
|
||||
import { message } from '@/utils/notification'
|
||||
import { getShikiSingleton } from '@/utils/shiki'
|
||||
import {
|
||||
commands,
|
||||
useCoreDir,
|
||||
useServicePrompt,
|
||||
useSystemService,
|
||||
} from '@nyanpasu/interface'
|
||||
import { cn } from '@nyanpasu/ui'
|
||||
import { writeText } from '@tauri-apps/plugin-clipboard-manager'
|
||||
import { SettingsCard, SettingsCardContent } from '../../_modules/settings-card'
|
||||
|
||||
const SystemServiceCtrlItem = ({
|
||||
name,
|
||||
value,
|
||||
}: {
|
||||
name: string
|
||||
value?: string
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex w-full leading-8" data-slot="system-service-ctrl-item">
|
||||
<div
|
||||
className="w-32 capitalize"
|
||||
data-slot="system-service-ctrl-item-name"
|
||||
>
|
||||
{name}:
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="text-warp flex-1 break-all"
|
||||
data-slot="system-service-ctrl-item-value"
|
||||
>
|
||||
{value ?? '-'}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const ServiceDetailButton = () => {
|
||||
const { query } = useSystemService()
|
||||
|
||||
return (
|
||||
<Modal>
|
||||
<ModalTrigger asChild>
|
||||
<Button data-slot="system-service-detail-button">
|
||||
{m.settings_system_proxy_system_service_ctrl_detail()}
|
||||
</Button>
|
||||
</ModalTrigger>
|
||||
|
||||
<ModalContent>
|
||||
<Card className="w-96">
|
||||
<CardHeader>
|
||||
<ModalTitle>
|
||||
{m.settings_system_proxy_system_service_ctrl_detail()}
|
||||
</ModalTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<pre className="overflow-auto font-mono select-text">
|
||||
{JSON.stringify(query.data, null, 2)}
|
||||
</pre>
|
||||
</CardContent>
|
||||
|
||||
<CardFooter>
|
||||
<ModalClose>{m.common_close()}</ModalClose>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const ServiceInstallButton = () => {
|
||||
const { upsert } = useSystemService()
|
||||
|
||||
const handleInstallClick = useLockFn(async () => {
|
||||
try {
|
||||
await upsert.mutateAsync('install')
|
||||
await commands.restartSidecar()
|
||||
} catch (e) {
|
||||
const errorMessage = `${m.settings_system_proxy_system_service_ctrl_failed_install()}: ${formatError(e)}`
|
||||
|
||||
message(errorMessage, {
|
||||
kind: 'error',
|
||||
})
|
||||
// // If the installation fails, prompt the user to manually install the service
|
||||
// promptDialog.show(
|
||||
// query.data?.status === 'not_installed' ? 'install' : 'uninstall',
|
||||
// )
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="flat"
|
||||
onClick={handleInstallClick}
|
||||
loading={upsert.isPending}
|
||||
>
|
||||
{m.settings_system_proxy_system_service_ctrl_install()}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
const ServiceUninstallButton = () => {
|
||||
const { upsert } = useSystemService()
|
||||
|
||||
const handleUninstallClick = useLockFn(async () => {
|
||||
await upsert.mutateAsync('uninstall')
|
||||
})
|
||||
|
||||
return (
|
||||
<Button onClick={handleUninstallClick} loading={upsert.isPending}>
|
||||
{m.settings_system_proxy_system_service_ctrl_uninstall()}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
// {
|
||||
// operation: 'uninstall' | 'install' | 'start' | 'stop' | null
|
||||
// }
|
||||
const ServicePromptButton = () => {
|
||||
const {
|
||||
query: { data: systemService },
|
||||
} = useSystemService()
|
||||
|
||||
const { data: serviceInstallPrompt } = useServicePrompt()
|
||||
|
||||
const { data: coreDir } = useCoreDir()
|
||||
|
||||
const [codes, setCodes] = useState<string | null>(null)
|
||||
|
||||
const userOperationCommands = useMemo(() => {
|
||||
if (systemService?.status === 'not_installed' && serviceInstallPrompt) {
|
||||
return `cd "${coreDir}"\n${serviceInstallPrompt}`
|
||||
} else if (systemService?.status) {
|
||||
const operation = systemService?.status === 'running' ? 'stop' : 'start'
|
||||
|
||||
return `cd "${coreDir}"\n${OS !== 'windows' ? 'sudo ' : ''}./nyanpasu-service ${operation}`
|
||||
}
|
||||
return ''
|
||||
}, [systemService?.status, serviceInstallPrompt, coreDir])
|
||||
|
||||
useEffect(() => {
|
||||
const handleGenerateCodes = async () => {
|
||||
const shiki = await getShikiSingleton()
|
||||
const code = shiki.codeToHtml(userOperationCommands, {
|
||||
lang: 'shell',
|
||||
themes: {
|
||||
dark: 'nord',
|
||||
light: 'min-light',
|
||||
},
|
||||
})
|
||||
|
||||
setCodes(code)
|
||||
}
|
||||
|
||||
handleGenerateCodes()
|
||||
}, [userOperationCommands])
|
||||
|
||||
const handleCopyToClipboard = useLockFn(async () => {
|
||||
if (!userOperationCommands) {
|
||||
return
|
||||
}
|
||||
|
||||
await writeText(userOperationCommands)
|
||||
})
|
||||
|
||||
return (
|
||||
<Modal>
|
||||
<ModalTrigger asChild>
|
||||
<Button variant="flat">
|
||||
{m.settings_system_proxy_system_service_ctrl_prompt()}
|
||||
</Button>
|
||||
</ModalTrigger>
|
||||
|
||||
<ModalContent>
|
||||
<Card className="max-w-3xl min-w-96">
|
||||
<CardHeader>
|
||||
<ModalTitle>
|
||||
{m.settings_system_proxy_system_service_ctrl_manual_prompt()}
|
||||
</ModalTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<p className="leading-6">
|
||||
{m.settings_system_proxy_system_service_ctrl_manual_operation_prompt()}
|
||||
</p>
|
||||
|
||||
{codes && (
|
||||
<div
|
||||
className={cn(
|
||||
'overflow-clip rounded select-text',
|
||||
'[&>pre]:overflow-auto [&>pre]:p-2',
|
||||
'[&>pre]:bg-surface-variant! dark:[&>pre]:bg-black!',
|
||||
)}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: codes,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</CardContent>
|
||||
|
||||
<CardFooter className="gap-2">
|
||||
<Button variant="flat" onClick={handleCopyToClipboard}>
|
||||
{m.common_copy()}
|
||||
</Button>
|
||||
|
||||
<ModalClose>{m.common_close()}</ModalClose>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
const ServiceControlButtons = () => {
|
||||
const { query, upsert } = useSystemService()
|
||||
|
||||
const handleToggleClick = useLockFn(async () => {
|
||||
await upsert.mutateAsync(
|
||||
query.data?.status === 'running' ? 'stop' : 'start',
|
||||
)
|
||||
})
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="flat"
|
||||
onClick={handleToggleClick}
|
||||
loading={upsert.isPending}
|
||||
>
|
||||
{query.data?.status === 'running'
|
||||
? m.settings_system_proxy_system_service_ctrl_stop()
|
||||
: m.settings_system_proxy_system_service_ctrl_start()}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
export default function SystemServiceCtrl() {
|
||||
const { query } = useSystemService()
|
||||
|
||||
const isInstalled = query.data?.status !== 'not_installed'
|
||||
|
||||
return (
|
||||
<SettingsCard data-slot="system-service-ctrl-card">
|
||||
<SettingsCardContent
|
||||
data-slot="system-service-ctrl-card-content"
|
||||
className="flex flex-col gap-3 px-2"
|
||||
>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
{m.settings_system_proxy_system_service_ctrl_label()}
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="gap-1 select-text">
|
||||
<SystemServiceCtrlItem
|
||||
name="Service Name"
|
||||
value={query.data?.name}
|
||||
/>
|
||||
|
||||
<SystemServiceCtrlItem
|
||||
name="Server Version"
|
||||
value={query.data?.server?.version}
|
||||
/>
|
||||
|
||||
<SystemServiceCtrlItem
|
||||
name="Service Status"
|
||||
value={startCase(query.data?.status)}
|
||||
/>
|
||||
</CardContent>
|
||||
|
||||
<CardFooter className="flex-wrap-reverse gap-2">
|
||||
{isInstalled ? (
|
||||
<>
|
||||
<ServiceControlButtons />
|
||||
|
||||
<ServiceUninstallButton />
|
||||
</>
|
||||
) : (
|
||||
<ServiceInstallButton />
|
||||
)}
|
||||
|
||||
<ServiceDetailButton />
|
||||
|
||||
<div className="flex-1" />
|
||||
|
||||
<ServicePromptButton />
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</SettingsCardContent>
|
||||
</SettingsCard>
|
||||
)
|
||||
}
|
||||
+66
@@ -0,0 +1,66 @@
|
||||
import { Switch } from '@/components/ui/switch'
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip'
|
||||
import { useLockFn } from '@/hooks/use-lock-fn'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { formatError } from '@/utils'
|
||||
import { message } from '@/utils/notification'
|
||||
import { useSetting, useSystemService } from '@nyanpasu/interface'
|
||||
import { SettingsCard, SettingsCardContent } from '../../_modules/settings-card'
|
||||
|
||||
export default function SystemServiceSwitch() {
|
||||
const serviceMode = useSetting('enable_service_mode')
|
||||
|
||||
const { query } = useSystemService()
|
||||
|
||||
const disabled = query.data?.status === 'not_installed'
|
||||
|
||||
const handleServiceMode = useLockFn(async () => {
|
||||
try {
|
||||
await serviceMode.upsert(!serviceMode.value)
|
||||
} catch (error) {
|
||||
message(
|
||||
`Activation Service Mode failed!\n Error: ${formatError(error)}`,
|
||||
{
|
||||
title: 'Error',
|
||||
kind: 'error',
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<SettingsCard data-slot="system-service-switch-card">
|
||||
<SettingsCardContent
|
||||
className="flex items-center justify-between px-3"
|
||||
data-slot="system-service-switch-card-content"
|
||||
>
|
||||
<div>{m.settings_system_proxy_service_mode_label()}</div>
|
||||
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<div>
|
||||
<Switch
|
||||
checked={Boolean(serviceMode.value)}
|
||||
onCheckedChange={handleServiceMode}
|
||||
loading={serviceMode.isPending}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</TooltipTrigger>
|
||||
|
||||
{disabled && (
|
||||
<TooltipContent>
|
||||
<span>
|
||||
{m.settings_system_proxy_service_mode_disabled_tooltip()}
|
||||
</span>
|
||||
</TooltipContent>
|
||||
)}
|
||||
</Tooltip>
|
||||
</SettingsCardContent>
|
||||
</SettingsCard>
|
||||
)
|
||||
}
|
||||
+11
-2
@@ -2,6 +2,7 @@ import {
|
||||
SystemProxyButton,
|
||||
TunModeButton,
|
||||
} from '@/components/settings/system-proxy'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { SettingsCard, SettingsCardContent } from '../_modules/settings-card'
|
||||
@@ -13,8 +14,10 @@ import CurrentSystemProxy from './_modules/current-system-proxy'
|
||||
import ProxyBypassConfig from './_modules/proxy-bypass-config'
|
||||
import ProxyGuardConfig from './_modules/proxy-guard-config'
|
||||
import ProxyGuardSwitch from './_modules/proxy-guard-switch'
|
||||
import SystemServiceCtrl from './_modules/system-service-ctrl'
|
||||
import SystemServiceSwitch from './_modules/system-service-switch'
|
||||
|
||||
export const Route = createFileRoute('/(main)/main/settings/system-proxy')({
|
||||
export const Route = createFileRoute('/(main)/main/settings/system')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
@@ -22,7 +25,7 @@ function RouteComponent() {
|
||||
return (
|
||||
<>
|
||||
<SettingsTitlePlaceholder />
|
||||
<SettingsTitle>{m.settings_system_proxy_title()}</SettingsTitle>
|
||||
<SettingsTitle>{m.settings_label_system()}</SettingsTitle>
|
||||
|
||||
<SettingsCard>
|
||||
<SettingsCardContent>
|
||||
@@ -41,6 +44,12 @@ function RouteComponent() {
|
||||
<ProxyBypassConfig />
|
||||
|
||||
<CurrentSystemProxy />
|
||||
|
||||
<Separator className="my-4" />
|
||||
|
||||
<SystemServiceSwitch />
|
||||
|
||||
<SystemServiceCtrl />
|
||||
</>
|
||||
)
|
||||
}
|
||||
+27
-27
@@ -1,12 +1,11 @@
|
||||
import { useLanguage } from '@/components/providers/language-provider'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { Locale, locales } from '@/paraglide/runtime'
|
||||
import { SettingsCard, SettingsCardContent } from '../../_modules/settings-card'
|
||||
@@ -21,29 +20,30 @@ export default function LanguageSelector() {
|
||||
return (
|
||||
<SettingsCard data-slot="language-selector-card">
|
||||
<SettingsCardContent
|
||||
className="flex items-center justify-between px-3"
|
||||
className="flex items-center justify-between px-2"
|
||||
data-slot="language-selector-card-content"
|
||||
>
|
||||
<div>{m.settings_user_interface_language_label()}</div>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="flat">{m.language()}</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuRadioGroup
|
||||
value={language}
|
||||
onValueChange={handleLanguageChange}
|
||||
<Select
|
||||
variant="outlined"
|
||||
value={language}
|
||||
onValueChange={handleLanguageChange}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={m.settings_user_interface_language_label()}
|
||||
>
|
||||
{locales.map((value) => (
|
||||
<DropdownMenuRadioItem key={value} value={value}>
|
||||
{m.language(value, { locale: value })}
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
{language ? m.language(language, { locale: language }) : null}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
{Object.entries(locales).map(([key, value]) => (
|
||||
<SelectItem key={key} value={value}>
|
||||
{m.language(key, { locale: value })}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SettingsCardContent>
|
||||
</SettingsCard>
|
||||
)
|
||||
|
||||
+34
-30
@@ -26,43 +26,47 @@ export default function SwitchLegacy() {
|
||||
return (
|
||||
<SettingsCard data-slot="switch-legacy-card">
|
||||
<SettingsCardContent
|
||||
className="flex items-center justify-between px-3"
|
||||
className="flex items-center justify-between px-2"
|
||||
data-slot="switch-legacy-card-content"
|
||||
>
|
||||
<div>Switch to Legacy UI</div>
|
||||
<Card className="w-full space-y-4">
|
||||
<CardHeader>Switch to Legacy UI</CardHeader>
|
||||
|
||||
<Modal>
|
||||
<ModalTrigger asChild>
|
||||
<Button variant="flat">Open</Button>
|
||||
</ModalTrigger>
|
||||
<CardFooter>
|
||||
<Modal>
|
||||
<ModalTrigger asChild>
|
||||
<Button variant="stroked">Open</Button>
|
||||
</ModalTrigger>
|
||||
|
||||
<ModalContent>
|
||||
<Card className="w-96">
|
||||
<CardHeader>
|
||||
<ModalTitle>
|
||||
Are you sure you want to switch to Legacy UI?
|
||||
</ModalTitle>
|
||||
</CardHeader>
|
||||
<ModalContent>
|
||||
<Card className="w-96">
|
||||
<CardHeader>
|
||||
<ModalTitle>
|
||||
Are you sure you want to switch to Legacy UI?
|
||||
</ModalTitle>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent>
|
||||
<p>
|
||||
Switching to Legacy UI will revert the UI to the original
|
||||
design.
|
||||
</p>
|
||||
</CardContent>
|
||||
<CardContent>
|
||||
<p>
|
||||
Switching to Legacy UI will revert the UI to the original
|
||||
design.
|
||||
</p>
|
||||
</CardContent>
|
||||
|
||||
<CardFooter className="gap-2">
|
||||
<Button variant="flat" onClick={handleClick}>
|
||||
Continue
|
||||
</Button>
|
||||
<CardFooter className="gap-2">
|
||||
<Button variant="flat" onClick={handleClick}>
|
||||
Continue
|
||||
</Button>
|
||||
|
||||
<ModalClose asChild>
|
||||
<Button>Cancel</Button>
|
||||
</ModalClose>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<ModalClose asChild>
|
||||
<Button>Cancel</Button>
|
||||
</ModalClose>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</SettingsCardContent>
|
||||
</SettingsCard>
|
||||
)
|
||||
|
||||
+76
-31
@@ -1,7 +1,11 @@
|
||||
import Check from '~icons/material-symbols/check-rounded'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useExperimentalThemeContext } from '@/components/providers/theme-provider'
|
||||
import {
|
||||
DEFAULT_COLOR,
|
||||
useExperimentalThemeContext,
|
||||
} from '@/components/providers/theme-provider'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import { Card, CardContent, CardFooter, CardHeader } from '@/components/ui/card'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
@@ -11,6 +15,15 @@ import { m } from '@/paraglide/messages'
|
||||
import { Wheel } from '@uiw/react-color'
|
||||
import { SettingsCard, SettingsCardContent } from '../../_modules/settings-card'
|
||||
|
||||
const PERSETS = [
|
||||
DEFAULT_COLOR,
|
||||
'#9e1e67',
|
||||
'#3d009e',
|
||||
'#00089e',
|
||||
'#066b9e',
|
||||
'#9e5a00',
|
||||
] as const
|
||||
|
||||
export default function ThemeColorConfig() {
|
||||
const { themeColor, setThemeColor } = useExperimentalThemeContext()
|
||||
|
||||
@@ -26,42 +39,74 @@ export default function ThemeColorConfig() {
|
||||
return (
|
||||
<SettingsCard data-slot="theme-color-config-card">
|
||||
<SettingsCardContent
|
||||
className="flex items-center justify-between px-3"
|
||||
className="flex items-center justify-between px-2"
|
||||
data-slot="theme-color-config-card-content"
|
||||
>
|
||||
<div>{m.settings_user_interface_theme_color_label()}</div>
|
||||
<Card className="w-full">
|
||||
<CardHeader>
|
||||
{m.settings_user_interface_theme_color_label()}
|
||||
</CardHeader>
|
||||
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button className="flex items-center gap-2 px-4" variant="flat">
|
||||
<span
|
||||
className="size-4 rounded"
|
||||
style={{ backgroundColor: themeColor }}
|
||||
/>
|
||||
<CardContent>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{PERSETS.map((color) => (
|
||||
<Button
|
||||
key={color}
|
||||
className="flex items-center gap-2 px-4"
|
||||
variant={themeColor === color ? 'flat' : 'stroked'}
|
||||
onClick={() => setThemeColor(color)}
|
||||
>
|
||||
<span
|
||||
className="outline-surface-variant size-4 rounded outline"
|
||||
style={{ backgroundColor: color }}
|
||||
/>
|
||||
|
||||
<span>{themeColor}</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<span>{color.toLocaleUpperCase()}</span>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
|
||||
<DropdownMenuContent className="flex flex-col gap-4 rounded-2xl p-4">
|
||||
<Wheel
|
||||
data-slot="theme-color-config-colorful"
|
||||
color={cachedThemeColor}
|
||||
onChange={(color) => {
|
||||
setCachedThemeColor(color.hex)
|
||||
}}
|
||||
/>
|
||||
<CardFooter>
|
||||
<DropdownMenu open={open} onOpenChange={setOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button className="flex items-center gap-2 px-4" variant="flat">
|
||||
<span
|
||||
className="outline-surface-variant size-4 rounded outline"
|
||||
style={{
|
||||
backgroundColor: themeColor,
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
className="flex items-center justify-center gap-2"
|
||||
variant="flat"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<Check className="size-5" />
|
||||
<span>{m.common_submit()}</span>
|
||||
</Button>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
<span>
|
||||
{PERSETS.includes(themeColor as (typeof PERSETS)[number])
|
||||
? m.settings_user_interface_theme_color_custom()
|
||||
: themeColor.toLocaleUpperCase()}
|
||||
</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent className="flex flex-col gap-4 rounded-2xl p-4">
|
||||
<Wheel
|
||||
data-slot="theme-color-config-colorful"
|
||||
color={cachedThemeColor}
|
||||
onChange={(color) => {
|
||||
setCachedThemeColor(color.hex)
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
className="flex items-center justify-center gap-2"
|
||||
variant="flat"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<Check className="size-5" />
|
||||
<span>{m.common_submit()}</span>
|
||||
</Button>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</CardFooter>
|
||||
</Card>
|
||||
</SettingsCardContent>
|
||||
</SettingsCard>
|
||||
)
|
||||
|
||||
+27
-27
@@ -2,14 +2,13 @@ import {
|
||||
ThemeMode,
|
||||
useExperimentalThemeContext,
|
||||
} from '@/components/providers/theme-provider'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuRadioGroup,
|
||||
DropdownMenuRadioItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select'
|
||||
import { m } from '@/paraglide/messages'
|
||||
import { SettingsCard, SettingsCardContent } from '../../_modules/settings-card'
|
||||
|
||||
@@ -29,29 +28,30 @@ export default function ThemeModeSelector() {
|
||||
return (
|
||||
<SettingsCard data-slot="theme-mode-selection-card">
|
||||
<SettingsCardContent
|
||||
className="flex items-center justify-between px-3"
|
||||
className="flex items-center justify-between px-2"
|
||||
data-slot="theme-mode-selection-card-content"
|
||||
>
|
||||
<div>{m.settings_user_interface_theme_mode_label()}</div>
|
||||
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant="flat">{messages[themeMode]}</Button>
|
||||
</DropdownMenuTrigger>
|
||||
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuRadioGroup
|
||||
value={themeMode}
|
||||
onValueChange={handleThemeModeChange}
|
||||
<Select
|
||||
variant="outlined"
|
||||
value={themeMode}
|
||||
onValueChange={handleThemeModeChange}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue
|
||||
placeholder={m.settings_user_interface_theme_mode_label()}
|
||||
>
|
||||
{Object.values(ThemeMode).map((value) => (
|
||||
<DropdownMenuRadioItem key={value} value={value}>
|
||||
{value}
|
||||
</DropdownMenuRadioItem>
|
||||
))}
|
||||
</DropdownMenuRadioGroup>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
{themeMode ? messages[themeMode] : null}
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
|
||||
<SelectContent>
|
||||
{Object.entries(messages).map(([key, value]) => (
|
||||
<SelectItem key={key} value={key}>
|
||||
{value}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</SettingsCardContent>
|
||||
</SettingsCard>
|
||||
)
|
||||
|
||||
@@ -37,15 +37,10 @@ import { Route as mainMainProxiesIndexRouteImport } from './pages/(main)/main/pr
|
||||
import { Route as mainMainProfilesIndexRouteImport } from './pages/(main)/main/profiles/index'
|
||||
import { Route as mainMainSettingsWebUiRouteRouteImport } from './pages/(main)/main/settings/web-ui/route'
|
||||
import { Route as mainMainSettingsUserInterfaceRouteRouteImport } from './pages/(main)/main/settings/user-interface/route'
|
||||
import { Route as mainMainSettingsSystemServiceRouteRouteImport } from './pages/(main)/main/settings/system-service/route'
|
||||
import { Route as mainMainSettingsSystemProxyRouteRouteImport } from './pages/(main)/main/settings/system-proxy/route'
|
||||
import { Route as mainMainSettingsSystemBehaviorRouteRouteImport } from './pages/(main)/main/settings/system-behavior/route'
|
||||
import { Route as mainMainSettingsNyanpasuConfigRouteRouteImport } from './pages/(main)/main/settings/nyanpasu-config/route'
|
||||
import { Route as mainMainSettingsDebugUtilsRouteRouteImport } from './pages/(main)/main/settings/debug-utils/route'
|
||||
import { Route as mainMainSettingsClashSettingsRouteRouteImport } from './pages/(main)/main/settings/clash-settings/route'
|
||||
import { Route as mainMainSettingsClashFiledRouteRouteImport } from './pages/(main)/main/settings/clash-filed/route'
|
||||
import { Route as mainMainSettingsClashExternalControllRouteRouteImport } from './pages/(main)/main/settings/clash-external-controll/route'
|
||||
import { Route as mainMainSettingsClashCoreRouteRouteImport } from './pages/(main)/main/settings/clash-core/route'
|
||||
import { Route as mainMainSettingsSystemRouteRouteImport } from './pages/(main)/main/settings/system/route'
|
||||
import { Route as mainMainSettingsNyanpasuRouteRouteImport } from './pages/(main)/main/settings/nyanpasu/route'
|
||||
import { Route as mainMainSettingsDebugRouteRouteImport } from './pages/(main)/main/settings/debug/route'
|
||||
import { Route as mainMainSettingsClashRouteRouteImport } from './pages/(main)/main/settings/clash/route'
|
||||
import { Route as mainMainSettingsAboutRouteRouteImport } from './pages/(main)/main/settings/about/route'
|
||||
import { Route as mainMainProfilesInspectRouteRouteImport } from './pages/(main)/main/profiles/inspect/route'
|
||||
import { Route as mainMainProfilesTypeIndexRouteImport } from './pages/(main)/main/profiles/$type/index'
|
||||
@@ -193,58 +188,28 @@ const mainMainSettingsUserInterfaceRouteRoute =
|
||||
path: '/user-interface',
|
||||
getParentRoute: () => mainMainSettingsRouteRoute,
|
||||
} as any)
|
||||
const mainMainSettingsSystemServiceRouteRoute =
|
||||
mainMainSettingsSystemServiceRouteRouteImport.update({
|
||||
id: '/system-service',
|
||||
path: '/system-service',
|
||||
const mainMainSettingsSystemRouteRoute =
|
||||
mainMainSettingsSystemRouteRouteImport.update({
|
||||
id: '/system',
|
||||
path: '/system',
|
||||
getParentRoute: () => mainMainSettingsRouteRoute,
|
||||
} as any)
|
||||
const mainMainSettingsSystemProxyRouteRoute =
|
||||
mainMainSettingsSystemProxyRouteRouteImport.update({
|
||||
id: '/system-proxy',
|
||||
path: '/system-proxy',
|
||||
const mainMainSettingsNyanpasuRouteRoute =
|
||||
mainMainSettingsNyanpasuRouteRouteImport.update({
|
||||
id: '/nyanpasu',
|
||||
path: '/nyanpasu',
|
||||
getParentRoute: () => mainMainSettingsRouteRoute,
|
||||
} as any)
|
||||
const mainMainSettingsSystemBehaviorRouteRoute =
|
||||
mainMainSettingsSystemBehaviorRouteRouteImport.update({
|
||||
id: '/system-behavior',
|
||||
path: '/system-behavior',
|
||||
const mainMainSettingsDebugRouteRoute =
|
||||
mainMainSettingsDebugRouteRouteImport.update({
|
||||
id: '/debug',
|
||||
path: '/debug',
|
||||
getParentRoute: () => mainMainSettingsRouteRoute,
|
||||
} as any)
|
||||
const mainMainSettingsNyanpasuConfigRouteRoute =
|
||||
mainMainSettingsNyanpasuConfigRouteRouteImport.update({
|
||||
id: '/nyanpasu-config',
|
||||
path: '/nyanpasu-config',
|
||||
getParentRoute: () => mainMainSettingsRouteRoute,
|
||||
} as any)
|
||||
const mainMainSettingsDebugUtilsRouteRoute =
|
||||
mainMainSettingsDebugUtilsRouteRouteImport.update({
|
||||
id: '/debug-utils',
|
||||
path: '/debug-utils',
|
||||
getParentRoute: () => mainMainSettingsRouteRoute,
|
||||
} as any)
|
||||
const mainMainSettingsClashSettingsRouteRoute =
|
||||
mainMainSettingsClashSettingsRouteRouteImport.update({
|
||||
id: '/clash-settings',
|
||||
path: '/clash-settings',
|
||||
getParentRoute: () => mainMainSettingsRouteRoute,
|
||||
} as any)
|
||||
const mainMainSettingsClashFiledRouteRoute =
|
||||
mainMainSettingsClashFiledRouteRouteImport.update({
|
||||
id: '/clash-filed',
|
||||
path: '/clash-filed',
|
||||
getParentRoute: () => mainMainSettingsRouteRoute,
|
||||
} as any)
|
||||
const mainMainSettingsClashExternalControllRouteRoute =
|
||||
mainMainSettingsClashExternalControllRouteRouteImport.update({
|
||||
id: '/clash-external-controll',
|
||||
path: '/clash-external-controll',
|
||||
getParentRoute: () => mainMainSettingsRouteRoute,
|
||||
} as any)
|
||||
const mainMainSettingsClashCoreRouteRoute =
|
||||
mainMainSettingsClashCoreRouteRouteImport.update({
|
||||
id: '/clash-core',
|
||||
path: '/clash-core',
|
||||
const mainMainSettingsClashRouteRoute =
|
||||
mainMainSettingsClashRouteRouteImport.update({
|
||||
id: '/clash',
|
||||
path: '/clash',
|
||||
getParentRoute: () => mainMainSettingsRouteRoute,
|
||||
} as any)
|
||||
const mainMainSettingsAboutRouteRoute =
|
||||
@@ -301,15 +266,10 @@ export interface FileRoutesByFullPath {
|
||||
'/main/': typeof mainMainIndexRoute
|
||||
'/main/profiles/inspect': typeof mainMainProfilesInspectRouteRoute
|
||||
'/main/settings/about': typeof mainMainSettingsAboutRouteRoute
|
||||
'/main/settings/clash-core': typeof mainMainSettingsClashCoreRouteRoute
|
||||
'/main/settings/clash-external-controll': typeof mainMainSettingsClashExternalControllRouteRoute
|
||||
'/main/settings/clash-filed': typeof mainMainSettingsClashFiledRouteRoute
|
||||
'/main/settings/clash-settings': typeof mainMainSettingsClashSettingsRouteRoute
|
||||
'/main/settings/debug-utils': typeof mainMainSettingsDebugUtilsRouteRoute
|
||||
'/main/settings/nyanpasu-config': typeof mainMainSettingsNyanpasuConfigRouteRoute
|
||||
'/main/settings/system-behavior': typeof mainMainSettingsSystemBehaviorRouteRoute
|
||||
'/main/settings/system-proxy': typeof mainMainSettingsSystemProxyRouteRoute
|
||||
'/main/settings/system-service': typeof mainMainSettingsSystemServiceRouteRoute
|
||||
'/main/settings/clash': typeof mainMainSettingsClashRouteRoute
|
||||
'/main/settings/debug': typeof mainMainSettingsDebugRouteRoute
|
||||
'/main/settings/nyanpasu': typeof mainMainSettingsNyanpasuRouteRoute
|
||||
'/main/settings/system': typeof mainMainSettingsSystemRouteRoute
|
||||
'/main/settings/user-interface': typeof mainMainSettingsUserInterfaceRouteRoute
|
||||
'/main/settings/web-ui': typeof mainMainSettingsWebUiRouteRoute
|
||||
'/main/profiles/': typeof mainMainProfilesIndexRoute
|
||||
@@ -338,15 +298,10 @@ export interface FileRoutesByTo {
|
||||
'/main': typeof mainMainIndexRoute
|
||||
'/main/profiles/inspect': typeof mainMainProfilesInspectRouteRoute
|
||||
'/main/settings/about': typeof mainMainSettingsAboutRouteRoute
|
||||
'/main/settings/clash-core': typeof mainMainSettingsClashCoreRouteRoute
|
||||
'/main/settings/clash-external-controll': typeof mainMainSettingsClashExternalControllRouteRoute
|
||||
'/main/settings/clash-filed': typeof mainMainSettingsClashFiledRouteRoute
|
||||
'/main/settings/clash-settings': typeof mainMainSettingsClashSettingsRouteRoute
|
||||
'/main/settings/debug-utils': typeof mainMainSettingsDebugUtilsRouteRoute
|
||||
'/main/settings/nyanpasu-config': typeof mainMainSettingsNyanpasuConfigRouteRoute
|
||||
'/main/settings/system-behavior': typeof mainMainSettingsSystemBehaviorRouteRoute
|
||||
'/main/settings/system-proxy': typeof mainMainSettingsSystemProxyRouteRoute
|
||||
'/main/settings/system-service': typeof mainMainSettingsSystemServiceRouteRoute
|
||||
'/main/settings/clash': typeof mainMainSettingsClashRouteRoute
|
||||
'/main/settings/debug': typeof mainMainSettingsDebugRouteRoute
|
||||
'/main/settings/nyanpasu': typeof mainMainSettingsNyanpasuRouteRoute
|
||||
'/main/settings/system': typeof mainMainSettingsSystemRouteRoute
|
||||
'/main/settings/user-interface': typeof mainMainSettingsUserInterfaceRouteRoute
|
||||
'/main/settings/web-ui': typeof mainMainSettingsWebUiRouteRoute
|
||||
'/main/profiles': typeof mainMainProfilesIndexRoute
|
||||
@@ -383,15 +338,10 @@ export interface FileRoutesById {
|
||||
'/(main)/main/': typeof mainMainIndexRoute
|
||||
'/(main)/main/profiles/inspect': typeof mainMainProfilesInspectRouteRoute
|
||||
'/(main)/main/settings/about': typeof mainMainSettingsAboutRouteRoute
|
||||
'/(main)/main/settings/clash-core': typeof mainMainSettingsClashCoreRouteRoute
|
||||
'/(main)/main/settings/clash-external-controll': typeof mainMainSettingsClashExternalControllRouteRoute
|
||||
'/(main)/main/settings/clash-filed': typeof mainMainSettingsClashFiledRouteRoute
|
||||
'/(main)/main/settings/clash-settings': typeof mainMainSettingsClashSettingsRouteRoute
|
||||
'/(main)/main/settings/debug-utils': typeof mainMainSettingsDebugUtilsRouteRoute
|
||||
'/(main)/main/settings/nyanpasu-config': typeof mainMainSettingsNyanpasuConfigRouteRoute
|
||||
'/(main)/main/settings/system-behavior': typeof mainMainSettingsSystemBehaviorRouteRoute
|
||||
'/(main)/main/settings/system-proxy': typeof mainMainSettingsSystemProxyRouteRoute
|
||||
'/(main)/main/settings/system-service': typeof mainMainSettingsSystemServiceRouteRoute
|
||||
'/(main)/main/settings/clash': typeof mainMainSettingsClashRouteRoute
|
||||
'/(main)/main/settings/debug': typeof mainMainSettingsDebugRouteRoute
|
||||
'/(main)/main/settings/nyanpasu': typeof mainMainSettingsNyanpasuRouteRoute
|
||||
'/(main)/main/settings/system': typeof mainMainSettingsSystemRouteRoute
|
||||
'/(main)/main/settings/user-interface': typeof mainMainSettingsUserInterfaceRouteRoute
|
||||
'/(main)/main/settings/web-ui': typeof mainMainSettingsWebUiRouteRoute
|
||||
'/(main)/main/profiles/': typeof mainMainProfilesIndexRoute
|
||||
@@ -427,15 +377,10 @@ export interface FileRouteTypes {
|
||||
| '/main/'
|
||||
| '/main/profiles/inspect'
|
||||
| '/main/settings/about'
|
||||
| '/main/settings/clash-core'
|
||||
| '/main/settings/clash-external-controll'
|
||||
| '/main/settings/clash-filed'
|
||||
| '/main/settings/clash-settings'
|
||||
| '/main/settings/debug-utils'
|
||||
| '/main/settings/nyanpasu-config'
|
||||
| '/main/settings/system-behavior'
|
||||
| '/main/settings/system-proxy'
|
||||
| '/main/settings/system-service'
|
||||
| '/main/settings/clash'
|
||||
| '/main/settings/debug'
|
||||
| '/main/settings/nyanpasu'
|
||||
| '/main/settings/system'
|
||||
| '/main/settings/user-interface'
|
||||
| '/main/settings/web-ui'
|
||||
| '/main/profiles/'
|
||||
@@ -464,15 +409,10 @@ export interface FileRouteTypes {
|
||||
| '/main'
|
||||
| '/main/profiles/inspect'
|
||||
| '/main/settings/about'
|
||||
| '/main/settings/clash-core'
|
||||
| '/main/settings/clash-external-controll'
|
||||
| '/main/settings/clash-filed'
|
||||
| '/main/settings/clash-settings'
|
||||
| '/main/settings/debug-utils'
|
||||
| '/main/settings/nyanpasu-config'
|
||||
| '/main/settings/system-behavior'
|
||||
| '/main/settings/system-proxy'
|
||||
| '/main/settings/system-service'
|
||||
| '/main/settings/clash'
|
||||
| '/main/settings/debug'
|
||||
| '/main/settings/nyanpasu'
|
||||
| '/main/settings/system'
|
||||
| '/main/settings/user-interface'
|
||||
| '/main/settings/web-ui'
|
||||
| '/main/profiles'
|
||||
@@ -508,15 +448,10 @@ export interface FileRouteTypes {
|
||||
| '/(main)/main/'
|
||||
| '/(main)/main/profiles/inspect'
|
||||
| '/(main)/main/settings/about'
|
||||
| '/(main)/main/settings/clash-core'
|
||||
| '/(main)/main/settings/clash-external-controll'
|
||||
| '/(main)/main/settings/clash-filed'
|
||||
| '/(main)/main/settings/clash-settings'
|
||||
| '/(main)/main/settings/debug-utils'
|
||||
| '/(main)/main/settings/nyanpasu-config'
|
||||
| '/(main)/main/settings/system-behavior'
|
||||
| '/(main)/main/settings/system-proxy'
|
||||
| '/(main)/main/settings/system-service'
|
||||
| '/(main)/main/settings/clash'
|
||||
| '/(main)/main/settings/debug'
|
||||
| '/(main)/main/settings/nyanpasu'
|
||||
| '/(main)/main/settings/system'
|
||||
| '/(main)/main/settings/user-interface'
|
||||
| '/(main)/main/settings/web-ui'
|
||||
| '/(main)/main/profiles/'
|
||||
@@ -732,67 +667,32 @@ declare module '@tanstack/react-router' {
|
||||
preLoaderRoute: typeof mainMainSettingsUserInterfaceRouteRouteImport
|
||||
parentRoute: typeof mainMainSettingsRouteRoute
|
||||
}
|
||||
'/(main)/main/settings/system-service': {
|
||||
id: '/(main)/main/settings/system-service'
|
||||
path: '/system-service'
|
||||
fullPath: '/main/settings/system-service'
|
||||
preLoaderRoute: typeof mainMainSettingsSystemServiceRouteRouteImport
|
||||
'/(main)/main/settings/system': {
|
||||
id: '/(main)/main/settings/system'
|
||||
path: '/system'
|
||||
fullPath: '/main/settings/system'
|
||||
preLoaderRoute: typeof mainMainSettingsSystemRouteRouteImport
|
||||
parentRoute: typeof mainMainSettingsRouteRoute
|
||||
}
|
||||
'/(main)/main/settings/system-proxy': {
|
||||
id: '/(main)/main/settings/system-proxy'
|
||||
path: '/system-proxy'
|
||||
fullPath: '/main/settings/system-proxy'
|
||||
preLoaderRoute: typeof mainMainSettingsSystemProxyRouteRouteImport
|
||||
'/(main)/main/settings/nyanpasu': {
|
||||
id: '/(main)/main/settings/nyanpasu'
|
||||
path: '/nyanpasu'
|
||||
fullPath: '/main/settings/nyanpasu'
|
||||
preLoaderRoute: typeof mainMainSettingsNyanpasuRouteRouteImport
|
||||
parentRoute: typeof mainMainSettingsRouteRoute
|
||||
}
|
||||
'/(main)/main/settings/system-behavior': {
|
||||
id: '/(main)/main/settings/system-behavior'
|
||||
path: '/system-behavior'
|
||||
fullPath: '/main/settings/system-behavior'
|
||||
preLoaderRoute: typeof mainMainSettingsSystemBehaviorRouteRouteImport
|
||||
'/(main)/main/settings/debug': {
|
||||
id: '/(main)/main/settings/debug'
|
||||
path: '/debug'
|
||||
fullPath: '/main/settings/debug'
|
||||
preLoaderRoute: typeof mainMainSettingsDebugRouteRouteImport
|
||||
parentRoute: typeof mainMainSettingsRouteRoute
|
||||
}
|
||||
'/(main)/main/settings/nyanpasu-config': {
|
||||
id: '/(main)/main/settings/nyanpasu-config'
|
||||
path: '/nyanpasu-config'
|
||||
fullPath: '/main/settings/nyanpasu-config'
|
||||
preLoaderRoute: typeof mainMainSettingsNyanpasuConfigRouteRouteImport
|
||||
parentRoute: typeof mainMainSettingsRouteRoute
|
||||
}
|
||||
'/(main)/main/settings/debug-utils': {
|
||||
id: '/(main)/main/settings/debug-utils'
|
||||
path: '/debug-utils'
|
||||
fullPath: '/main/settings/debug-utils'
|
||||
preLoaderRoute: typeof mainMainSettingsDebugUtilsRouteRouteImport
|
||||
parentRoute: typeof mainMainSettingsRouteRoute
|
||||
}
|
||||
'/(main)/main/settings/clash-settings': {
|
||||
id: '/(main)/main/settings/clash-settings'
|
||||
path: '/clash-settings'
|
||||
fullPath: '/main/settings/clash-settings'
|
||||
preLoaderRoute: typeof mainMainSettingsClashSettingsRouteRouteImport
|
||||
parentRoute: typeof mainMainSettingsRouteRoute
|
||||
}
|
||||
'/(main)/main/settings/clash-filed': {
|
||||
id: '/(main)/main/settings/clash-filed'
|
||||
path: '/clash-filed'
|
||||
fullPath: '/main/settings/clash-filed'
|
||||
preLoaderRoute: typeof mainMainSettingsClashFiledRouteRouteImport
|
||||
parentRoute: typeof mainMainSettingsRouteRoute
|
||||
}
|
||||
'/(main)/main/settings/clash-external-controll': {
|
||||
id: '/(main)/main/settings/clash-external-controll'
|
||||
path: '/clash-external-controll'
|
||||
fullPath: '/main/settings/clash-external-controll'
|
||||
preLoaderRoute: typeof mainMainSettingsClashExternalControllRouteRouteImport
|
||||
parentRoute: typeof mainMainSettingsRouteRoute
|
||||
}
|
||||
'/(main)/main/settings/clash-core': {
|
||||
id: '/(main)/main/settings/clash-core'
|
||||
path: '/clash-core'
|
||||
fullPath: '/main/settings/clash-core'
|
||||
preLoaderRoute: typeof mainMainSettingsClashCoreRouteRouteImport
|
||||
'/(main)/main/settings/clash': {
|
||||
id: '/(main)/main/settings/clash'
|
||||
path: '/clash'
|
||||
fullPath: '/main/settings/clash'
|
||||
preLoaderRoute: typeof mainMainSettingsClashRouteRouteImport
|
||||
parentRoute: typeof mainMainSettingsRouteRoute
|
||||
}
|
||||
'/(main)/main/settings/about': {
|
||||
@@ -906,15 +806,10 @@ const mainMainRulesRouteRouteWithChildren =
|
||||
|
||||
interface mainMainSettingsRouteRouteChildren {
|
||||
mainMainSettingsAboutRouteRoute: typeof mainMainSettingsAboutRouteRoute
|
||||
mainMainSettingsClashCoreRouteRoute: typeof mainMainSettingsClashCoreRouteRoute
|
||||
mainMainSettingsClashExternalControllRouteRoute: typeof mainMainSettingsClashExternalControllRouteRoute
|
||||
mainMainSettingsClashFiledRouteRoute: typeof mainMainSettingsClashFiledRouteRoute
|
||||
mainMainSettingsClashSettingsRouteRoute: typeof mainMainSettingsClashSettingsRouteRoute
|
||||
mainMainSettingsDebugUtilsRouteRoute: typeof mainMainSettingsDebugUtilsRouteRoute
|
||||
mainMainSettingsNyanpasuConfigRouteRoute: typeof mainMainSettingsNyanpasuConfigRouteRoute
|
||||
mainMainSettingsSystemBehaviorRouteRoute: typeof mainMainSettingsSystemBehaviorRouteRoute
|
||||
mainMainSettingsSystemProxyRouteRoute: typeof mainMainSettingsSystemProxyRouteRoute
|
||||
mainMainSettingsSystemServiceRouteRoute: typeof mainMainSettingsSystemServiceRouteRoute
|
||||
mainMainSettingsClashRouteRoute: typeof mainMainSettingsClashRouteRoute
|
||||
mainMainSettingsDebugRouteRoute: typeof mainMainSettingsDebugRouteRoute
|
||||
mainMainSettingsNyanpasuRouteRoute: typeof mainMainSettingsNyanpasuRouteRoute
|
||||
mainMainSettingsSystemRouteRoute: typeof mainMainSettingsSystemRouteRoute
|
||||
mainMainSettingsUserInterfaceRouteRoute: typeof mainMainSettingsUserInterfaceRouteRoute
|
||||
mainMainSettingsWebUiRouteRoute: typeof mainMainSettingsWebUiRouteRoute
|
||||
mainMainSettingsIndexRoute: typeof mainMainSettingsIndexRoute
|
||||
@@ -922,20 +817,10 @@ interface mainMainSettingsRouteRouteChildren {
|
||||
|
||||
const mainMainSettingsRouteRouteChildren: mainMainSettingsRouteRouteChildren = {
|
||||
mainMainSettingsAboutRouteRoute: mainMainSettingsAboutRouteRoute,
|
||||
mainMainSettingsClashCoreRouteRoute: mainMainSettingsClashCoreRouteRoute,
|
||||
mainMainSettingsClashExternalControllRouteRoute:
|
||||
mainMainSettingsClashExternalControllRouteRoute,
|
||||
mainMainSettingsClashFiledRouteRoute: mainMainSettingsClashFiledRouteRoute,
|
||||
mainMainSettingsClashSettingsRouteRoute:
|
||||
mainMainSettingsClashSettingsRouteRoute,
|
||||
mainMainSettingsDebugUtilsRouteRoute: mainMainSettingsDebugUtilsRouteRoute,
|
||||
mainMainSettingsNyanpasuConfigRouteRoute:
|
||||
mainMainSettingsNyanpasuConfigRouteRoute,
|
||||
mainMainSettingsSystemBehaviorRouteRoute:
|
||||
mainMainSettingsSystemBehaviorRouteRoute,
|
||||
mainMainSettingsSystemProxyRouteRoute: mainMainSettingsSystemProxyRouteRoute,
|
||||
mainMainSettingsSystemServiceRouteRoute:
|
||||
mainMainSettingsSystemServiceRouteRoute,
|
||||
mainMainSettingsClashRouteRoute: mainMainSettingsClashRouteRoute,
|
||||
mainMainSettingsDebugRouteRoute: mainMainSettingsDebugRouteRoute,
|
||||
mainMainSettingsNyanpasuRouteRoute: mainMainSettingsNyanpasuRouteRoute,
|
||||
mainMainSettingsSystemRouteRoute: mainMainSettingsSystemRouteRoute,
|
||||
mainMainSettingsUserInterfaceRouteRoute:
|
||||
mainMainSettingsUserInterfaceRouteRoute,
|
||||
mainMainSettingsWebUiRouteRoute: mainMainSettingsWebUiRouteRoute,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// oxlint-disable typescript/no-explicit-any
|
||||
// Deep copy and change all keys to lowercase
|
||||
type TData = Record<string, any>
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// oxlint-disable typescript/no-explicit-any
|
||||
import { includes, isArray, isObject, isString, some } from 'lodash-es'
|
||||
import { EnvInfos } from '@nyanpasu/interface'
|
||||
import { EnvInfo } from '@nyanpasu/interface'
|
||||
|
||||
/**
|
||||
* classNames filter out falsy values and join the rest with a space
|
||||
* @param classes - array of classes
|
||||
* @returns string of classes
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function classNames(...classes: any[]) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
@@ -33,7 +33,7 @@ export function formatError(err: unknown): string {
|
||||
return `Error: ${err instanceof Error ? err.message : String(err)}`
|
||||
}
|
||||
|
||||
export function formatEnvInfos(envs: EnvInfos) {
|
||||
export function formatEnvInfos(envs: EnvInfo) {
|
||||
let result = '----------- System -----------\n'
|
||||
result += `OS: ${envs.os}\n`
|
||||
result += `Arch: ${envs.arch}\n`
|
||||
@@ -50,9 +50,11 @@ export function formatEnvInfos(envs: EnvInfos) {
|
||||
for (const k of Object.keys(envs.build_info) as string[]) {
|
||||
const key = k
|
||||
.split('_')
|
||||
.map((v) => v.charAt(0).toUpperCase() + v.slice(1))
|
||||
.map((v: string) => v.charAt(0).toUpperCase() + v.slice(1))
|
||||
.join(' ')
|
||||
result += `${key}: ${envs.build_info[k]}\n`
|
||||
// Fix linter error: explicitly type k as keyof typeof envs.build_info
|
||||
result += `${key}: ${envs.build_info[k as keyof typeof envs.build_info]}\n`
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -156,5 +156,6 @@ export default defineConfig(({ command, mode }) => {
|
||||
html: {},
|
||||
} satisfies UserConfig
|
||||
// fucking vite why embedded their own sass types definition????
|
||||
// oxlint-disable-next-line typescript/no-explicit-any
|
||||
return config as any as UserConfig
|
||||
})
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
"@radix-ui/react-scroll-area": "1.2.10",
|
||||
"@tauri-apps/api": "2.8.0",
|
||||
"@types/d3": "7.4.3",
|
||||
"@types/react": "19.2.11",
|
||||
"@types/react": "19.2.13",
|
||||
"@vitejs/plugin-react": "5.1.3",
|
||||
"ahooks": "3.9.6",
|
||||
"d3": "7.9.0",
|
||||
"framer-motion": "12.31.0",
|
||||
"framer-motion": "12.33.0",
|
||||
"react": "19.2.4",
|
||||
"react-dom": "19.2.4",
|
||||
"react-error-boundary": "6.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
// oxlint-disable typescript/no-explicit-any
|
||||
import * as d3 from 'd3'
|
||||
import { interpolatePath } from 'd3-interpolate-path'
|
||||
import { CSSProperties, FC, useEffect, useMemo, useRef } from 'react'
|
||||
|
||||
@@ -118,7 +118,7 @@ export const useContainerBreakpointValue = <T>(
|
||||
): T => {
|
||||
const currentBreakpoint = useContainerBreakpoint(containerRef)
|
||||
|
||||
const calculateValue = (): T => {
|
||||
const memoizedValue = useMemo(() => {
|
||||
const value = values[currentBreakpoint]
|
||||
|
||||
if (value !== undefined) {
|
||||
@@ -136,12 +136,7 @@ export const useContainerBreakpointValue = <T>(
|
||||
}
|
||||
|
||||
return defaultValue ?? (values[breakpointsOrder[0]] as T)
|
||||
}
|
||||
|
||||
const memoizedValue = useMemo(
|
||||
() => calculateValue(),
|
||||
[currentBreakpoint, values, defaultValue],
|
||||
)
|
||||
}, [currentBreakpoint, values, defaultValue])
|
||||
|
||||
return memoizedValue
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"manifest_version": 1,
|
||||
"latest": {
|
||||
"mihomo": "v1.19.19",
|
||||
"mihomo_alpha": "alpha-558b384",
|
||||
"mihomo_alpha": "alpha-86257fc",
|
||||
"clash_rs": "v0.9.4",
|
||||
"clash_premium": "2023-09-05-gdcc8d87",
|
||||
"clash_rs_alpha": "0.9.4-alpha+sha.5675004"
|
||||
@@ -69,5 +69,5 @@
|
||||
"linux-armv7hf": "clash-armv7-unknown-linux-gnueabihf"
|
||||
}
|
||||
},
|
||||
"updated_at": "2026-02-05T00:18:37.438Z"
|
||||
"updated_at": "2026-02-05T22:41:40.244Z"
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"web:visualize": "pnpm --filter=@nyanpasu/nyanpasu bundle:visualize",
|
||||
"lint": "run-s lint:*",
|
||||
"lint:prettier": "prettier --check .",
|
||||
"lint:eslint": "eslint --cache .",
|
||||
"lint:oxlint": "oxlint .",
|
||||
"lint:styles": "stylelint --cache --allow-empty-input \"**/*.{css,scss}\"",
|
||||
"lint:ts": "run-s lint:ts:*",
|
||||
"lint:ts:scripts": "tsc --noEmit --project ./scripts/tsconfig.json",
|
||||
@@ -35,7 +35,7 @@
|
||||
"fmt": "run-p fmt:*",
|
||||
"fmt:backend": "cargo fmt --manifest-path ./backend/Cargo.toml --all",
|
||||
"fmt:prettier": "prettier --write .",
|
||||
"fmt:eslint": "eslint --cache --fix . eslint.config.js",
|
||||
"fmt:oxlint": "oxlint --fix .",
|
||||
"updater": "tsx scripts/updater.ts",
|
||||
"updater:nightly": "tsx scripts/updater-nightly.ts",
|
||||
"send-notify": "tsx scripts/telegram-notify.ts",
|
||||
@@ -60,35 +60,20 @@
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "20.4.1",
|
||||
"@commitlint/config-conventional": "20.4.1",
|
||||
"@eslint/compat": "1.4.1",
|
||||
"@eslint/eslintrc": "3.3.3",
|
||||
"@ianvs/prettier-plugin-sort-imports": "4.7.1",
|
||||
"@tauri-apps/cli": "2.8.4",
|
||||
"@types/fs-extra": "11.0.4",
|
||||
"@types/lodash-es": "4.17.12",
|
||||
"@types/node": "24.10.10",
|
||||
"@typescript-eslint/eslint-plugin": "8.54.0",
|
||||
"@typescript-eslint/parser": "8.54.0",
|
||||
"@types/node": "24.10.11",
|
||||
"autoprefixer": "10.4.24",
|
||||
"conventional-changelog-conventionalcommits": "9.1.0",
|
||||
"cross-env": "10.1.0",
|
||||
"dedent": "1.7.1",
|
||||
"eslint": "9.39.2",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-import-resolver-alias": "1.1.2",
|
||||
"eslint-plugin-html": "8.1.4",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"eslint-plugin-n": "17.23.2",
|
||||
"eslint-plugin-prettier": "5.5.5",
|
||||
"eslint-plugin-promise": "7.2.1",
|
||||
"eslint-plugin-react": "7.37.5",
|
||||
"eslint-plugin-react-compiler": "19.1.0-rc.2",
|
||||
"eslint-plugin-react-hooks": "7.0.1",
|
||||
"globals": "16.5.0",
|
||||
"knip": "5.83.0",
|
||||
"lint-staged": "16.2.7",
|
||||
"neostandard": "0.12.2",
|
||||
"npm-run-all2": "8.0.4",
|
||||
"oxlint": "^1.43.0",
|
||||
"postcss": "8.5.6",
|
||||
"postcss-html": "1.8.1",
|
||||
"postcss-import": "16.1.1",
|
||||
@@ -106,8 +91,7 @@
|
||||
"stylelint-scss": "6.14.0",
|
||||
"tailwindcss": "4.1.18",
|
||||
"tsx": "4.21.0",
|
||||
"typescript": "5.9.3",
|
||||
"typescript-eslint": "8.54.0"
|
||||
"typescript": "5.9.3"
|
||||
},
|
||||
"packageManager": "pnpm@10.28.2",
|
||||
"engines": {
|
||||
|
||||
Generated
+552
-3946
File diff suppressed because it is too large
Load Diff
@@ -9,7 +9,7 @@
|
||||
"figlet": "1.10.0",
|
||||
"filesize": "11.0.13",
|
||||
"p-retry": "7.1.1",
|
||||
"semver": "7.7.3",
|
||||
"semver": "7.7.4",
|
||||
"zod": "4.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
--- a/include/linux/if_bridge.h
|
||||
+++ b/include/linux/if_bridge.h
|
||||
@@ -71,6 +71,9 @@ void brioctl_set(int (*hook)(struct net
|
||||
@@ -68,6 +68,9 @@ struct net_bridge;
|
||||
void brioctl_set(int (*hook)(struct net *net, unsigned int cmd,
|
||||
void __user *uarg));
|
||||
int br_ioctl_call(struct net *net, struct net_bridge *br, unsigned int cmd,
|
||||
struct ifreq *ifr, void __user *uarg);
|
||||
int br_ioctl_call(struct net *net, unsigned int cmd, void __user *uarg);
|
||||
+/* extern void br_dev_update_stats(struct net_device *dev,
|
||||
+ struct rtnl_link_stats64 *nlstats); */
|
||||
+extern bool br_is_hairpin_enabled(struct net_device *dev);
|
||||
|
||||
extern void br_dev_update_stats(struct net_device *dev,
|
||||
struct rtnl_link_stats64 *nlstats);
|
||||
@@ -216,4 +219,42 @@ static inline clock_t br_get_ageing_time
|
||||
#if IS_ENABLED(CONFIG_BRIDGE) && IS_ENABLED(CONFIG_BRIDGE_IGMP_SNOOPING)
|
||||
int br_multicast_list_adjacent(struct net_device *dev,
|
||||
@@ -210,4 +213,42 @@ static inline clock_t br_get_ageing_time(const struct net_device *br_dev)
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
return false;
|
||||
--- a/include/linux/netdevice.h
|
||||
+++ b/include/linux/netdevice.h
|
||||
@@ -2907,6 +2907,10 @@ enum netdev_cmd {
|
||||
@@ -2897,6 +2897,10 @@ enum netdev_cmd {
|
||||
NETDEV_OFFLOAD_XSTATS_REPORT_USED,
|
||||
NETDEV_OFFLOAD_XSTATS_REPORT_DELTA,
|
||||
NETDEV_XDP_FEAT_CHANGE,
|
||||
@@ -134,12 +134,6 @@
|
||||
struct neigh_seq_state {
|
||||
struct seq_net_private p;
|
||||
struct neigh_table *tbl;
|
||||
@@ -601,4 +613,5 @@ static inline void neigh_update_is_route
|
||||
*notify = 1;
|
||||
}
|
||||
}
|
||||
+
|
||||
#endif
|
||||
--- a/include/net/route.h
|
||||
+++ b/include/net/route.h
|
||||
@@ -246,6 +246,11 @@ struct rtable *rt_dst_alloc(struct net_d
|
||||
@@ -156,7 +150,7 @@
|
||||
void fib_del_ifaddr(struct in_ifaddr *, struct in_ifaddr *);
|
||||
--- a/net/bridge/br_private.h
|
||||
+++ b/net/bridge/br_private.h
|
||||
@@ -2304,4 +2304,6 @@ void br_do_suppress_nd(struct sk_buff *s
|
||||
@@ -2311,4 +2311,6 @@ void br_do_suppress_nd(struct sk_buff *skb, struct net_bridge *br,
|
||||
u16 vid, struct net_bridge_port *p, struct nd_msg *msg);
|
||||
struct nd_msg *br_is_nd_neigh_msg(struct sk_buff *skb, struct nd_msg *m);
|
||||
bool br_is_neigh_suppress_enabled(const struct net_bridge_port *p, u16 vid);
|
||||
@@ -307,7 +301,7 @@
|
||||
spin_unlock_bh(&br->hash_lock);
|
||||
}
|
||||
}
|
||||
@@ -928,6 +985,12 @@ void br_fdb_update(struct net_bridge *br
|
||||
@@ -932,6 +989,12 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
|
||||
*/
|
||||
if (unlikely(test_bit(BR_FDB_LOCKED, &fdb->flags)))
|
||||
clear_bit(BR_FDB_LOCKED, &fdb->flags);
|
||||
@@ -320,7 +314,7 @@
|
||||
}
|
||||
|
||||
if (unlikely(test_bit(BR_FDB_ADDED_BY_USER, &flags))) {
|
||||
@@ -955,6 +1018,64 @@ void br_fdb_update(struct net_bridge *br
|
||||
@@ -959,6 +1022,64 @@ void br_fdb_update(struct net_bridge *br, struct net_bridge_port *source,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -400,7 +394,7 @@
|
||||
/*
|
||||
* Determine initial path cost based on speed.
|
||||
* using recommendations from 802.1d standard
|
||||
@@ -697,6 +703,8 @@ int br_add_if(struct net_bridge *br, str
|
||||
@@ -698,6 +704,8 @@ int br_add_if(struct net_bridge *br, struct net_device *dev,
|
||||
|
||||
kobject_uevent(&p->kobj, KOBJ_ADD);
|
||||
|
||||
@@ -409,7 +403,7 @@
|
||||
return 0;
|
||||
|
||||
err6:
|
||||
@@ -732,6 +740,8 @@ int br_del_if(struct net_bridge *br, str
|
||||
@@ -733,6 +741,8 @@ int br_del_if(struct net_bridge *br, struct net_device *dev)
|
||||
if (!p || p->br != br)
|
||||
return -EINVAL;
|
||||
|
||||
@@ -418,7 +412,7 @@
|
||||
/* Since more than one interface can be attached to a bridge,
|
||||
* there still maybe an alternate path for netconsole to use;
|
||||
* therefore there is no reason for a NETDEV_RELEASE event.
|
||||
@@ -797,3 +807,97 @@ bool br_port_flag_is_set(const struct ne
|
||||
@@ -776,3 +786,97 @@ bool br_port_flag_is_set(const struct net_device *dev, unsigned long flag)
|
||||
return p->flags & flag;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(br_port_flag_is_set);
|
||||
@@ -620,7 +614,7 @@
|
||||
fib_release_info(fa_to_delete->fa_info);
|
||||
alias_free_mem_rcu(fa_to_delete);
|
||||
return 0;
|
||||
@@ -2386,6 +2395,20 @@ void __init fib_trie_init(void)
|
||||
@@ -2387,6 +2396,20 @@ void __init fib_trie_init(void)
|
||||
0, SLAB_PANIC | SLAB_ACCOUNT, NULL);
|
||||
}
|
||||
|
||||
@@ -643,7 +637,7 @@
|
||||
struct fib_table *tb;
|
||||
--- a/net/ipv6/ndisc.c
|
||||
+++ b/net/ipv6/ndisc.c
|
||||
@@ -670,6 +670,7 @@ void ndisc_send_ns(struct net_device *de
|
||||
@@ -672,6 +672,7 @@ void ndisc_send_ns(struct net_device *dev, const struct in6_addr *solicit,
|
||||
if (skb)
|
||||
ndisc_send_skb(skb, daddr, saddr);
|
||||
}
|
||||
@@ -653,7 +647,7 @@
|
||||
const struct in6_addr *daddr)
|
||||
--- a/net/ipv6/route.c
|
||||
+++ b/net/ipv6/route.c
|
||||
@@ -196,6 +196,9 @@ static void rt6_uncached_list_flush_dev(
|
||||
@@ -194,6 +194,9 @@ static void rt6_uncached_list_flush_dev(struct net_device *dev)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -663,7 +657,7 @@
|
||||
static inline const void *choose_neigh_daddr(const struct in6_addr *p,
|
||||
struct sk_buff *skb,
|
||||
const void *daddr)
|
||||
@@ -3917,6 +3920,10 @@ int ip6_route_add(struct fib6_config *cf
|
||||
@@ -3909,6 +3912,10 @@ int ip6_route_add(struct fib6_config *cfg, gfp_t gfp_flags,
|
||||
return PTR_ERR(rt);
|
||||
|
||||
err = __ip6_ins_rt(rt, &cfg->fc_nlinfo, extack);
|
||||
@@ -674,7 +668,7 @@
|
||||
fib6_info_release(rt);
|
||||
|
||||
return err;
|
||||
@@ -3938,6 +3945,9 @@ static int __ip6_del_rt(struct fib6_info
|
||||
@@ -3930,6 +3937,9 @@ static int __ip6_del_rt(struct fib6_info *rt, struct nl_info *info)
|
||||
err = fib6_del(rt, info);
|
||||
spin_unlock_bh(&table->tb6_lock);
|
||||
|
||||
@@ -684,7 +678,7 @@
|
||||
out:
|
||||
fib6_info_release(rt);
|
||||
return err;
|
||||
@@ -6409,6 +6419,20 @@ static int ip6_route_dev_notify(struct n
|
||||
@@ -6390,6 +6400,20 @@ static int ip6_route_dev_notify(struct notifier_block *this,
|
||||
return NOTIFY_OK;
|
||||
}
|
||||
|
||||
@@ -707,7 +701,7 @@
|
||||
*/
|
||||
--- a/net/core/dev.c
|
||||
+++ b/net/core/dev.c
|
||||
@@ -1768,6 +1768,7 @@ const char *netdev_cmd_to_name(enum netd
|
||||
@@ -1750,6 +1750,7 @@ const char *netdev_cmd_to_name(enum netdev_cmd cmd)
|
||||
N(PRE_CHANGEADDR) N(OFFLOAD_XSTATS_ENABLE) N(OFFLOAD_XSTATS_DISABLE)
|
||||
N(OFFLOAD_XSTATS_REPORT_USED) N(OFFLOAD_XSTATS_REPORT_DELTA)
|
||||
N(XDP_FEAT_CHANGE)
|
||||
@@ -785,12 +779,12 @@
|
||||
#endif /* _UAPI_LINUX_IN_H */
|
||||
--- a/net/netfilter/nf_conntrack_ecache.c
|
||||
+++ b/net/netfilter/nf_conntrack_ecache.c
|
||||
@@ -365,7 +365,7 @@ int nf_conntrack_register_notifier(struc
|
||||
@@ -266,7 +266,7 @@ void nf_conntrack_register_notifier(struct net *net,
|
||||
mutex_lock(&nf_ct_ecache_mutex);
|
||||
notify = rcu_dereference_protected(net->ct.nf_conntrack_event_cb,
|
||||
lockdep_is_held(&nf_ct_ecache_mutex));
|
||||
- WARN_ON_ONCE(notify);
|
||||
+ /* WARN_ON_ONCE(notify); */
|
||||
if (notify != NULL) {
|
||||
ret = -EBUSY;
|
||||
goto out_unlock;
|
||||
rcu_assign_pointer(net->ct.nf_conntrack_event_cb, new);
|
||||
mutex_unlock(&nf_ct_ecache_mutex);
|
||||
}
|
||||
|
||||
+13
-1
@@ -180,7 +180,7 @@ client-mac-arm64:
|
||||
|
||||
# Build windows clients.
|
||||
.PHONY: client-windows
|
||||
client-windows: client-windows-x86 client-windows-amd64
|
||||
client-windows: client-windows-x86 client-windows-amd64 client-windows-arm64
|
||||
|
||||
# Build windows x86 client.
|
||||
.PHONY: client-windows-x86
|
||||
@@ -206,6 +206,18 @@ client-windows-amd64:
|
||||
mv mieru_${VERSION}_windows_amd64.zip ../../
|
||||
mv mieru_${VERSION}_windows_amd64.zip.sha256.txt ../../
|
||||
|
||||
# Build windows arm64 client.
|
||||
.PHONY: client-windows-arm64
|
||||
client-windows-arm64:
|
||||
mkdir -p release/windows/arm64
|
||||
env GOOS=windows GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o release/windows/arm64/mieru.exe cmd/mieru/mieru.go
|
||||
cd release/windows/arm64
|
||||
sha256sum mieru.exe > mieru_${VERSION}_windows_arm64.exe.sha256.txt
|
||||
zip -r mieru_${VERSION}_windows_arm64.zip mieru.exe
|
||||
sha256sum mieru_${VERSION}_windows_arm64.zip > mieru_${VERSION}_windows_arm64.zip.sha256.txt
|
||||
mv mieru_${VERSION}_windows_arm64.zip ../../
|
||||
mv mieru_${VERSION}_windows_arm64.zip.sha256.txt ../../
|
||||
|
||||
# Build linux servers.
|
||||
.PHONY: server-linux
|
||||
server-linux: server-linux-amd64 server-linux-arm64
|
||||
|
||||
@@ -29,20 +29,19 @@ import (
|
||||
"github.com/enfein/mieru/v3/pkg/stderror"
|
||||
)
|
||||
|
||||
// ValidateClientConfigSingleProfile validates
|
||||
// a single client config profile.
|
||||
// ValidateClientConfigSingleProfile validates a single client config profile.
|
||||
//
|
||||
// It validates
|
||||
// 1. profile name is not empty
|
||||
// 2. user name is not empty
|
||||
// 3. user has either a password or a hashed password
|
||||
// 4. user has no quota
|
||||
// 5. it has at least 1 server, and for each server
|
||||
// 5.1. the server has either IP address or domain name
|
||||
// 5.2. if set, server's IP address is parsable
|
||||
// 5.3. the server has at least 1 port binding, and all port bindings are valid
|
||||
// 6. if set, MTU is valid
|
||||
// 7. if set, traffic pattern is valid
|
||||
// It validates:
|
||||
// - profile name is not empty
|
||||
// - user name is not empty
|
||||
// - user has either a password or a hashed password
|
||||
// - user has no quota
|
||||
// - it has at least 1 server, and for each server:
|
||||
// 1. the server has either IP address or domain name
|
||||
// 2. if set, server's IP address is parsable
|
||||
// 3. the server has at least 1 port binding, and all port bindings are valid
|
||||
// - if set, MTU is valid
|
||||
// - if set, traffic pattern is valid
|
||||
func ValidateClientConfigSingleProfile(profile *pb.ClientProfile) error {
|
||||
name := profile.GetProfileName()
|
||||
if name == "" {
|
||||
|
||||
@@ -21,15 +21,14 @@ import (
|
||||
pb "github.com/enfein/mieru/v3/pkg/appctl/appctlpb"
|
||||
)
|
||||
|
||||
// ValidateServerConfigSingleUser validates
|
||||
// a single server config user.
|
||||
// ValidateServerConfigSingleUser validates a single server config user.
|
||||
//
|
||||
// It validates
|
||||
// 1. user name is not empty
|
||||
// 2. user has either a password or a hashed password
|
||||
// 3. for each quota
|
||||
// 3.1. number of days is valid
|
||||
// 3.2. traffic volume in megabyte is valid
|
||||
// It validates:
|
||||
// - user name is not empty
|
||||
// - user has either a password or a hashed password
|
||||
// - for each quota:
|
||||
// 1. number of days is valid
|
||||
// 2. traffic volume in megabyte is valid
|
||||
func ValidateServerConfigSingleUser(user *pb.User) error {
|
||||
if user.GetName() == "" {
|
||||
return fmt.Errorf("user name is not set")
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user