remove apps to another repository
This commit is contained in:
@@ -1,7 +1,3 @@
|
|||||||
/target
|
/target
|
||||||
things.txt
|
things.txt
|
||||||
/gen
|
/gen
|
||||||
/app/node_modules
|
|
||||||
/app/src-tauri/gen
|
|
||||||
/app/src-tauri/icons
|
|
||||||
/app/dist
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://ui.shadcn.com/schema.json",
|
|
||||||
"style": "radix-maia",
|
|
||||||
"rsc": false,
|
|
||||||
"tsx": true,
|
|
||||||
"tailwind": {
|
|
||||||
"config": "",
|
|
||||||
"css": "style.css",
|
|
||||||
"baseColor": "mist",
|
|
||||||
"cssVariables": true,
|
|
||||||
"prefix": ""
|
|
||||||
},
|
|
||||||
"iconLibrary": "tabler",
|
|
||||||
"rtl": false,
|
|
||||||
"aliases": {
|
|
||||||
"components": "@/components",
|
|
||||||
"utils": "@/lib/utils",
|
|
||||||
"ui": "@/components/ui",
|
|
||||||
"lib": "@/lib",
|
|
||||||
"hooks": "@/hooks"
|
|
||||||
},
|
|
||||||
"menuColor": "default",
|
|
||||||
"menuAccent": "subtle",
|
|
||||||
"registries": {}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html lang="ru">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<link href="/src/style.css" rel="stylesheet" />
|
|
||||||
<title>Netrunner VPN</title>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="root"></div>
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "netrunner-app",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"dev": "vite",
|
|
||||||
"build": "tsc && vite build",
|
|
||||||
"preview": "vite preview",
|
|
||||||
"tauri": "tauri"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@fontsource-variable/figtree": "^5.2.10",
|
|
||||||
"@fontsource-variable/nunito-sans": "^5.2.7",
|
|
||||||
"@hugeicons/core-free-icons": "^4.0.0",
|
|
||||||
"@hugeicons/react": "^1.1.5",
|
|
||||||
"@tabler/icons-react": "^3.40.0",
|
|
||||||
"@tauri-apps/api": "^2.0.0",
|
|
||||||
"@tauri-apps/plugin-shell": "^2.0.0",
|
|
||||||
"class-variance-authority": "^0.7.1",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"framer-motion": "^12.35.2",
|
|
||||||
"i18next": "^25.8.16",
|
|
||||||
"i18next-browser-languagedetector": "^8.2.1",
|
|
||||||
"lucide-react": "^0.577.0",
|
|
||||||
"radix-ui": "^1.4.3",
|
|
||||||
"react": "^19.0.0",
|
|
||||||
"react-country-flag": "^3.1.0",
|
|
||||||
"react-dom": "^19.0.0",
|
|
||||||
"react-i18next": "^16.5.6",
|
|
||||||
"react-router-dom": "^7.1.5",
|
|
||||||
"shadcn": "^4.0.2",
|
|
||||||
"tailwind-merge": "^3.5.0",
|
|
||||||
"tw-animate-css": "^1.4.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@tailwindcss/vite": "^4.2.1",
|
|
||||||
"@tauri-apps/cli": "^2.0.0",
|
|
||||||
"@types/node": "^25.3.5",
|
|
||||||
"@types/react": "^19.0.0",
|
|
||||||
"@types/react-dom": "^19.0.0",
|
|
||||||
"@vitejs/plugin-react": "^5.1.4",
|
|
||||||
"autoprefixer": "^10.4.27",
|
|
||||||
"postcss": "^8.5.8",
|
|
||||||
"tailwindcss": "^4.2.1",
|
|
||||||
"typescript": "^5.0.0",
|
|
||||||
"vite": "^7.3.1"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Generated
-5979
File diff suppressed because it is too large
Load Diff
@@ -1,22 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "netrunner-app"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2021"
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
|
||||||
name = "netrunner_app"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
tauri-build = { version = "2.0.0", features = [] }
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
jni = "0.21"
|
|
||||||
netrunner-client = { path = "../../client" }
|
|
||||||
netrunner-core = { path = "../../core" }
|
|
||||||
tauri = { version = "2.0.0", features = [] }
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
|
||||||
ndk-context = "0.1"
|
|
||||||
log = "0.4"
|
|
||||||
android_logger = "0.14"
|
|
||||||
tauri-plugin-vpn = { path = "./src/plugins/vpn-plugin" }
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 20 KiB |
@@ -1,3 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
tauri_build::build();
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "../gen/schemas/desktop-schema.json",
|
|
||||||
"identifier": "default",
|
|
||||||
"description": "Capability for the main window",
|
|
||||||
"windows": ["main"],
|
|
||||||
"permissions": [
|
|
||||||
"core:default",
|
|
||||||
{
|
|
||||||
"identifier": "core:app:default",
|
|
||||||
"allow": ["start_vpn_android"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
mod core;
|
|
||||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
||||||
pub fn run() {
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
android_logger::init_once(
|
|
||||||
android_logger::Config::default()
|
|
||||||
.with_max_level(log::LevelFilter::Debug)
|
|
||||||
.with_tag("NetrunnerRust"),
|
|
||||||
);
|
|
||||||
tauri::Builder::default()
|
|
||||||
.run(tauri::generate_context!())
|
|
||||||
.expect("error while running tauri application");
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
fn main() {
|
|
||||||
netrunner_app::run();
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/.vs
|
|
||||||
.DS_Store
|
|
||||||
.Thumbs.db
|
|
||||||
*.sublime*
|
|
||||||
.idea/
|
|
||||||
debug.log
|
|
||||||
package-lock.json
|
|
||||||
.vscode/settings.json
|
|
||||||
yarn.lock
|
|
||||||
|
|
||||||
/.tauri
|
|
||||||
/target
|
|
||||||
Cargo.lock
|
|
||||||
node_modules/
|
|
||||||
|
|
||||||
dist-js
|
|
||||||
dist
|
|
||||||
|
|
||||||
|
|
||||||
/android/.tauri
|
|
||||||
/android/.gradle
|
|
||||||
/android/build
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "tauri-plugin-vpn"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = [ "You" ]
|
|
||||||
description = ""
|
|
||||||
edition = "2021"
|
|
||||||
rust-version = "1.77.2"
|
|
||||||
exclude = ["/examples", "/dist-js", "/guest-js", "/node_modules"]
|
|
||||||
links = "tauri-plugin-vpn"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
tauri = { version = "2.10.3" }
|
|
||||||
serde = "1.0"
|
|
||||||
thiserror = "2"
|
|
||||||
|
|
||||||
[build-dependencies]
|
|
||||||
tauri-plugin = { version = "2.5.4", features = ["build"] }
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
# Tauri Plugin vpn
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id("com.android.library")
|
|
||||||
id("org.jetbrains.kotlin.android")
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "app.netrunner.vpn"
|
|
||||||
compileSdk = 36
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = 21
|
|
||||||
}
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = "17"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation("androidx.core:core-ktx:1.9.0")
|
|
||||||
implementation("androidx.appcompat:appcompat:1.6.0")
|
|
||||||
implementation(project(":tauri-android"))
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
include ':tauri-android'
|
|
||||||
project(':tauri-android').projectDir = new File('./.tauri/tauri-api')
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="app.netrunner.vpn">
|
|
||||||
|
|
||||||
<application>
|
|
||||||
<service
|
|
||||||
android:name=".ExampleVpnService"
|
|
||||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.net.VpnService"/>
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
</application>
|
|
||||||
</manifest>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package app.netrunner.vpn;
|
|
||||||
import android.net.VpnService;
|
|
||||||
|
|
||||||
class VpnEngine : VpnService() {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
const COMMANDS: &[&str] = &["ping"];
|
|
||||||
|
|
||||||
fn main() {
|
|
||||||
tauri_plugin::Builder::new(COMMANDS)
|
|
||||||
.android_path("android")
|
|
||||||
.ios_path("ios")
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import { invoke } from '@tauri-apps/api/core'
|
|
||||||
|
|
||||||
export async function ping(value: string): Promise<string | null> {
|
|
||||||
return await invoke<{value?: string}>('plugin:vpn|ping', {
|
|
||||||
payload: {
|
|
||||||
value,
|
|
||||||
},
|
|
||||||
}).then((r) => (r.value ? r.value : null));
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "tauri-plugin-vpn-api",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"author": "You",
|
|
||||||
"description": "",
|
|
||||||
"type": "module",
|
|
||||||
"types": "./dist-js/index.d.ts",
|
|
||||||
"main": "./dist-js/index.cjs",
|
|
||||||
"module": "./dist-js/index.js",
|
|
||||||
"exports": {
|
|
||||||
"types": "./dist-js/index.d.ts",
|
|
||||||
"import": "./dist-js/index.js",
|
|
||||||
"require": "./dist-js/index.cjs"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"dist-js",
|
|
||||||
"README.md"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"build": "rollup -c",
|
|
||||||
"prepublishOnly": "pnpm build",
|
|
||||||
"pretest": "pnpm build"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@tauri-apps/api": "^2.0.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@rollup/plugin-typescript": "^12.0.0",
|
|
||||||
"rollup": "^4.9.6",
|
|
||||||
"typescript": "^5.3.3",
|
|
||||||
"tslib": "^2.6.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
# Automatically generated - DO NOT EDIT!
|
|
||||||
|
|
||||||
"$schema" = "../../schemas/schema.json"
|
|
||||||
|
|
||||||
[[permission]]
|
|
||||||
identifier = "allow-ping"
|
|
||||||
description = "Enables the ping command without any pre-configured scope."
|
|
||||||
commands.allow = ["ping"]
|
|
||||||
|
|
||||||
[[permission]]
|
|
||||||
identifier = "deny-ping"
|
|
||||||
description = "Denies the ping command without any pre-configured scope."
|
|
||||||
commands.deny = ["ping"]
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
## Permission Table
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th>Identifier</th>
|
|
||||||
<th>Description</th>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`vpn:allow-ping`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Enables the ping command without any pre-configured scope.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`vpn:deny-ping`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Denies the ping command without any pre-configured scope.
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
`vpn:vpn-control`
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
Allows access to VPN Service
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
[[permission]]
|
|
||||||
identifier = "vpn-control"
|
|
||||||
description = "Allows access to VPN Service"
|
|
||||||
name = "allow-vpn-control"
|
|
||||||
commands = { allow = ["ping", "prepare_vpn", "start_vpn", "stop_vpn"] }
|
|
||||||
@@ -1,318 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
||||||
"title": "PermissionFile",
|
|
||||||
"description": "Permission file that can define a default permission, a set of permissions or a list of inlined permissions.",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"default": {
|
|
||||||
"description": "The default permission set for the plugin",
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/DefaultPermission"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "null"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"set": {
|
|
||||||
"description": "A list of permissions sets defined",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/PermissionSet"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"permission": {
|
|
||||||
"description": "A list of inlined permissions",
|
|
||||||
"default": [],
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/Permission"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"definitions": {
|
|
||||||
"DefaultPermission": {
|
|
||||||
"description": "The default permission set of the plugin.\n\nWorks similarly to a permission with the \"default\" identifier.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"permissions"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"version": {
|
|
||||||
"description": "The version of the permission.",
|
|
||||||
"type": [
|
|
||||||
"integer",
|
|
||||||
"null"
|
|
||||||
],
|
|
||||||
"format": "uint64",
|
|
||||||
"minimum": 1.0
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"description": "Human-readable description of what the permission does. Tauri convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"permissions": {
|
|
||||||
"description": "All permissions this set contains.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"PermissionSet": {
|
|
||||||
"description": "A set of direct permissions grouped together under a new name.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"description",
|
|
||||||
"identifier",
|
|
||||||
"permissions"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"identifier": {
|
|
||||||
"description": "A unique identifier for the permission.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"description": "Human-readable description of what the permission does.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"permissions": {
|
|
||||||
"description": "All permissions this set contains.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/PermissionKind"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Permission": {
|
|
||||||
"description": "Descriptions of explicit privileges of commands.\n\nIt can enable commands to be accessible in the frontend of the application.\n\nIf the scope is defined it can be used to fine grain control the access of individual or multiple commands.",
|
|
||||||
"type": "object",
|
|
||||||
"required": [
|
|
||||||
"identifier"
|
|
||||||
],
|
|
||||||
"properties": {
|
|
||||||
"version": {
|
|
||||||
"description": "The version of the permission.",
|
|
||||||
"type": [
|
|
||||||
"integer",
|
|
||||||
"null"
|
|
||||||
],
|
|
||||||
"format": "uint64",
|
|
||||||
"minimum": 1.0
|
|
||||||
},
|
|
||||||
"identifier": {
|
|
||||||
"description": "A unique identifier for the permission.",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
"description": {
|
|
||||||
"description": "Human-readable description of what the permission does. Tauri internal convention is to use `<h4>` headings in markdown content for Tauri documentation generation purposes.",
|
|
||||||
"type": [
|
|
||||||
"string",
|
|
||||||
"null"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"commands": {
|
|
||||||
"description": "Allowed or denied commands when using this permission.",
|
|
||||||
"default": {
|
|
||||||
"allow": [],
|
|
||||||
"deny": []
|
|
||||||
},
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/Commands"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"scope": {
|
|
||||||
"description": "Allowed or denied scoped when using this permission.",
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/Scopes"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"platforms": {
|
|
||||||
"description": "Target platforms this permission applies. By default all platforms are affected by this permission.",
|
|
||||||
"type": [
|
|
||||||
"array",
|
|
||||||
"null"
|
|
||||||
],
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/Target"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Commands": {
|
|
||||||
"description": "Allowed and denied commands inside a permission.\n\nIf two commands clash inside of `allow` and `deny`, it should be denied by default.",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"allow": {
|
|
||||||
"description": "Allowed command.",
|
|
||||||
"default": [],
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"deny": {
|
|
||||||
"description": "Denied command, which takes priority.",
|
|
||||||
"default": [],
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Scopes": {
|
|
||||||
"description": "An argument for fine grained behavior control of Tauri commands.\n\nIt can be of any serde serializable type and is used to allow or prevent certain actions inside a Tauri command. The configured scope is passed to the command and will be enforced by the command implementation.\n\n## Example\n\n```json { \"allow\": [{ \"path\": \"$HOME/**\" }], \"deny\": [{ \"path\": \"$HOME/secret.txt\" }] } ```",
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"allow": {
|
|
||||||
"description": "Data that defines what is allowed by the scope.",
|
|
||||||
"type": [
|
|
||||||
"array",
|
|
||||||
"null"
|
|
||||||
],
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/Value"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"deny": {
|
|
||||||
"description": "Data that defines what is denied by the scope. This should be prioritized by validation logic.",
|
|
||||||
"type": [
|
|
||||||
"array",
|
|
||||||
"null"
|
|
||||||
],
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/Value"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Value": {
|
|
||||||
"description": "All supported ACL values.",
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"description": "Represents a null JSON value.",
|
|
||||||
"type": "null"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Represents a [`bool`].",
|
|
||||||
"type": "boolean"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Represents a valid ACL [`Number`].",
|
|
||||||
"allOf": [
|
|
||||||
{
|
|
||||||
"$ref": "#/definitions/Number"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Represents a [`String`].",
|
|
||||||
"type": "string"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Represents a list of other [`Value`]s.",
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/Value"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Represents a map of [`String`] keys to [`Value`]s.",
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {
|
|
||||||
"$ref": "#/definitions/Value"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Number": {
|
|
||||||
"description": "A valid ACL number.",
|
|
||||||
"anyOf": [
|
|
||||||
{
|
|
||||||
"description": "Represents an [`i64`].",
|
|
||||||
"type": "integer",
|
|
||||||
"format": "int64"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Represents a [`f64`].",
|
|
||||||
"type": "number",
|
|
||||||
"format": "double"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"Target": {
|
|
||||||
"description": "Platform target.",
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"description": "MacOS.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"macOS"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Windows.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"windows"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Linux.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"linux"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Android.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"android"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "iOS.",
|
|
||||||
"type": "string",
|
|
||||||
"enum": [
|
|
||||||
"iOS"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"PermissionKind": {
|
|
||||||
"type": "string",
|
|
||||||
"oneOf": [
|
|
||||||
{
|
|
||||||
"description": "Enables the ping command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "allow-ping",
|
|
||||||
"markdownDescription": "Enables the ping command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Denies the ping command without any pre-configured scope.",
|
|
||||||
"type": "string",
|
|
||||||
"const": "deny-ping",
|
|
||||||
"markdownDescription": "Denies the ping command without any pre-configured scope."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "Allows access to VPN Service",
|
|
||||||
"type": "string",
|
|
||||||
"const": "vpn-control",
|
|
||||||
"markdownDescription": "Allows access to VPN Service"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { readFileSync } from 'node:fs'
|
|
||||||
import { dirname, join } from 'node:path'
|
|
||||||
import { cwd } from 'node:process'
|
|
||||||
import typescript from '@rollup/plugin-typescript'
|
|
||||||
|
|
||||||
const pkg = JSON.parse(readFileSync(join(cwd(), 'package.json'), 'utf8'))
|
|
||||||
|
|
||||||
export default {
|
|
||||||
input: 'guest-js/index.ts',
|
|
||||||
output: [
|
|
||||||
{
|
|
||||||
file: pkg.exports.import,
|
|
||||||
format: 'esm'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
file: pkg.exports.require,
|
|
||||||
format: 'cjs'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
plugins: [
|
|
||||||
typescript({
|
|
||||||
declaration: true,
|
|
||||||
declarationDir: dirname(pkg.exports.import)
|
|
||||||
})
|
|
||||||
],
|
|
||||||
external: [
|
|
||||||
/^@tauri-apps\/api/,
|
|
||||||
...Object.keys(pkg.dependencies || {}),
|
|
||||||
...Object.keys(pkg.peerDependencies || {})
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
use tauri::{AppHandle, command, Runtime};
|
|
||||||
|
|
||||||
use crate::models::*;
|
|
||||||
use crate::Result;
|
|
||||||
use crate::VpnExt;
|
|
||||||
|
|
||||||
#[command]
|
|
||||||
pub(crate) async fn ping<R: Runtime>(
|
|
||||||
app: AppHandle<R>,
|
|
||||||
payload: PingRequest,
|
|
||||||
) -> Result<PingResponse> {
|
|
||||||
app.vpn().ping(payload)
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
use serde::de::DeserializeOwned;
|
|
||||||
use tauri::{plugin::PluginApi, AppHandle, Runtime};
|
|
||||||
|
|
||||||
use crate::models::*;
|
|
||||||
|
|
||||||
pub fn init<R: Runtime, C: DeserializeOwned>(
|
|
||||||
app: &AppHandle<R>,
|
|
||||||
_api: PluginApi<R, C>,
|
|
||||||
) -> crate::Result<Vpn<R>> {
|
|
||||||
Ok(Vpn(app.clone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Access to the vpn APIs.
|
|
||||||
pub struct Vpn<R: Runtime>(AppHandle<R>);
|
|
||||||
|
|
||||||
impl<R: Runtime> Vpn<R> {
|
|
||||||
pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
|
|
||||||
Ok(PingResponse {
|
|
||||||
value: payload.value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
use serde::{ser::Serializer, Serialize};
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum Error {
|
|
||||||
#[error(transparent)]
|
|
||||||
Io(#[from] std::io::Error),
|
|
||||||
#[cfg(mobile)]
|
|
||||||
#[error(transparent)]
|
|
||||||
PluginInvoke(#[from] tauri::plugin::mobile::PluginInvokeError),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for Error {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
serializer.serialize_str(self.to_string().as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
use tauri::{
|
|
||||||
plugin::{Builder, TauriPlugin},
|
|
||||||
Manager, Runtime,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub use models::*;
|
|
||||||
|
|
||||||
#[cfg(desktop)]
|
|
||||||
mod desktop;
|
|
||||||
#[cfg(mobile)]
|
|
||||||
mod mobile;
|
|
||||||
|
|
||||||
mod commands;
|
|
||||||
mod error;
|
|
||||||
mod models;
|
|
||||||
|
|
||||||
pub use error::{Error, Result};
|
|
||||||
|
|
||||||
#[cfg(desktop)]
|
|
||||||
use desktop::Vpn;
|
|
||||||
#[cfg(mobile)]
|
|
||||||
use mobile::Vpn;
|
|
||||||
|
|
||||||
/// Extensions to [`tauri::App`], [`tauri::AppHandle`] and [`tauri::Window`] to access the vpn APIs.
|
|
||||||
pub trait VpnExt<R: Runtime> {
|
|
||||||
fn vpn(&self) -> &Vpn<R>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R: Runtime, T: Manager<R>> crate::VpnExt<R> for T {
|
|
||||||
fn vpn(&self) -> &Vpn<R> {
|
|
||||||
self.state::<Vpn<R>>().inner()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Initializes the plugin.
|
|
||||||
pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|
||||||
Builder::new("vpn")
|
|
||||||
.invoke_handler(tauri::generate_handler![commands::ping])
|
|
||||||
.setup(|app, api| {
|
|
||||||
#[cfg(mobile)]
|
|
||||||
let vpn = mobile::init(app, api)?;
|
|
||||||
#[cfg(desktop)]
|
|
||||||
let vpn = desktop::init(app, api)?;
|
|
||||||
app.manage(vpn);
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
use serde::de::DeserializeOwned;
|
|
||||||
use tauri::{
|
|
||||||
plugin::{PluginApi, PluginHandle},
|
|
||||||
AppHandle, Runtime,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::models::*;
|
|
||||||
|
|
||||||
#[cfg(target_os = "ios")]
|
|
||||||
tauri::ios_plugin_binding!(init_plugin_vpn);
|
|
||||||
|
|
||||||
// initializes the Kotlin or Swift plugin classes
|
|
||||||
pub fn init<R: Runtime, C: DeserializeOwned>(
|
|
||||||
_app: &AppHandle<R>,
|
|
||||||
api: PluginApi<R, C>,
|
|
||||||
) -> crate::Result<Vpn<R>> {
|
|
||||||
#[cfg(target_os = "android")]
|
|
||||||
let handle = api.register_android_plugin("", "ExamplePlugin")?;
|
|
||||||
#[cfg(target_os = "ios")]
|
|
||||||
let handle = api.register_ios_plugin(init_plugin_vpn)?;
|
|
||||||
Ok(Vpn(handle))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Access to the vpn APIs.
|
|
||||||
pub struct Vpn<R: Runtime>(PluginHandle<R>);
|
|
||||||
|
|
||||||
impl<R: Runtime> Vpn<R> {
|
|
||||||
pub fn ping(&self, payload: PingRequest) -> crate::Result<PingResponse> {
|
|
||||||
self
|
|
||||||
.0
|
|
||||||
.run_mobile_plugin("ping", payload)
|
|
||||||
.map_err(Into::into)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct PingRequest {
|
|
||||||
pub value: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, Deserialize, Serialize)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct PingResponse {
|
|
||||||
pub value: Option<String>,
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "es2021",
|
|
||||||
"module": "esnext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"noEmit": true
|
|
||||||
},
|
|
||||||
"include": ["guest-js/*.ts"],
|
|
||||||
"exclude": ["dist-js", "node_modules"]
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
{
|
|
||||||
"$schema": "https://schema.tauri.app/config/2",
|
|
||||||
"productName": "netrunner_app",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"identifier": "com.netrunner.vpn",
|
|
||||||
"build": {
|
|
||||||
"beforeDevCommand": "pnpm dev",
|
|
||||||
"beforeBuildCommand": "pnpm build",
|
|
||||||
"devUrl": "http://192.168.110.169:1420",
|
|
||||||
"frontendDist": "../dist"
|
|
||||||
},
|
|
||||||
"app": {
|
|
||||||
"windows": [
|
|
||||||
{
|
|
||||||
"title": "Netrunner VPN",
|
|
||||||
"width": 500,
|
|
||||||
"height": 800
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"security": {
|
|
||||||
"csp": null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"bundle": {
|
|
||||||
"active": true,
|
|
||||||
"targets": "all",
|
|
||||||
"icon": [
|
|
||||||
"icons/32x32.png",
|
|
||||||
"icons/128x128.png",
|
|
||||||
"icons/128x128@2x.png",
|
|
||||||
"icons/icon.ico",
|
|
||||||
"icons/icon.icns"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { Header } from "./features/Header";
|
|
||||||
import { AnimatedBackground } from "./components/shared/AnimatedBackground";
|
|
||||||
import { BrowserRouter, Route, Routes } from "react-router-dom";
|
|
||||||
import { BurgerMenu } from "./features/BurgerMenu";
|
|
||||||
import { Home } from "./pages/Home";
|
|
||||||
import { About } from "./pages/About";
|
|
||||||
import { Settings } from "./pages/Settings";
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<BrowserRouter>
|
|
||||||
<div className="fixed inset-0 h-screen w-full overflow-hidden bg-background">
|
|
||||||
<AnimatedBackground />
|
|
||||||
|
|
||||||
<div className="bg-background">
|
|
||||||
<Header onMenuClick={() => setIsMenuOpen(!isMenuOpen)} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<BurgerMenu isOpen={isMenuOpen} onClose={() => setIsMenuOpen(false)} />
|
|
||||||
<main className="flex h-[calc(100vh-4rem)] flex-col items-center justify-center px-6 py-6 overflow-hidden">
|
|
||||||
<Routes>
|
|
||||||
<Route path="/" element={<Home />} />
|
|
||||||
<Route path="/settings" element={<Settings />} />
|
|
||||||
<Route path="/about" element={<About />} />
|
|
||||||
</Routes>
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</BrowserRouter>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
import { useSettings } from "@/hooks/SettingsContext";
|
|
||||||
import { motion } from "framer-motion";
|
|
||||||
|
|
||||||
export function AnimatedBackground() {
|
|
||||||
const { reduceMotion } = useSettings();
|
|
||||||
if (reduceMotion) {
|
|
||||||
return <div className="fixed inset-0 w-full h-full bg-background -z-10" />;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="fixed w-full h-full">
|
|
||||||
{/* Каустические пятна (используем контрастные Tailwind цвета) */}
|
|
||||||
|
|
||||||
<svg className="absolute w-0 h-0">
|
|
||||||
<defs>
|
|
||||||
<filter id="blob-filter">
|
|
||||||
<feGaussianBlur in="SourceGraphic" stdDeviation="40" />
|
|
||||||
</filter>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="absolute inset-0"
|
|
||||||
style={{ filter: "url(#blob-filter)", isolation: "isolate" }}
|
|
||||||
>
|
|
||||||
<motion.div
|
|
||||||
className="absolute top-[-10%] left-[-10%] w-[60%] h-[60%] rounded-full bg-chart-1/20"
|
|
||||||
animate={{
|
|
||||||
x: [0, 100, 0],
|
|
||||||
y: [0, 50, 0],
|
|
||||||
rotate: [0, 90, 0],
|
|
||||||
}}
|
|
||||||
style={{ willChange: "transform" }}
|
|
||||||
transition={{ duration: 20, repeat: Infinity, ease: "easeInOut" }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
className="absolute bottom-[-10%] right-[-10%] w-[50%] h-[50%] rounded-full bg-chart-2/20"
|
|
||||||
animate={{
|
|
||||||
x: [0, -50, 0],
|
|
||||||
y: [0, 100, 0],
|
|
||||||
rotate: [0, -45, 0],
|
|
||||||
}}
|
|
||||||
style={{ willChange: "transform" }}
|
|
||||||
transition={{ duration: 15, repeat: Infinity, ease: "easeInOut" }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
className="absolute top-[20%] right-[10%] w-[40%] h-[40%] rounded-full bg-chart-3/20"
|
|
||||||
animate={{
|
|
||||||
scale: [1, 1.2, 1],
|
|
||||||
opacity: [0.3, 0.6, 0.3],
|
|
||||||
}}
|
|
||||||
style={{ willChange: "transform" }}
|
|
||||||
transition={{ duration: 10, repeat: Infinity, ease: "easeInOut" }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
className="absolute bottom-[20%] left-[20%] w-[30%] h-[30%] rounded-full bg-chart-3/20 "
|
|
||||||
animate={{
|
|
||||||
x: [0, 30, 0],
|
|
||||||
y: [0, -30, 0],
|
|
||||||
}}
|
|
||||||
style={{ willChange: "transform" }}
|
|
||||||
transition={{ duration: 8, repeat: Infinity, ease: "easeInOut" }}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { Switch } from "@/components/ui/switch";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
|
|
||||||
interface SettingsToggleProps {
|
|
||||||
label: string;
|
|
||||||
description: string;
|
|
||||||
checked: boolean;
|
|
||||||
onCheckedChange: (checked: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function SettingsToggle({
|
|
||||||
label,
|
|
||||||
description,
|
|
||||||
checked,
|
|
||||||
onCheckedChange,
|
|
||||||
}: SettingsToggleProps) {
|
|
||||||
return (
|
|
||||||
<div className="flex items-center justify-between p-4 rounded-xl bg-white/5 border border-white/10 backdrop-blur-sm transition-all hover:bg-white/10">
|
|
||||||
<div className="space-y-0.5">
|
|
||||||
<Label className="text-sm font-medium text-foreground">{label}</Label>
|
|
||||||
<p className="text-[11px] text-muted-foreground">{description}</p>
|
|
||||||
</div>
|
|
||||||
<Switch
|
|
||||||
checked={checked}
|
|
||||||
onCheckedChange={onCheckedChange}
|
|
||||||
className="data-[state=checked]:bg-primary"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
|
||||||
import { Slot } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
const badgeVariants = cva(
|
|
||||||
"group/badge inline-flex h-5 w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-4xl border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-all focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3!",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
|
|
||||||
secondary:
|
|
||||||
"bg-secondary text-secondary-foreground [a]:hover:bg-secondary/80",
|
|
||||||
destructive:
|
|
||||||
"bg-destructive/10 text-destructive focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:focus-visible:ring-destructive/40 [a]:hover:bg-destructive/20",
|
|
||||||
outline:
|
|
||||||
"border-border bg-input/30 text-foreground [a]:hover:bg-muted [a]:hover:text-muted-foreground",
|
|
||||||
ghost:
|
|
||||||
"hover:bg-muted hover:text-muted-foreground dark:hover:bg-muted/50",
|
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function Badge({
|
|
||||||
className,
|
|
||||||
variant = "default",
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"span"> &
|
|
||||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
|
||||||
const Comp = asChild ? Slot.Root : "span"
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Comp
|
|
||||||
data-slot="badge"
|
|
||||||
data-variant={variant}
|
|
||||||
className={cn(badgeVariants({ variant }), className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Badge, badgeVariants }
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
|
||||||
import { Slot } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
const buttonVariants = cva(
|
|
||||||
"group/button inline-flex shrink-0 items-center justify-center rounded-4xl border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "bg-primary text-primary-foreground hover:bg-primary/80",
|
|
||||||
outline:
|
|
||||||
"border-border bg-input/30 hover:bg-input/50 hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground",
|
|
||||||
secondary:
|
|
||||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
|
|
||||||
ghost:
|
|
||||||
"hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
|
|
||||||
destructive:
|
|
||||||
"bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
|
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default:
|
|
||||||
"h-9 gap-1.5 px-3 has-data-[icon=inline-end]:pr-2.5 has-data-[icon=inline-start]:pl-2.5",
|
|
||||||
xs: "h-6 gap-1 px-2.5 text-xs has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2 [&_svg:not([class*='size-'])]:size-3",
|
|
||||||
sm: "h-8 gap-1 px-3 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
|
|
||||||
lg: "h-10 gap-1.5 px-4 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3",
|
|
||||||
icon: "size-9",
|
|
||||||
"icon-xs": "size-6 [&_svg:not([class*='size-'])]:size-3",
|
|
||||||
"icon-sm": "size-8",
|
|
||||||
"icon-lg": "size-10",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
size: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function Button({
|
|
||||||
className,
|
|
||||||
variant = "default",
|
|
||||||
size = "default",
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"button"> &
|
|
||||||
VariantProps<typeof buttonVariants> & {
|
|
||||||
asChild?: boolean
|
|
||||||
}) {
|
|
||||||
const Comp = asChild ? Slot.Root : "button"
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Comp
|
|
||||||
data-slot="button"
|
|
||||||
data-variant={variant}
|
|
||||||
data-size={size}
|
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
function Card({
|
|
||||||
className,
|
|
||||||
size = "default",
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card"
|
|
||||||
data-size={size}
|
|
||||||
className={cn(
|
|
||||||
"group/card flex flex-col gap-6 overflow-hidden rounded-2xl bg-card py-6 text-sm text-card-foreground ring-1 ring-foreground/10 has-[>img:first-child]:pt-0 data-[size=sm]:gap-4 data-[size=sm]:py-4 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-header"
|
|
||||||
className={cn(
|
|
||||||
"group/card-header @container/card-header grid auto-rows-min items-start gap-2 rounded-t-xl px-6 group-data-[size=sm]/card:px-4 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-6 group-data-[size=sm]/card:[.border-b]:pb-4",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-title"
|
|
||||||
className={cn("text-base font-medium", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-description"
|
|
||||||
className={cn("text-sm text-muted-foreground", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-action"
|
|
||||||
className={cn(
|
|
||||||
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-content"
|
|
||||||
className={cn("px-6 group-data-[size=sm]/card:px-4", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-footer"
|
|
||||||
className={cn(
|
|
||||||
"flex items-center rounded-b-xl px-6 group-data-[size=sm]/card:px-4 [.border-t]:pt-6 group-data-[size=sm]/card:[.border-t]:pt-4",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
Card,
|
|
||||||
CardHeader,
|
|
||||||
CardFooter,
|
|
||||||
CardTitle,
|
|
||||||
CardAction,
|
|
||||||
CardDescription,
|
|
||||||
CardContent,
|
|
||||||
}
|
|
||||||
@@ -1,163 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
import { Dialog as DialogPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { IconX } from "@tabler/icons-react"
|
|
||||||
|
|
||||||
function Dialog({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Root>) {
|
|
||||||
return <DialogPrimitive.Root data-slot="dialog" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogTrigger({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Trigger>) {
|
|
||||||
return <DialogPrimitive.Trigger data-slot="dialog-trigger" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogPortal({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Portal>) {
|
|
||||||
return <DialogPrimitive.Portal data-slot="dialog-portal" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogClose({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Close>) {
|
|
||||||
return <DialogPrimitive.Close data-slot="dialog-close" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogOverlay({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {
|
|
||||||
return (
|
|
||||||
<DialogPrimitive.Overlay
|
|
||||||
data-slot="dialog-overlay"
|
|
||||||
className={cn(
|
|
||||||
"fixed inset-0 isolate z-50 bg-black/80 duration-100 supports-backdrop-filter:backdrop-blur-xs data-open:animate-in data-open:fade-in-0 data-closed:animate-out data-closed:fade-out-0",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogContent({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
showCloseButton = true,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Content> & {
|
|
||||||
showCloseButton?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<DialogPortal>
|
|
||||||
<DialogOverlay />
|
|
||||||
<DialogPrimitive.Content
|
|
||||||
data-slot="dialog-content"
|
|
||||||
className={cn(
|
|
||||||
"fixed top-1/2 left-1/2 z-50 grid w-full max-w-[calc(100%-2rem)] -translate-x-1/2 -translate-y-1/2 gap-6 rounded-4xl bg-background p-6 text-sm ring-1 ring-foreground/5 duration-100 outline-none sm:max-w-md data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
{showCloseButton && (
|
|
||||||
<DialogPrimitive.Close data-slot="dialog-close" asChild>
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
className="absolute top-4 right-4"
|
|
||||||
size="icon-sm"
|
|
||||||
>
|
|
||||||
<IconX
|
|
||||||
/>
|
|
||||||
<span className="sr-only">Close</span>
|
|
||||||
</Button>
|
|
||||||
</DialogPrimitive.Close>
|
|
||||||
)}
|
|
||||||
</DialogPrimitive.Content>
|
|
||||||
</DialogPortal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="dialog-header"
|
|
||||||
className={cn("flex flex-col gap-2", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogFooter({
|
|
||||||
className,
|
|
||||||
showCloseButton = false,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"div"> & {
|
|
||||||
showCloseButton?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="dialog-footer"
|
|
||||||
className={cn(
|
|
||||||
"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
{showCloseButton && (
|
|
||||||
<DialogPrimitive.Close asChild>
|
|
||||||
<Button variant="outline">Close</Button>
|
|
||||||
</DialogPrimitive.Close>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogTitle({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Title>) {
|
|
||||||
return (
|
|
||||||
<DialogPrimitive.Title
|
|
||||||
data-slot="dialog-title"
|
|
||||||
className={cn("text-base leading-none font-medium", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DialogDescription({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof DialogPrimitive.Description>) {
|
|
||||||
return (
|
|
||||||
<DialogPrimitive.Description
|
|
||||||
data-slot="dialog-description"
|
|
||||||
className={cn(
|
|
||||||
"text-sm text-muted-foreground *:[a]:underline *:[a]:underline-offset-3 *:[a]:hover:text-foreground",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
Dialog,
|
|
||||||
DialogClose,
|
|
||||||
DialogContent,
|
|
||||||
DialogDescription,
|
|
||||||
DialogFooter,
|
|
||||||
DialogHeader,
|
|
||||||
DialogOverlay,
|
|
||||||
DialogPortal,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
import { Label as LabelPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
function Label({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof LabelPrimitive.Root>) {
|
|
||||||
return (
|
|
||||||
<LabelPrimitive.Root
|
|
||||||
data-slot="label"
|
|
||||||
className={cn(
|
|
||||||
"flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Label }
|
|
||||||
@@ -1,193 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
import { Select as SelectPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { IconSelector, IconCheck, IconChevronUp, IconChevronDown } from "@tabler/icons-react"
|
|
||||||
|
|
||||||
function Select({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Root>) {
|
|
||||||
return <SelectPrimitive.Root data-slot="select" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectGroup({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Group>) {
|
|
||||||
return (
|
|
||||||
<SelectPrimitive.Group
|
|
||||||
data-slot="select-group"
|
|
||||||
className={cn("scroll-my-1 p-1", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectValue({
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Value>) {
|
|
||||||
return <SelectPrimitive.Value data-slot="select-value" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectTrigger({
|
|
||||||
className,
|
|
||||||
size = "default",
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Trigger> & {
|
|
||||||
size?: "sm" | "default"
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<SelectPrimitive.Trigger
|
|
||||||
data-slot="select-trigger"
|
|
||||||
data-size={size}
|
|
||||||
className={cn(
|
|
||||||
"flex w-fit items-center justify-between gap-1.5 rounded-4xl border border-input bg-input/30 px-3 py-2 text-sm whitespace-nowrap transition-colors outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<SelectPrimitive.Icon asChild>
|
|
||||||
<IconSelector className="pointer-events-none size-4 text-muted-foreground" />
|
|
||||||
</SelectPrimitive.Icon>
|
|
||||||
</SelectPrimitive.Trigger>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectContent({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
position = "item-aligned",
|
|
||||||
align = "center",
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Content>) {
|
|
||||||
return (
|
|
||||||
<SelectPrimitive.Portal>
|
|
||||||
<SelectPrimitive.Content
|
|
||||||
data-slot="select-content"
|
|
||||||
data-align-trigger={position === "item-aligned"}
|
|
||||||
className={cn("relative z-50 max-h-(--radix-select-content-available-height) min-w-36 origin-(--radix-select-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-2xl bg-popover text-popover-foreground shadow-2xl ring-1 ring-foreground/5 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", position ==="popper"&&"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1", className )}
|
|
||||||
position={position}
|
|
||||||
align={align}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<SelectScrollUpButton />
|
|
||||||
<SelectPrimitive.Viewport
|
|
||||||
data-position={position}
|
|
||||||
className={cn(
|
|
||||||
"data-[position=popper]:h-(--radix-select-trigger-height) data-[position=popper]:w-full data-[position=popper]:min-w-(--radix-select-trigger-width)",
|
|
||||||
position === "popper" && ""
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</SelectPrimitive.Viewport>
|
|
||||||
<SelectScrollDownButton />
|
|
||||||
</SelectPrimitive.Content>
|
|
||||||
</SelectPrimitive.Portal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectLabel({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Label>) {
|
|
||||||
return (
|
|
||||||
<SelectPrimitive.Label
|
|
||||||
data-slot="select-label"
|
|
||||||
className={cn("px-3 py-2.5 text-xs text-muted-foreground", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectItem({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Item>) {
|
|
||||||
return (
|
|
||||||
<SelectPrimitive.Item
|
|
||||||
data-slot="select-item"
|
|
||||||
className={cn(
|
|
||||||
"relative flex w-full cursor-default items-center gap-2.5 rounded-xl py-2 pr-8 pl-3 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center">
|
|
||||||
<SelectPrimitive.ItemIndicator>
|
|
||||||
<IconCheck className="pointer-events-none" />
|
|
||||||
</SelectPrimitive.ItemIndicator>
|
|
||||||
</span>
|
|
||||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
||||||
</SelectPrimitive.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectSeparator({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.Separator>) {
|
|
||||||
return (
|
|
||||||
<SelectPrimitive.Separator
|
|
||||||
data-slot="select-separator"
|
|
||||||
className={cn(
|
|
||||||
"pointer-events-none -mx-1 my-1 h-px bg-border/50",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectScrollUpButton({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpButton>) {
|
|
||||||
return (
|
|
||||||
<SelectPrimitive.ScrollUpButton
|
|
||||||
data-slot="select-scroll-up-button"
|
|
||||||
className={cn(
|
|
||||||
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<IconChevronUp
|
|
||||||
/>
|
|
||||||
</SelectPrimitive.ScrollUpButton>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function SelectScrollDownButton({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownButton>) {
|
|
||||||
return (
|
|
||||||
<SelectPrimitive.ScrollDownButton
|
|
||||||
data-slot="select-scroll-down-button"
|
|
||||||
className={cn(
|
|
||||||
"z-10 flex cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<IconChevronDown
|
|
||||||
/>
|
|
||||||
</SelectPrimitive.ScrollDownButton>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectGroup,
|
|
||||||
SelectItem,
|
|
||||||
SelectLabel,
|
|
||||||
SelectScrollDownButton,
|
|
||||||
SelectScrollUpButton,
|
|
||||||
SelectSeparator,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
import { Switch as SwitchPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
function Switch({
|
|
||||||
className,
|
|
||||||
size = "default",
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof SwitchPrimitive.Root> & {
|
|
||||||
size?: "sm" | "default"
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<SwitchPrimitive.Root
|
|
||||||
data-slot="switch"
|
|
||||||
data-size={size}
|
|
||||||
className={cn(
|
|
||||||
"peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-[3px] aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<SwitchPrimitive.Thumb
|
|
||||||
data-slot="switch-thumb"
|
|
||||||
className="pointer-events-none block rounded-full bg-background ring-0 transition-transform group-data-[size=default]/switch:size-4 group-data-[size=sm]/switch:size-3 group-data-[size=default]/switch:data-checked:translate-x-[calc(100%-2px)] group-data-[size=sm]/switch:data-checked:translate-x-[calc(100%-2px)] dark:data-checked:bg-primary-foreground group-data-[size=default]/switch:data-unchecked:translate-x-0 group-data-[size=sm]/switch:data-unchecked:translate-x-0 dark:data-unchecked:bg-foreground"
|
|
||||||
/>
|
|
||||||
</SwitchPrimitive.Root>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Switch }
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
import * as React from "react"
|
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
|
||||||
import { Tabs as TabsPrimitive } from "radix-ui"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
function Tabs({
|
|
||||||
className,
|
|
||||||
orientation = "horizontal",
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
|
|
||||||
return (
|
|
||||||
<TabsPrimitive.Root
|
|
||||||
data-slot="tabs"
|
|
||||||
data-orientation={orientation}
|
|
||||||
className={cn(
|
|
||||||
"group/tabs flex gap-2 data-horizontal:flex-col",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tabsListVariants = cva(
|
|
||||||
"group/tabs-list inline-flex w-fit items-center justify-center rounded-4xl p-[3px] text-muted-foreground group-data-horizontal/tabs:h-9 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col group-data-vertical/tabs:rounded-2xl data-[variant=line]:rounded-none",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "bg-muted",
|
|
||||||
line: "gap-1 bg-transparent",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function TabsList({
|
|
||||||
className,
|
|
||||||
variant = "default",
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TabsPrimitive.List> &
|
|
||||||
VariantProps<typeof tabsListVariants>) {
|
|
||||||
return (
|
|
||||||
<TabsPrimitive.List
|
|
||||||
data-slot="tabs-list"
|
|
||||||
data-variant={variant}
|
|
||||||
className={cn(tabsListVariants({ variant }), className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function TabsTrigger({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
|
|
||||||
return (
|
|
||||||
<TabsPrimitive.Trigger
|
|
||||||
data-slot="tabs-trigger"
|
|
||||||
className={cn(
|
|
||||||
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-xl border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start group-data-vertical/tabs:px-2.5 group-data-vertical/tabs:py-1.5 hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
|
|
||||||
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
|
||||||
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
|
||||||
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function TabsContent({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
|
|
||||||
return (
|
|
||||||
<TabsPrimitive.Content
|
|
||||||
data-slot="tabs-content"
|
|
||||||
className={cn("flex-1 text-sm outline-none", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import { Link } from "react-router-dom";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
interface BurgerMenuProps {
|
|
||||||
isOpen: boolean;
|
|
||||||
onClose: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function BurgerMenu({ isOpen, onClose }: BurgerMenuProps) {
|
|
||||||
if (!isOpen) return null;
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* затемнение только под хедером */}
|
|
||||||
<div
|
|
||||||
className="fixed inset-x-0 bottom-0 top-25 z-30 bg-black/20 backdrop-blur-sm"
|
|
||||||
onClick={onClose}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* меню */}
|
|
||||||
<div className="fixed left-0 top-25 z-60 h-[calc(100vh-4rem)] w-64 bg-card border-r p-6 shadow-xl animate-in slide-in-from-left">
|
|
||||||
<nav className="flex flex-col gap-6" onClick={onClose}>
|
|
||||||
<Link
|
|
||||||
to="/"
|
|
||||||
className="text-lg font-medium hover:text-primary transition-colors"
|
|
||||||
>
|
|
||||||
{t("home_label")}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
to="/settings"
|
|
||||||
className="text-lg font-medium hover:text-primary transition-colors"
|
|
||||||
>
|
|
||||||
{t("settings")}
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
to="/about"
|
|
||||||
className="text-lg font-medium hover:text-primary transition-colors"
|
|
||||||
>
|
|
||||||
{t("about_title")}
|
|
||||||
</Link>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import {
|
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Globe } from "lucide-react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import ReactCountryFlag from "react-country-flag";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
|
|
||||||
const countries = [
|
|
||||||
{ code: "us", name: "United States" },
|
|
||||||
{ code: "de", name: "Germany" },
|
|
||||||
{ code: "jp", name: "Japan" },
|
|
||||||
];
|
|
||||||
|
|
||||||
export function CountrySelect() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [selectedCode, setSelectedCode] = useState<string>("");
|
|
||||||
|
|
||||||
const selectedCountry = countries.find((c) => c.code === selectedCode);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Select onValueChange={setSelectedCode} value={selectedCode}>
|
|
||||||
<SelectTrigger className="w-[280px] h-12 bg-white/5 backdrop-blur-lg border-white/10 hover:bg-white/10 transition-all rounded-xl shadow-lg ring-offset-0 focus:ring-0">
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex items-center gap-2 w-full px-3",
|
|
||||||
selectedCountry ? "justify-start" : "justify-center",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className="truncate flex-1 text-left">
|
|
||||||
<SelectValue placeholder={t("select_location_placeholder")} />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</SelectTrigger>
|
|
||||||
|
|
||||||
<SelectContent
|
|
||||||
className="bg-white/10 backdrop-blur-2xl border-white/10 rounded-xl overflow-hidden"
|
|
||||||
style={{ width: "var(--radix-select-trigger-width)" }}
|
|
||||||
>
|
|
||||||
{countries.map((country) => (
|
|
||||||
<SelectItem
|
|
||||||
key={country.code}
|
|
||||||
value={country.code}
|
|
||||||
className="cursor-pointer hover:bg-white/10 focus:bg-white/20 transition-colors"
|
|
||||||
>
|
|
||||||
<div className="flex items-center gap-3">
|
|
||||||
<ReactCountryFlag
|
|
||||||
countryCode={country.code.toUpperCase()}
|
|
||||||
svg
|
|
||||||
className="size-5 rounded-sm"
|
|
||||||
/>
|
|
||||||
<span className="font-medium text-foreground">
|
|
||||||
{country.name}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</SelectItem>
|
|
||||||
))}
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
import { Menu } from "lucide-react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { ThemeToggle } from "./ThemeToggle";
|
|
||||||
|
|
||||||
export function Header({ onMenuClick }: { onMenuClick: () => void }) {
|
|
||||||
return (
|
|
||||||
<header className="sticky top-0 z-50 w-full border-b border-border/40 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 shadow-sm pt-[env(safe-area-inset-top)]">
|
|
||||||
{/* Используем grid для идеальной центровки */}
|
|
||||||
<div className="grid h-16 w-full grid-cols-[auto_1fr_auto] items-center px-4">
|
|
||||||
{/* Слева: Бургер (занимает место по контенту) */}
|
|
||||||
<div className="flex justify-start">
|
|
||||||
<Button variant="ghost" size="icon" onClick={onMenuClick}>
|
|
||||||
<Menu className="size-6" />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* По центру: Лого (занимает всё свободное пространство и центрирует контент) */}
|
|
||||||
<div className="flex justify-center">
|
|
||||||
<h1 className="text-xl font-bold tracking-tighter">
|
|
||||||
Netrunner <span className="text-primary italic">VPN</span>
|
|
||||||
</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Справа: Тема (занимает место по контенту) */}
|
|
||||||
<div className="flex justify-end">
|
|
||||||
<ThemeToggle />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import { Moon, Sun } from "lucide-react";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { useDarkMode } from "@/hooks/useDarkMode";
|
|
||||||
|
|
||||||
export function ThemeToggle() {
|
|
||||||
const { isDark, toggleDark } = useDarkMode();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
size="icon"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
toggleDark();
|
|
||||||
}}
|
|
||||||
className="rounded-full transition-all duration-300 hover:scale-105"
|
|
||||||
>
|
|
||||||
{isDark ? (
|
|
||||||
<Sun className="h-[1.2rem] w-[1.2rem]" />
|
|
||||||
) : (
|
|
||||||
<Moon className="h-[1.2rem] w-[1.2rem]" />
|
|
||||||
)}
|
|
||||||
<span className="sr-only">Переключить тему</span>
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
|
|
||||||
export function LanguageToggle() {
|
|
||||||
const { i18n } = useTranslation();
|
|
||||||
|
|
||||||
const toggleLanguage = () => {
|
|
||||||
const newLang = i18n.language === "en" ? "ru" : "en";
|
|
||||||
i18n.changeLanguage(newLang);
|
|
||||||
localStorage.setItem("lang", newLang); // Сохраняем выбор
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Button variant="ghost" onClick={toggleLanguage}>
|
|
||||||
{i18n.language.toUpperCase()}
|
|
||||||
</Button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { motion, AnimatePresence } from "framer-motion";
|
|
||||||
import { Power } from "lucide-react";
|
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
|
||||||
|
|
||||||
export function VpnControl() {
|
|
||||||
const [status, setStatus] = useState<"idle" | "connecting" | "connected">(
|
|
||||||
"idle",
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleToggle = async () => {
|
|
||||||
try {
|
|
||||||
if (status === "idle") {
|
|
||||||
setStatus("connecting");
|
|
||||||
await invoke("start_vpn_android", {
|
|
||||||
remoteAddress: "62.60.244.156",
|
|
||||||
});
|
|
||||||
|
|
||||||
setStatus("connected");
|
|
||||||
} else {
|
|
||||||
setStatus("idle");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Ошибка VPN:", error);
|
|
||||||
setStatus("idle");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
const isActive = status !== "idle";
|
|
||||||
|
|
||||||
const statusMap = {
|
|
||||||
idle: t("disconnected"),
|
|
||||||
connecting: t("connecting"),
|
|
||||||
connected: t("connected"),
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center justify-center gap-6 p-10">
|
|
||||||
{/* Контейнер с эффектом жидкого стекла */}
|
|
||||||
<div className="relative flex size-64 items-center justify-center rounded-full bg-background/20 backdrop-blur-xl border border-white/10 shadow-2xl p-4">
|
|
||||||
<svg
|
|
||||||
className="absolute size-full rotate-90 pointer-events-none"
|
|
||||||
viewBox="-20 -20 300 300"
|
|
||||||
>
|
|
||||||
{/* Фоновая линия */}
|
|
||||||
<circle
|
|
||||||
cx="130"
|
|
||||||
cy="130"
|
|
||||||
r="140"
|
|
||||||
className="stroke-primary/20 fill-none"
|
|
||||||
strokeWidth="4"
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Правая половина (по часовой от 6 к 12) */}
|
|
||||||
<motion.circle
|
|
||||||
cx="130"
|
|
||||||
cy="130"
|
|
||||||
r="140"
|
|
||||||
className="stroke-primary/80 fill-none drop-shadow-[0_0_3px_var(--color-primary)]"
|
|
||||||
strokeWidth="4"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeDasharray="800"
|
|
||||||
initial={{ pathLength: 0 }}
|
|
||||||
animate={{
|
|
||||||
pathLength: isActive ? 0.495 : 0,
|
|
||||||
opacity: isActive ? 1 : 0,
|
|
||||||
}}
|
|
||||||
transition={{ duration: 1.5, ease: "easeInOut" }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
{/* Левая половина (против часовой от 6 к 12) */}
|
|
||||||
<motion.circle
|
|
||||||
cx="130"
|
|
||||||
cy="130"
|
|
||||||
r="140"
|
|
||||||
className="stroke-primary/80 fill-none drop-shadow-[0_0_3px_var(--color-primary)]"
|
|
||||||
strokeWidth="4"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeDasharray="800"
|
|
||||||
// Поворачиваем на 180 градусов относительно центра, чтобы рисовать в другую сторону
|
|
||||||
style={{ rotate: 180, originX: "140px", originY: "141px" }}
|
|
||||||
initial={{ pathLength: 0 }}
|
|
||||||
animate={{
|
|
||||||
pathLength: isActive ? 0.495 : 0,
|
|
||||||
opacity: isActive ? 1 : 0,
|
|
||||||
}}
|
|
||||||
transition={{ duration: 1.5, ease: "easeInOut" }}
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<button
|
|
||||||
onClick={handleToggle}
|
|
||||||
className={cn(
|
|
||||||
"relative size-40 rounded-full transition-all duration-700 flex items-center justify-center",
|
|
||||||
isActive
|
|
||||||
? "bg-primary/20 shadow-[0_0_40px_var(--color-primary)]"
|
|
||||||
: "bg-muted shadow-inner",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<Power
|
|
||||||
className={cn(
|
|
||||||
"size-16 transition-colors duration-500",
|
|
||||||
isActive ? "text-primary" : "text-muted-foreground",
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Статус */}
|
|
||||||
<div className="text-center space-y-1">
|
|
||||||
<p className="text-sm uppercase tracking-widest text-muted-foreground font-semibold">
|
|
||||||
{t("status_label")}
|
|
||||||
</p>
|
|
||||||
<AnimatePresence mode="wait">
|
|
||||||
<motion.p
|
|
||||||
key={status}
|
|
||||||
initial={{ opacity: 0, y: 10 }}
|
|
||||||
animate={{ opacity: 1, y: 0 }}
|
|
||||||
exit={{ opacity: 0, y: -10 }}
|
|
||||||
className={cn(
|
|
||||||
"text-xl font-bold",
|
|
||||||
isActive ? "text-primary" : "text-foreground",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{statusMap[status]}
|
|
||||||
</motion.p>
|
|
||||||
</AnimatePresence>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { cn } from "@/lib/utils";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
interface VpnStatsProps {
|
|
||||||
received: string;
|
|
||||||
sent: string;
|
|
||||||
className?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function VpnStats({ received, sent, className }: VpnStatsProps) {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"w-full max-w-70",
|
|
||||||
"relative flex items-center justify-between px-8 py-4 rounded-2xl", // Увеличил отступы внутри
|
|
||||||
"bg-background/20 backdrop-blur-md border border-white/10",
|
|
||||||
"shadow-[inset_0_1px_1px_rgba(255,255,255,0.4),0_8px_16px_rgba(0,0,0,0.1)]",
|
|
||||||
"backdrop-blur-md saturate-150 brightness-110",
|
|
||||||
className,
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{/* Теперь контент внутри растянется равномерно */}
|
|
||||||
<div className="flex flex-col items-center flex-1">
|
|
||||||
<span className="text-[10px] uppercase tracking-widest text-muted-foreground font-bold">
|
|
||||||
{t("rx")}
|
|
||||||
</span>
|
|
||||||
<span className="text-sm font-mono text-foreground font-semibold tracking-tight">
|
|
||||||
{received}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="h-8 w-px bg-border/50 mx-4" />
|
|
||||||
|
|
||||||
<div className="flex flex-col items-center flex-1">
|
|
||||||
<span className="text-[10px] uppercase tracking-widest text-muted-foreground font-bold">
|
|
||||||
{t("tx")}
|
|
||||||
</span>
|
|
||||||
<span className="text-sm font-mono text-foreground font-semibold tracking-tight">
|
|
||||||
{sent}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
import { createContext, useContext, useState, ReactNode } from "react";
|
|
||||||
|
|
||||||
const SettingsContext = createContext({
|
|
||||||
reduceMotion: false,
|
|
||||||
setReduceMotion: (val: boolean) => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
export function SettingsProvider({ children }: { children: ReactNode }) {
|
|
||||||
const [reduceMotion, setReduceMotion] = useState(false);
|
|
||||||
return (
|
|
||||||
<SettingsContext.Provider value={{ reduceMotion, setReduceMotion }}>
|
|
||||||
{children}
|
|
||||||
</SettingsContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useSettings = () => useContext(SettingsContext);
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
|
|
||||||
export const useDarkMode = () => {
|
|
||||||
const [isDark, setIsDark] = useState(() => {
|
|
||||||
if (typeof window === "undefined") return false;
|
|
||||||
return localStorage.getItem("theme") === "dark";
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Слушатель для изменений в localStorage из других компонентов/окон
|
|
||||||
const handleStorageChange = () => {
|
|
||||||
const isDarkMode = localStorage.getItem("theme") === "dark";
|
|
||||||
setIsDark(isDarkMode);
|
|
||||||
document.documentElement.classList.toggle("dark", isDarkMode);
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("storage", handleStorageChange);
|
|
||||||
return () => window.removeEventListener("storage", handleStorageChange);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const toggleDark = () => {
|
|
||||||
const nextDark = !isDark;
|
|
||||||
setIsDark(nextDark);
|
|
||||||
document.documentElement.classList.toggle("dark", nextDark);
|
|
||||||
localStorage.setItem("theme", nextDark ? "dark" : "light");
|
|
||||||
|
|
||||||
// ВАЖНО: вручную вызываем событие, чтобы другие компоненты в том же окне узнали об изменении
|
|
||||||
window.dispatchEvent(new Event("storage"));
|
|
||||||
};
|
|
||||||
|
|
||||||
return { isDark, toggleDark };
|
|
||||||
};
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import i18n from "i18next";
|
|
||||||
import { initReactI18next } from "react-i18next";
|
|
||||||
import LanguageDetector from "i18next-browser-languagedetector";
|
|
||||||
import en from "./locales/en.json";
|
|
||||||
import ru from "./locales/ru.json";
|
|
||||||
|
|
||||||
i18n
|
|
||||||
.use(LanguageDetector)
|
|
||||||
.use(initReactI18next)
|
|
||||||
.init({
|
|
||||||
resources: { en: { translation: en }, ru: { translation: ru } },
|
|
||||||
fallbackLng: "en", // язык по умолчанию
|
|
||||||
interpolation: { escapeValue: false },
|
|
||||||
});
|
|
||||||
|
|
||||||
export default i18n;
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
import { clsx, type ClassValue } from "clsx"
|
|
||||||
import { twMerge } from "tailwind-merge"
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
|
||||||
return twMerge(clsx(inputs))
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"rx": "Download",
|
|
||||||
"tx": "Upload",
|
|
||||||
"status_label": "Status",
|
|
||||||
"disconnected": "DISCONNECTED",
|
|
||||||
"connecting": "CONNECTING...",
|
|
||||||
"connected": "CONNECTED",
|
|
||||||
"about_title": "Netrunner VPN",
|
|
||||||
"about_desc": "We created Netrunner not as a standard VPN, but as a secure tunnel that ISPs perceive as regular web traffic.",
|
|
||||||
"proto_title": "Proprietary Protocol",
|
|
||||||
"proto_desc": "TLS mimicry makes your connection invisible to DPI systems.",
|
|
||||||
"zk_title": "Zero-Knowledge",
|
|
||||||
"zk_desc": "We don't keep logs. Our architecture ensures that we technically cannot see what you are transmitting.",
|
|
||||||
"version_info": "Version 1.0.0-stable | 2026",
|
|
||||||
"footer_text": "Developed with care for network freedom.",
|
|
||||||
"select_location_placeholder": "Select a VPN location",
|
|
||||||
"section_network": "Network",
|
|
||||||
"kill_switch": "Kill Switch",
|
|
||||||
"kill_switch_desc": "Block internet on connection loss",
|
|
||||||
"stealth_mode": "Stealth Mode",
|
|
||||||
"stealth_mode_desc": "Enhanced TLS obfuscation",
|
|
||||||
"section_interface": "Interface",
|
|
||||||
"reduce_motion": "Reduce Motion",
|
|
||||||
"reduce_motion_desc": "For resource efficiency",
|
|
||||||
"theme_toggle": "Appearance Theme",
|
|
||||||
"language_toggle": "Interface Language",
|
|
||||||
"home_label": "Home",
|
|
||||||
"settings": "Settings",
|
|
||||||
"about_menu": "About"
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
{
|
|
||||||
"rx": "Получено",
|
|
||||||
"tx": "Отправлено",
|
|
||||||
"status_label": "Статус",
|
|
||||||
"disconnected": "ОТКЛЮЧЕНО",
|
|
||||||
"connecting": "ПОДКЛЮЧЕНИЕ...",
|
|
||||||
"connected": "ПОДКЛЮЧЕНО",
|
|
||||||
"about_title": "Netrunner VPN",
|
|
||||||
"about_desc": "Мы создали Netrunner не как обычный VPN, а как защищенный туннель, который интернет-провайдеры видят просто как обычный просмотр веб-сайтов.",
|
|
||||||
"proto_title": "Собственный протокол",
|
|
||||||
"proto_desc": "Мимикрия под TLS-трафик делает ваше соединение невидимым для систем DPI.",
|
|
||||||
"zk_title": "Zero-Knowledge",
|
|
||||||
"zk_desc": "Мы не храним логи. Наша архитектура гарантирует, что мы технически не можем видеть, что вы передаете.",
|
|
||||||
"version_info": "Версия 1.0.0-stable | 2026",
|
|
||||||
"footer_text": "Разработано с заботой о свободе в сети.",
|
|
||||||
"select_location_placeholder": "Выберите локацию VPN",
|
|
||||||
"section_network": "Сеть",
|
|
||||||
"kill_switch": "Kill Switch",
|
|
||||||
"kill_switch_desc": "Блокировать интернет при обрыве",
|
|
||||||
"stealth_mode": "Stealth Mode",
|
|
||||||
"stealth_mode_desc": "Усиленная TLS-маскировка",
|
|
||||||
"section_interface": "Интерфейс",
|
|
||||||
"reduce_motion": "Снизить анимации",
|
|
||||||
"reduce_motion_desc": "Для экономии ресурсов",
|
|
||||||
"theme_toggle": "Тема оформления",
|
|
||||||
"language_toggle": "Язык интерфейса",
|
|
||||||
"home_label": "Главная",
|
|
||||||
"settings": "Настройки",
|
|
||||||
"about_menu": "О приложении"
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom/client";
|
|
||||||
import App from "./App";
|
|
||||||
import "../style.css";
|
|
||||||
import "./i18n";
|
|
||||||
import { SettingsProvider } from "./hooks/SettingsContext";
|
|
||||||
|
|
||||||
const root = document.getElementById("root");
|
|
||||||
|
|
||||||
if (root) {
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
||||||
<SettingsProvider>
|
|
||||||
<App />
|
|
||||||
</SettingsProvider>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
|
||||||
|
|
||||||
export function About() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col items-center gap-6 p-6 max-w-[320px] text-center">
|
|
||||||
<h2 className="text-2xl font-bold tracking-tight">{t("about_title")}</h2>
|
|
||||||
|
|
||||||
<p className="text-sm text-muted-foreground leading-relaxed">
|
|
||||||
{t("about_desc")}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="w-full flex flex-col gap-3">
|
|
||||||
<div className="p-4 rounded-xl bg-white/5 border border-white/10 backdrop-blur-sm">
|
|
||||||
<h4 className="text-xs font-bold uppercase tracking-widest text-primary mb-1">
|
|
||||||
{t("proto_title")}
|
|
||||||
</h4>
|
|
||||||
<p className="text-[11px] text-muted-foreground">{t("proto_desc")}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4 rounded-xl bg-white/5 border border-white/10 backdrop-blur-sm">
|
|
||||||
<h4 className="text-xs font-bold uppercase tracking-widest text-primary mb-1">
|
|
||||||
{t("zk_title")}
|
|
||||||
</h4>
|
|
||||||
<p className="text-[11px] text-muted-foreground">{t("zk_desc")}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="text-[10px] text-muted-foreground/60 mt-2">
|
|
||||||
{t("version_info")}
|
|
||||||
<br />
|
|
||||||
{t("footer_text")}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import { VpnControl } from "../features/VpnControl";
|
|
||||||
import { VpnStats } from "../features/VpnStats";
|
|
||||||
import { CountrySelect } from "../features/CountrySelect";
|
|
||||||
|
|
||||||
export function Home() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col gap-1.75 items-center">
|
|
||||||
<VpnControl />
|
|
||||||
<CountrySelect />
|
|
||||||
<VpnStats received="0" sent="0" />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import { useState } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { SettingsToggle } from "@/components/shared/SettingsToggle";
|
|
||||||
import { ThemeToggle } from "@/features/ThemeToggle";
|
|
||||||
import { LanguageToggle } from "@/features/ToggleLanguge";
|
|
||||||
import { useSettings } from "@/hooks/SettingsContext";
|
|
||||||
|
|
||||||
export function Settings() {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const [killSwitch, setKillSwitch] = useState(false);
|
|
||||||
const [stealthMode, setStealthMode] = useState(true);
|
|
||||||
const { reduceMotion, setReduceMotion } = useSettings();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="w-full max-w-[320px] flex flex-col gap-8 p-6 z-5">
|
|
||||||
<h2 className="text-xl font-bold">{t("settings")}</h2>
|
|
||||||
|
|
||||||
{/* Секция: Сеть
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h3 className="text-[10px] font-bold text-muted-foreground uppercase tracking-wider">
|
|
||||||
{t("section_network")}
|
|
||||||
</h3>
|
|
||||||
<SettingsToggle
|
|
||||||
label={t("kill_switch")}
|
|
||||||
description={t("kill_switch_desc")}
|
|
||||||
checked={killSwitch}
|
|
||||||
onCheckedChange={setKillSwitch}
|
|
||||||
/>
|
|
||||||
<SettingsToggle
|
|
||||||
label={t("stealth_mode")}
|
|
||||||
description={t("stealth_mode_desc")}
|
|
||||||
checked={stealthMode}
|
|
||||||
onCheckedChange={setStealthMode}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
*/}
|
|
||||||
{/* Секция: Интерфейс */}
|
|
||||||
<div className="space-y-4">
|
|
||||||
<h3 className="text-[10px] font-bold text-muted-foreground uppercase tracking-wider">
|
|
||||||
{t("section_interface")}
|
|
||||||
</h3>
|
|
||||||
|
|
||||||
<SettingsToggle
|
|
||||||
label={t("reduce_motion")}
|
|
||||||
description={t("reduce_motion_desc")}
|
|
||||||
checked={reduceMotion}
|
|
||||||
onCheckedChange={setReduceMotion}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between p-4 rounded-xl bg-white/5 border border-white/10">
|
|
||||||
<span className="text-sm font-medium">{t("theme_toggle")}</span>
|
|
||||||
<ThemeToggle />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-center justify-between p-4 rounded-xl bg-white/5 border border-white/10">
|
|
||||||
<span className="text-sm font-medium">{t("language_toggle")}</span>
|
|
||||||
<LanguageToggle />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
-129
@@ -1,129 +0,0 @@
|
|||||||
@import "tailwindcss";
|
|
||||||
@import "tw-animate-css";
|
|
||||||
@import "shadcn/tailwind.css";
|
|
||||||
@import "@fontsource-variable/nunito-sans";
|
|
||||||
|
|
||||||
@custom-variant dark (&:is(.dark *));
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--background: oklch(1 0 0);
|
|
||||||
--foreground: oklch(0.148 0.004 228.8);
|
|
||||||
--card: oklch(1 0 0);
|
|
||||||
--card-foreground: oklch(0.148 0.004 228.8);
|
|
||||||
--popover: oklch(1 0 0);
|
|
||||||
--popover-foreground: oklch(0.148 0.004 228.8);
|
|
||||||
--primary: oklch(0.496 0.265 301.924);
|
|
||||||
--primary-foreground: oklch(0.977 0.014 308.299);
|
|
||||||
--secondary: oklch(0.967 0.001 286.375);
|
|
||||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
||||||
--muted: oklch(0.963 0.002 197.1);
|
|
||||||
--muted-foreground: oklch(0.56 0.021 213.5);
|
|
||||||
--accent: oklch(0.963 0.002 197.1);
|
|
||||||
--accent-foreground: oklch(0.218 0.008 223.9);
|
|
||||||
--destructive: oklch(0.577 0.245 27.325);
|
|
||||||
--border: oklch(0.925 0.005 214.3);
|
|
||||||
--input: oklch(0.925 0.005 214.3);
|
|
||||||
--ring: oklch(0.723 0.014 214.4);
|
|
||||||
--chart-1: oklch(0.827 0.119 306.383);
|
|
||||||
--chart-2: oklch(0.627 0.265 303.9);
|
|
||||||
--chart-3: oklch(0.558 0.288 302.321);
|
|
||||||
--chart-4: oklch(0.496 0.265 301.924);
|
|
||||||
--chart-5: oklch(0.438 0.218 303.724);
|
|
||||||
--radius: 0.625rem;
|
|
||||||
--sidebar: oklch(0.987 0.002 197.1);
|
|
||||||
--sidebar-foreground: oklch(0.148 0.004 228.8);
|
|
||||||
--sidebar-primary: oklch(0.558 0.288 302.321);
|
|
||||||
--sidebar-primary-foreground: oklch(0.977 0.014 308.299);
|
|
||||||
--sidebar-accent: oklch(0.963 0.002 197.1);
|
|
||||||
--sidebar-accent-foreground: oklch(0.218 0.008 223.9);
|
|
||||||
--sidebar-border: oklch(0.925 0.005 214.3);
|
|
||||||
--sidebar-ring: oklch(0.723 0.014 214.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--background: oklch(0.148 0.004 228.8);
|
|
||||||
--foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--card: oklch(0.218 0.008 223.9);
|
|
||||||
--card-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--popover: oklch(0.218 0.008 223.9);
|
|
||||||
--popover-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--primary: oklch(0.438 0.218 303.724);
|
|
||||||
--primary-foreground: oklch(0.977 0.014 308.299);
|
|
||||||
--secondary: oklch(0.274 0.006 286.033);
|
|
||||||
--secondary-foreground: oklch(0.985 0 0);
|
|
||||||
--muted: oklch(0.275 0.011 216.9);
|
|
||||||
--muted-foreground: oklch(0.723 0.014 214.4);
|
|
||||||
--accent: oklch(0.275 0.011 216.9);
|
|
||||||
--accent-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 10%);
|
|
||||||
--input: oklch(1 0 0 / 15%);
|
|
||||||
--ring: oklch(0.56 0.021 213.5);
|
|
||||||
--chart-1: oklch(0.827 0.119 306.383);
|
|
||||||
--chart-2: oklch(0.627 0.265 303.9);
|
|
||||||
--chart-3: oklch(0.558 0.288 302.321);
|
|
||||||
--chart-4: oklch(0.496 0.265 301.924);
|
|
||||||
--chart-5: oklch(0.438 0.218 303.724);
|
|
||||||
--sidebar: oklch(0.218 0.008 223.9);
|
|
||||||
--sidebar-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--sidebar-primary: oklch(0.627 0.265 303.9);
|
|
||||||
--sidebar-primary-foreground: oklch(0.977 0.014 308.299);
|
|
||||||
--sidebar-accent: oklch(0.275 0.011 216.9);
|
|
||||||
--sidebar-accent-foreground: oklch(0.987 0.002 197.1);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 10%);
|
|
||||||
--sidebar-ring: oklch(0.56 0.021 213.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
@theme inline {
|
|
||||||
--font-sans: "Nunito Sans Variable", sans-serif;
|
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
|
||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
||||||
--color-sidebar-accent: var(--sidebar-accent);
|
|
||||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
||||||
--color-sidebar-primary: var(--sidebar-primary);
|
|
||||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
||||||
--color-sidebar: var(--sidebar);
|
|
||||||
--color-chart-5: var(--chart-5);
|
|
||||||
--color-chart-4: var(--chart-4);
|
|
||||||
--color-chart-3: var(--chart-3);
|
|
||||||
--color-chart-2: var(--chart-2);
|
|
||||||
--color-chart-1: var(--chart-1);
|
|
||||||
--color-ring: var(--ring);
|
|
||||||
--color-input: var(--input);
|
|
||||||
--color-border: var(--border);
|
|
||||||
--color-destructive: var(--destructive);
|
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
|
||||||
--color-accent: var(--accent);
|
|
||||||
--color-muted-foreground: var(--muted-foreground);
|
|
||||||
--color-muted: var(--muted);
|
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
|
||||||
--color-secondary: var(--secondary);
|
|
||||||
--color-primary-foreground: var(--primary-foreground);
|
|
||||||
--color-primary: var(--primary);
|
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
|
||||||
--color-popover: var(--popover);
|
|
||||||
--color-card-foreground: var(--card-foreground);
|
|
||||||
--color-card: var(--card);
|
|
||||||
--color-foreground: var(--foreground);
|
|
||||||
--color-background: var(--background);
|
|
||||||
--radius-sm: calc(var(--radius) * 0.6);
|
|
||||||
--radius-md: calc(var(--radius) * 0.8);
|
|
||||||
--radius-lg: var(--radius);
|
|
||||||
--radius-xl: calc(var(--radius) * 1.4);
|
|
||||||
--radius-2xl: calc(var(--radius) * 1.8);
|
|
||||||
--radius-3xl: calc(var(--radius) * 2.2);
|
|
||||||
--radius-4xl: calc(var(--radius) * 2.6);
|
|
||||||
}
|
|
||||||
|
|
||||||
@layer base {
|
|
||||||
* {
|
|
||||||
@apply border-border outline-ring/50;
|
|
||||||
}
|
|
||||||
body {
|
|
||||||
@apply bg-background text-foreground;
|
|
||||||
}
|
|
||||||
html {
|
|
||||||
@apply font-sans;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"useDefineForClassFields": true,
|
|
||||||
"module": "ESNext",
|
|
||||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"noEmit": true,
|
|
||||||
"jsx": "react-jsx",
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": false,
|
|
||||||
"noUnusedParameters": false,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["./src/*"]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"include": ["src"]
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { defineConfig } from "vite";
|
|
||||||
import react from "@vitejs/plugin-react";
|
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
const host = process.env.TAURI_DEV_HOST;
|
|
||||||
|
|
||||||
export default defineConfig({
|
|
||||||
plugins: [react(), tailwindcss()],
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
"@": path.resolve(__dirname, "./src"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
clearScreen: false,
|
|
||||||
server: {
|
|
||||||
host: "0.0.0.0",
|
|
||||||
port: 1420,
|
|
||||||
strictPort: true,
|
|
||||||
hmr: host
|
|
||||||
? {
|
|
||||||
protocol: "ws",
|
|
||||||
host: "localhost",
|
|
||||||
port: 1421,
|
|
||||||
}
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
envPrefix: ["VITE_", "TAURI_ENV_*"],
|
|
||||||
build: {
|
|
||||||
target:
|
|
||||||
process.env.TAURI_ENV_PLATFORM == "windows" ? "chrome105" : "safari13",
|
|
||||||
minify: !process.env.TAURI_ENV_DEBUG ? "esbuild" : false,
|
|
||||||
sourcemap: !!process.env.TAURI_ENV_DEBUG,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user