mirror of
https://github.com/lush2020/edgetunnel.git
synced 2026-03-24 09:08:16 +08:00
add edge ui common project
This commit is contained in:
18
libs/edge-ui/.eslintrc.json
Normal file
18
libs/edge-ui/.eslintrc.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"extends": ["plugin:@nrwl/nx/react", "../../.eslintrc.json"],
|
||||
"ignorePatterns": ["!**/*"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts", "*.tsx"],
|
||||
"rules": {}
|
||||
},
|
||||
{
|
||||
"files": ["*.js", "*.jsx"],
|
||||
"rules": {}
|
||||
}
|
||||
]
|
||||
}
|
||||
7
libs/edge-ui/README.md
Normal file
7
libs/edge-ui/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
# edge-ui
|
||||
|
||||
This library was generated with [Nx](https://nx.dev).
|
||||
|
||||
## Running unit tests
|
||||
|
||||
Run `nx test edge-ui` to execute the unit tests via [Vitest](https://vitest.dev/).
|
||||
15
libs/edge-ui/postcss.config.js
Normal file
15
libs/edge-ui/postcss.config.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const { join } = require('path');
|
||||
|
||||
// Note: If you use library-specific PostCSS/Tailwind configuration then you should remove the `postcssConfig` build
|
||||
// option from your application's configuration (i.e. project.json).
|
||||
//
|
||||
// See: https://nx.dev/guides/using-tailwind-css-in-react#step-4:-applying-configuration-to-libraries
|
||||
|
||||
module.exports = {
|
||||
plugins: {
|
||||
tailwindcss: {
|
||||
config: join(__dirname, 'tailwind.config.js'),
|
||||
},
|
||||
autoprefixer: {},
|
||||
},
|
||||
};
|
||||
23
libs/edge-ui/project.json
Normal file
23
libs/edge-ui/project.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "edge-ui",
|
||||
"$schema": "../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "libs/edge-ui/src",
|
||||
"projectType": "library",
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"lint": {
|
||||
"executor": "@nrwl/linter:eslint",
|
||||
"outputs": ["{options.outputFile}"],
|
||||
"options": {
|
||||
"lintFilePatterns": ["libs/edge-ui/**/*.{ts,tsx,js,jsx}"]
|
||||
}
|
||||
},
|
||||
"test": {
|
||||
"executor": "@nrwl/vite:test",
|
||||
"outputs": ["{projectRoot}/coverage"],
|
||||
"options": {
|
||||
"passWithNoTests": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
libs/edge-ui/src/index.ts
Normal file
1
libs/edge-ui/src/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './lib/app';
|
||||
265
libs/edge-ui/src/lib/app.tsx
Normal file
265
libs/edge-ui/src/lib/app.tsx
Normal file
@@ -0,0 +1,265 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
import { Transition } from '@headlessui/react';
|
||||
import { ExclamationTriangleIcon, XMarkIcon } from '@heroicons/react/20/solid';
|
||||
import QRCode from 'qrcode';
|
||||
import { Fragment, useEffect, useState } from 'react';
|
||||
import { validate as uuidValidate } from 'uuid';
|
||||
export function EdgeApp() {
|
||||
const [text, setText] = useState('');
|
||||
const [show, setShow] = useState(false);
|
||||
function handleShare(text: string) {
|
||||
setText(text);
|
||||
setShow(true);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
console.log('useEffect---setShow');
|
||||
const timeoutID = setTimeout(() => {
|
||||
setShow(false);
|
||||
}, 1500);
|
||||
return () => {
|
||||
clearTimeout(timeoutID);
|
||||
};
|
||||
}
|
||||
}, [show]);
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col items-center h-screen">
|
||||
<Warning></Warning>
|
||||
<div className="flex flex-col h-full ite">
|
||||
<QRcodeImg text={text}></QRcodeImg>
|
||||
<ShareActions handleShare={handleShare}></ShareActions>
|
||||
<ShareAnything handleShare={handleShare}></ShareAnything>
|
||||
</div>
|
||||
</div>
|
||||
<ShareNotifications show={show} setShow={setShow}></ShareNotifications>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function ShareNotifications({
|
||||
show,
|
||||
setShow,
|
||||
}: {
|
||||
show: boolean;
|
||||
setShow: (show: boolean) => void;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
{/* Global notification live region, render this permanently at the end of the document */}
|
||||
<div
|
||||
aria-live="assertive"
|
||||
className="fixed inset-0 flex items-end px-4 py-6 pointer-events-none sm:items-start sm:p-6"
|
||||
>
|
||||
<div className="flex flex-col items-center w-full space-y-4 sm:items-end">
|
||||
{/* Notification panel, dynamically insert this into the live region when it needs to be displayed */}
|
||||
<Transition
|
||||
show={show}
|
||||
as={Fragment}
|
||||
enter="transform ease-out duration-300 transition"
|
||||
enterFrom="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
|
||||
enterTo="translate-y-0 opacity-100 sm:translate-x-0"
|
||||
leave="transition ease-in duration-100"
|
||||
leaveFrom="opacity-100"
|
||||
leaveTo="opacity-0"
|
||||
>
|
||||
<div className="w-full max-w-sm overflow-hidden bg-white rounded-lg shadow-lg pointer-events-auto ring-1 ring-black ring-opacity-5">
|
||||
<div className="p-4">
|
||||
<div className="flex items-start">
|
||||
<div className="flex-shrink-0">
|
||||
<ExclamationTriangleIcon
|
||||
className="w-6 h-6 text-red-700"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3 w-0 flex-1 pt-0.5">
|
||||
<p className="text-sm font-medium text-gray-900">
|
||||
分享成功!
|
||||
</p>
|
||||
<p className="mt-1 text-sm text-red-500">
|
||||
请不要随意泄露分享链接!!
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-shrink-0 ml-4">
|
||||
<button
|
||||
type="button"
|
||||
className="inline-flex text-gray-400 bg-white rounded-md hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
onClick={() => {
|
||||
setShow(false);
|
||||
}}
|
||||
>
|
||||
<span className="sr-only">Close</span>
|
||||
<XMarkIcon className="w-5 h-5" aria-hidden="true" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function QRcodeImg({ text }: { text: string }) {
|
||||
const [codeImg, setcodeImg] = useState('');
|
||||
const [copy, setCopy] = useState(false);
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
if (text) {
|
||||
const dataURL = await QRCode.toDataURL(text);
|
||||
setcodeImg(dataURL);
|
||||
}
|
||||
})();
|
||||
}, [text]);
|
||||
|
||||
async function copyText() {
|
||||
await navigator.clipboard.writeText(text);
|
||||
setCopy(true);
|
||||
setTimeout(() => {
|
||||
setCopy(false);
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col border border-blue-300 overflow-hidden w-[420px] h-[420px] justify-start items-center">
|
||||
<img
|
||||
src={codeImg}
|
||||
width="350"
|
||||
height="350"
|
||||
alt="二维码"
|
||||
className="border-spacing-1"
|
||||
/>
|
||||
<div className="flex flex-grow w-full bg-gray-200">
|
||||
<span className="flex-grow">{text}</span>
|
||||
<div className="w-6 h-6 ml-auto">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth={1.5}
|
||||
stroke="currentColor"
|
||||
onClick={copyText}
|
||||
className={`w-6 h-6 hover:cursor-pointer hover:border hover:border-indigo-500 ${
|
||||
copy ? 'hidden' : 'block'
|
||||
}`}
|
||||
>
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
d="M8.25 7.5V6.108c0-1.135.845-2.098 1.976-2.192.373-.03.748-.057 1.123-.08M15.75 18H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08M15.75 18.75v-1.875a3.375 3.375 0 00-3.375-3.375h-1.5a1.125 1.125 0 01-1.125-1.125v-1.5A3.375 3.375 0 006.375 7.5H5.25m11.9-3.664A2.251 2.251 0 0015 2.25h-1.5a2.251 2.251 0 00-2.15 1.586m5.8 0c.065.21.1.433.1.664v.75h-6V4.5c0-.231.035-.454.1-.664M6.75 7.5H4.875c-.621 0-1.125.504-1.125 1.125v12c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V16.5a9 9 0 00-9-9z"
|
||||
/>
|
||||
</svg>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
version="1.1"
|
||||
className={`w-6 h-6 hover:border hover:border-indigo-500 ${
|
||||
copy ? 'block bg-green-300' : 'hidden'
|
||||
}`}
|
||||
>
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
d="M13.78 4.22a.75.75 0 010 1.06l-7.25 7.25a.75.75 0 01-1.06 0L2.22 9.28a.75.75 0 011.06-1.06L6 10.94l6.72-6.72a.75.75 0 011.06 0z"
|
||||
></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function ShareAnything({
|
||||
handleShare,
|
||||
}: {
|
||||
handleShare: (text: string) => void;
|
||||
}) {
|
||||
const [text, setText] = useState('');
|
||||
return (
|
||||
<div className="mt-4">
|
||||
<label
|
||||
htmlFor="comment"
|
||||
className="block text-sm font-medium text-gray-700"
|
||||
>
|
||||
随意要分享的内容.
|
||||
</label>
|
||||
<div className="mt-1">
|
||||
<textarea
|
||||
rows={4}
|
||||
name="comment"
|
||||
id="comment"
|
||||
className="block w-full border border-gray-300 rounded-md shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm"
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex justify-end mt-2">
|
||||
<button
|
||||
onClick={() => handleShare(text)}
|
||||
type="submit"
|
||||
className="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-transparent rounded-md shadow-sm hover:border-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
|
||||
>
|
||||
分享
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ShareActions({
|
||||
handleShare,
|
||||
}: {
|
||||
handleShare: (text: string) => void;
|
||||
}) {
|
||||
function getPageURL() {
|
||||
return window.location.href;
|
||||
}
|
||||
function getVlessURL() {
|
||||
const url = new URL(window.location.href);
|
||||
const uuid = url.pathname.split('/').find(uuidValidate);
|
||||
return `vless://${uuid}@${url.hostname}:443?encryption=none&security=tls&type=ws#deno-vless`;
|
||||
}
|
||||
return (
|
||||
<span className="inline-flex self-center mt-4 rounded-md shadow-sm isolate">
|
||||
<button
|
||||
onClick={() => handleShare(getPageURL())}
|
||||
type="button"
|
||||
className="relative inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-l-md hover:border-indigo-500 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
||||
>
|
||||
分享本页
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleShare(getVlessURL())}
|
||||
type="button"
|
||||
className="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
|
||||
>
|
||||
分享 V2ray
|
||||
</button>
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function Warning() {
|
||||
return (
|
||||
<div className="flex justify-center w-full p-4 rounded-md bg-red-50">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<ExclamationTriangleIcon
|
||||
className="w-5 h-5 text-red-700"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<h3 className="text-sm font-medium text-red-700">注意!!</h3>
|
||||
<div className="mt-2 text-sm text-red-700">
|
||||
<p>泄露本页面就等于泄露你的设置。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default EdgeApp;
|
||||
17
libs/edge-ui/tailwind.config.js
Normal file
17
libs/edge-ui/tailwind.config.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const { createGlobPatternsForDependencies } = require('@nrwl/react/tailwind');
|
||||
const { join } = require('path');
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: [
|
||||
join(
|
||||
__dirname,
|
||||
'{src,pages,components}/**/*!(*.stories|*.spec).{ts,tsx,html}'
|
||||
),
|
||||
...createGlobPatternsForDependencies(__dirname),
|
||||
],
|
||||
theme: {
|
||||
extend: {},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
20
libs/edge-ui/tsconfig.json
Normal file
20
libs/edge-ui/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": false,
|
||||
"esModuleInterop": false,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
},
|
||||
{
|
||||
"path": "./tsconfig.spec.json"
|
||||
}
|
||||
],
|
||||
"extends": "../../tsconfig.base.json"
|
||||
}
|
||||
22
libs/edge-ui/tsconfig.lib.json
Normal file
22
libs/edge-ui/tsconfig.lib.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["node"]
|
||||
},
|
||||
"files": [
|
||||
"../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||
"../../node_modules/@nrwl/react/typings/image.d.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"**/*.spec.ts",
|
||||
"**/*.test.ts",
|
||||
"**/*.spec.tsx",
|
||||
"**/*.test.tsx",
|
||||
"**/*.spec.js",
|
||||
"**/*.test.js",
|
||||
"**/*.spec.jsx",
|
||||
"**/*.test.jsx"
|
||||
],
|
||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||
}
|
||||
19
libs/edge-ui/tsconfig.spec.json
Normal file
19
libs/edge-ui/tsconfig.spec.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "../../dist/out-tsc",
|
||||
"types": ["vitest/globals", "node"]
|
||||
},
|
||||
"include": [
|
||||
"vite.config.ts",
|
||||
"src/**/*.test.ts",
|
||||
"src/**/*.spec.ts",
|
||||
"src/**/*.test.tsx",
|
||||
"src/**/*.spec.tsx",
|
||||
"src/**/*.test.js",
|
||||
"src/**/*.spec.js",
|
||||
"src/**/*.test.jsx",
|
||||
"src/**/*.spec.jsx",
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
26
libs/edge-ui/vite.config.ts
Normal file
26
libs/edge-ui/vite.config.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/// <reference types="vitest" />
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import viteTsConfigPaths from 'vite-tsconfig-paths';
|
||||
|
||||
export default defineConfig({
|
||||
server: {
|
||||
port: 4200,
|
||||
host: 'localhost',
|
||||
},
|
||||
plugins: [
|
||||
react(),
|
||||
viteTsConfigPaths({
|
||||
root: '../../',
|
||||
}),
|
||||
],
|
||||
|
||||
test: {
|
||||
globals: true,
|
||||
cache: {
|
||||
dir: '../../node_modules/.vitest',
|
||||
},
|
||||
environment: 'jsdom',
|
||||
include: ['src/**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user