mirror of
https://github.com/NebulaServices/Nebula.git
synced 2025-05-13 12:00:01 -04:00
Prettier Format
This commit is contained in:
parent
3a9024d1e0
commit
ad67368a45
42 changed files with 1650 additions and 1609 deletions
8
.prettierignore
Normal file
8
.prettierignore
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Build artifacts
|
||||
dist/
|
||||
|
||||
# Other things that don't need to be reformatted
|
||||
public/uv/
|
||||
public/dynamic/
|
||||
pnpm-lock.yaml
|
||||
package-lock.json
|
16
.prettierrc
16
.prettierrc
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"singleQuote": false,
|
||||
"endOfLine": "crlf",
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"trailingComma": "none",
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
{
|
||||
"singleQuote": false,
|
||||
"endOfLine": "crlf",
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"trailingComma": "none",
|
||||
"plugins": ["prettier-plugin-tailwindcss"]
|
||||
}
|
||||
|
|
24
.vscode/settings.json
vendored
24
.vscode/settings.json
vendored
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"files.autoSave": "afterDelay",
|
||||
"files.autoSaveDelay": 0,
|
||||
"css.lint.unknownAtRules": "ignore",
|
||||
"editor.linkedEditing": true,
|
||||
"css.lint.unknownProperties": "ignore"
|
||||
}
|
||||
{
|
||||
"editor.tabSize": 2,
|
||||
"editor.insertSpaces": true,
|
||||
"editor.formatOnSave": true,
|
||||
"editor.formatOnPaste": true,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"files.autoSave": "afterDelay",
|
||||
"files.autoSaveDelay": 0,
|
||||
"css.lint.unknownAtRules": "ignore",
|
||||
"editor.linkedEditing": true,
|
||||
"css.lint.unknownProperties": "ignore"
|
||||
}
|
||||
|
|
15
README.md
15
README.md
|
@ -1,7 +1,8 @@
|
|||
# Nebula rewrite
|
||||
`npm i -g pnpm tsx`
|
||||
`git clone https://github.com/NebulaServices/Nebula.git`
|
||||
`git switch rewrite`
|
||||
`pnpm i`
|
||||
|
||||
readme meant for devs, will be changed before being merged into main
|
||||
# Nebula rewrite
|
||||
|
||||
`npm i -g pnpm tsx`
|
||||
`git clone https://github.com/NebulaServices/Nebula.git`
|
||||
`git switch rewrite`
|
||||
`pnpm i`
|
||||
|
||||
readme meant for devs, will be changed before being merged into main
|
||||
|
|
14
framer-motion.d.ts
vendored
14
framer-motion.d.ts
vendored
|
@ -1,7 +1,7 @@
|
|||
import * as React from "preact/compat";
|
||||
|
||||
declare module "framer-motion" {
|
||||
export interface AnimatePresenceProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
}
|
||||
import * as React from "preact/compat";
|
||||
|
||||
declare module "framer-motion" {
|
||||
export interface AnimatePresenceProps {
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
}
|
||||
|
|
58
index.html
58
index.html
|
@ -1,29 +1,29 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<title>Nebula</title>
|
||||
<script src="/uv/uv.bundle.js"></script>
|
||||
<script src="/uv/uv.config.js"></script>
|
||||
<script src="/dynamic/dynamic.config.js"></script>
|
||||
<script>
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
navigator.serviceWorker.register("/uvsw.js", {
|
||||
scope: __uv$config.prefix
|
||||
});
|
||||
navigator.serviceWorker.register("/dysw.js", {
|
||||
scope: __dynamic$config.prefix
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/logo.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="color-scheme" content="light dark" />
|
||||
<title>Nebula</title>
|
||||
<script src="/uv/uv.bundle.js"></script>
|
||||
<script src="/uv/uv.config.js"></script>
|
||||
<script src="/dynamic/dynamic.config.js"></script>
|
||||
<script>
|
||||
if ("serviceWorker" in navigator) {
|
||||
window.addEventListener("load", () => {
|
||||
navigator.serviceWorker.register("/uvsw.js", {
|
||||
scope: __uv$config.prefix
|
||||
});
|
||||
navigator.serviceWorker.register("/dysw.js", {
|
||||
scope: __dynamic$config.prefix
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/index.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
92
package.json
92
package.json
|
@ -1,46 +1,46 @@
|
|||
{
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"vite\" \"bare-server-node --port 8080\"",
|
||||
"build": "vite build",
|
||||
"bstart": "npm run build && tsx server.ts",
|
||||
"preview": "vite preview",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/compress": "^6.5.0",
|
||||
"@fastify/static": "^6.12.0",
|
||||
"@titaniumnetwork-dev/ultraviolet": "^2.0.0",
|
||||
"@tomphttp/bare-server-node": "^2.0.1",
|
||||
"classnames": "^2.3.2",
|
||||
"fastify": "^4.25.1",
|
||||
"framer-motion": "^10.16.16",
|
||||
"i18next": "^23.7.9",
|
||||
"i18next-browser-languagedetector": "^7.2.0",
|
||||
"million": "^2.6.4",
|
||||
"preact": "^10.13.1",
|
||||
"preact-iso": "^2.3.2",
|
||||
"preact-render-to-string": "^6.3.1",
|
||||
"preact-router": "^4.1.2",
|
||||
"rammerhead": "https://github.com/holy-unblocker/rammerhead/releases/download/v1.2.41-holy.5/rammerhead-1.2.41-holy.5.tgz",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^13.5.0",
|
||||
"react-icons": "^4.12.0",
|
||||
"tsx": "^4.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "^2.5.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-preact": "^1.3.0",
|
||||
"postcss": "^8.4.32",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-tailwindcss": "^0.5.9",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.9",
|
||||
"vite-plugin-static-copy": "^1.0.0"
|
||||
}
|
||||
}
|
||||
{
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "concurrently \"vite\" \"bare-server-node --port 8080\"",
|
||||
"build": "vite build",
|
||||
"bstart": "npm run build && tsx server.ts",
|
||||
"preview": "vite preview",
|
||||
"format": "prettier --write ."
|
||||
},
|
||||
"dependencies": {
|
||||
"@fastify/compress": "^6.5.0",
|
||||
"@fastify/static": "^6.12.0",
|
||||
"@titaniumnetwork-dev/ultraviolet": "^2.0.0",
|
||||
"@tomphttp/bare-server-node": "^2.0.1",
|
||||
"classnames": "^2.3.2",
|
||||
"fastify": "^4.25.1",
|
||||
"framer-motion": "^10.16.16",
|
||||
"i18next": "^23.7.9",
|
||||
"i18next-browser-languagedetector": "^7.2.0",
|
||||
"million": "^2.6.4",
|
||||
"preact": "^10.13.1",
|
||||
"preact-iso": "^2.3.2",
|
||||
"preact-render-to-string": "^6.3.1",
|
||||
"preact-router": "^4.1.2",
|
||||
"rammerhead": "https://github.com/holy-unblocker/rammerhead/releases/download/v1.2.41-holy.5/rammerhead-1.2.41-holy.5.tgz",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^13.5.0",
|
||||
"react-icons": "^4.12.0",
|
||||
"tsx": "^4.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@preact/preset-vite": "^2.5.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"concurrently": "^8.2.2",
|
||||
"eslint": "^8.55.0",
|
||||
"eslint-config-preact": "^1.3.0",
|
||||
"postcss": "^8.4.32",
|
||||
"prettier": "^3.1.1",
|
||||
"prettier-plugin-tailwindcss": "^0.5.9",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^5.0.9",
|
||||
"vite-plugin-static-copy": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
export default {
|
||||
plugins: {
|
||||
tailwindcss: {},
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
importScripts("/dynamic/dynamic.config.js");
|
||||
importScripts("/dynamic/dynamic.worker.js");
|
||||
|
||||
const dynamic = new Dynamic();
|
||||
|
||||
self.dynamic = dynamic;
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
if (
|
||||
event.request.url.startsWith(location.origin + self.__dynamic$config.prefix)
|
||||
)
|
||||
event.respondWith(
|
||||
(async function () {
|
||||
if (await dynamic.route(event)) {
|
||||
return await dynamic.fetch(event);
|
||||
}
|
||||
|
||||
return await fetch(event.request);
|
||||
})()
|
||||
);
|
||||
});
|
||||
importScripts("/dynamic/dynamic.config.js");
|
||||
importScripts("/dynamic/dynamic.worker.js");
|
||||
|
||||
const dynamic = new Dynamic();
|
||||
|
||||
self.dynamic = dynamic;
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
if (
|
||||
event.request.url.startsWith(location.origin + self.__dynamic$config.prefix)
|
||||
)
|
||||
event.respondWith(
|
||||
(async function () {
|
||||
if (await dynamic.route(event)) {
|
||||
return await dynamic.fetch(event);
|
||||
}
|
||||
|
||||
return await fetch(event.request);
|
||||
})()
|
||||
);
|
||||
});
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
importScripts("/uv/uv.bundle.js");
|
||||
importScripts("/uv/uv.config.js");
|
||||
importScripts(__uv$config.sw || "/uv/uv.sw.js");
|
||||
|
||||
const sw = new UVServiceWorker();
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
if (event.request.url.startsWith(location.origin + __uv$config.prefix))
|
||||
return event.respondWith(sw.fetch(event));
|
||||
});
|
||||
importScripts("/uv/uv.bundle.js");
|
||||
importScripts("/uv/uv.config.js");
|
||||
importScripts(__uv$config.sw || "/uv/uv.sw.js");
|
||||
|
||||
const sw = new UVServiceWorker();
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
if (event.request.url.startsWith(location.origin + __uv$config.prefix))
|
||||
return event.respondWith(sw.fetch(event));
|
||||
});
|
||||
|
|
172
server.ts
172
server.ts
|
@ -1,86 +1,86 @@
|
|||
import fastify from 'fastify';
|
||||
import fastifyStatic from '@fastify/static';
|
||||
import { fileURLToPath } from 'url';
|
||||
import path from 'path';
|
||||
import createRammerhead from "rammerhead/src/server/index.js";
|
||||
import { createBareServer } from "@tomphttp/bare-server-node";
|
||||
import { createServer } from "http";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const bare = createBareServer("/bare/");
|
||||
const rh = createRammerhead();
|
||||
|
||||
const rammerheadScopes = [
|
||||
"/rammerhead.js",
|
||||
"/hammerhead.js",
|
||||
"/transport-worker.js",
|
||||
"/task.js",
|
||||
"/iframe-task.js",
|
||||
"/worker-hammerhead.js",
|
||||
"/messaging",
|
||||
"/sessionexists",
|
||||
"/deletesession",
|
||||
"/newsession",
|
||||
"/editsession",
|
||||
"/needpassword",
|
||||
"/syncLocalStorage",
|
||||
"/api/shuffleDict",
|
||||
"/mainport"
|
||||
];
|
||||
|
||||
const rammerheadSession = /^\/[a-z0-9]{32}/;
|
||||
|
||||
function shouldRouteRh(req) {
|
||||
const url = new URL(req.url, "http://0.0.0.0");
|
||||
return (
|
||||
rammerheadScopes.includes(url.pathname) ||
|
||||
rammerheadSession.test(url.pathname)
|
||||
);
|
||||
}
|
||||
|
||||
function routeRhRequest(req, res) {
|
||||
rh.emit("request", req, res);
|
||||
}
|
||||
|
||||
function routeRhUpgrade(req, socket, head) {
|
||||
rh.emit("upgrade", req, socket, head);
|
||||
}
|
||||
|
||||
const serverFactory = (handler, opts) => {
|
||||
return createServer()
|
||||
.on("request", (req, res) => {
|
||||
if (bare.shouldRoute(req)) {
|
||||
bare.routeRequest(req, res);
|
||||
} else if (shouldRouteRh(req)) {
|
||||
routeRhRequest(req, res);
|
||||
} else {
|
||||
handler(req, res);
|
||||
}
|
||||
})
|
||||
.on("upgrade", (req, socket, head) => {
|
||||
if (bare.shouldRoute(req)) {
|
||||
bare.routeUpgrade(req, socket, head);
|
||||
} else if (shouldRouteRh(req)) {
|
||||
routeRhUpgrade(req, socket, head);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const app = fastify({ logger: true, serverFactory });
|
||||
|
||||
app.register(fastifyStatic, {
|
||||
root: path.join(__dirname, 'dist'),
|
||||
prefix: '/',
|
||||
serve: true,
|
||||
wildcard: false,
|
||||
});
|
||||
|
||||
app.setNotFoundHandler((req, res) => {
|
||||
res.sendFile('index.html') // SPA catch-all
|
||||
})
|
||||
|
||||
app.listen({
|
||||
port: 8080
|
||||
});
|
||||
import fastify from "fastify";
|
||||
import fastifyStatic from "@fastify/static";
|
||||
import { fileURLToPath } from "url";
|
||||
import path from "path";
|
||||
import createRammerhead from "rammerhead/src/server/index.js";
|
||||
import { createBareServer } from "@tomphttp/bare-server-node";
|
||||
import { createServer } from "http";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const bare = createBareServer("/bare/");
|
||||
const rh = createRammerhead();
|
||||
|
||||
const rammerheadScopes = [
|
||||
"/rammerhead.js",
|
||||
"/hammerhead.js",
|
||||
"/transport-worker.js",
|
||||
"/task.js",
|
||||
"/iframe-task.js",
|
||||
"/worker-hammerhead.js",
|
||||
"/messaging",
|
||||
"/sessionexists",
|
||||
"/deletesession",
|
||||
"/newsession",
|
||||
"/editsession",
|
||||
"/needpassword",
|
||||
"/syncLocalStorage",
|
||||
"/api/shuffleDict",
|
||||
"/mainport"
|
||||
];
|
||||
|
||||
const rammerheadSession = /^\/[a-z0-9]{32}/;
|
||||
|
||||
function shouldRouteRh(req) {
|
||||
const url = new URL(req.url, "http://0.0.0.0");
|
||||
return (
|
||||
rammerheadScopes.includes(url.pathname) ||
|
||||
rammerheadSession.test(url.pathname)
|
||||
);
|
||||
}
|
||||
|
||||
function routeRhRequest(req, res) {
|
||||
rh.emit("request", req, res);
|
||||
}
|
||||
|
||||
function routeRhUpgrade(req, socket, head) {
|
||||
rh.emit("upgrade", req, socket, head);
|
||||
}
|
||||
|
||||
const serverFactory = (handler, opts) => {
|
||||
return createServer()
|
||||
.on("request", (req, res) => {
|
||||
if (bare.shouldRoute(req)) {
|
||||
bare.routeRequest(req, res);
|
||||
} else if (shouldRouteRh(req)) {
|
||||
routeRhRequest(req, res);
|
||||
} else {
|
||||
handler(req, res);
|
||||
}
|
||||
})
|
||||
.on("upgrade", (req, socket, head) => {
|
||||
if (bare.shouldRoute(req)) {
|
||||
bare.routeUpgrade(req, socket, head);
|
||||
} else if (shouldRouteRh(req)) {
|
||||
routeRhUpgrade(req, socket, head);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const app = fastify({ logger: true, serverFactory });
|
||||
|
||||
app.register(fastifyStatic, {
|
||||
root: path.join(__dirname, "dist"),
|
||||
prefix: "/",
|
||||
serve: true,
|
||||
wildcard: false
|
||||
});
|
||||
|
||||
app.setNotFoundHandler((req, res) => {
|
||||
res.sendFile("index.html"); // SPA catch-all
|
||||
});
|
||||
|
||||
app.listen({
|
||||
port: 8080
|
||||
});
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Header } from "./Header";
|
||||
|
||||
export function HeaderRoute(props: { children: any }) {
|
||||
return (
|
||||
<div class="flex h-screen flex-col">
|
||||
<Header />
|
||||
<div class="flex-1 bg-primary">
|
||||
<main class="h-full">{props.children}</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import { Header } from "./Header";
|
||||
|
||||
export function HeaderRoute(props: { children: any }) {
|
||||
return (
|
||||
<div class="flex h-screen flex-col">
|
||||
<Header />
|
||||
<div class="flex-1 bg-primary">
|
||||
<main class="h-full">{props.children}</main>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,13 +1,23 @@
|
|||
import { motion } from "framer-motion"
|
||||
import { IframeHeader } from "./IframeHeader"
|
||||
|
||||
export function Iframe(props: { url: string }) {
|
||||
return (
|
||||
<>
|
||||
<IframeHeader url={ props.url } />
|
||||
<motion.div className="w-full h-[calc(100%_-_4rem)] bg-primary" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.5 }} >
|
||||
<iframe id="iframe" src={ props.url } className="w-full h-full border-none bg-primary" />
|
||||
</motion.div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
import { motion } from "framer-motion";
|
||||
import { IframeHeader } from "./IframeHeader";
|
||||
|
||||
export function Iframe(props: { url: string }) {
|
||||
return (
|
||||
<>
|
||||
<IframeHeader url={props.url} />
|
||||
<motion.div
|
||||
className="h-[calc(100%_-_4rem)] w-full bg-primary"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<iframe
|
||||
id="iframe"
|
||||
src={props.url}
|
||||
className="h-full w-full border-none bg-primary"
|
||||
/>
|
||||
</motion.div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,33 +1,48 @@
|
|||
import { useState } from "preact/hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "preact-router";
|
||||
import { RiPictureInPictureExitFill, RiFullscreenFill } from "react-icons/ri"
|
||||
|
||||
export function IframeHeader(props: { url: string }) {
|
||||
const { t } = useTranslation()
|
||||
const [showPopout, setShowPopout] = useState(false);
|
||||
const [showFullScreen, setFullScreen] = useState(false);
|
||||
if (showPopout) {
|
||||
window.location.replace(props.url);
|
||||
}
|
||||
if (showFullScreen) {
|
||||
document.getElementById("iframe").requestFullscreen();
|
||||
setFullScreen(false);
|
||||
}
|
||||
return (
|
||||
<div id="iframeNav" className="flex h-16 flex-row items-center justify-between bg-navbar-color px-4">
|
||||
<Link href="/" class="w-1/2">
|
||||
<div className="flex flex-row items-center">
|
||||
<img src="/logo.png" className="h-16 w-16 transition-all duration-1000 hover:rotate-[360deg]"></img>
|
||||
<h1 className="font-roboto text-2xl font-bold text-navbar-text-color md:text-4xl"> {t("header.title")} </h1>
|
||||
</div>
|
||||
</Link>
|
||||
<div id="navItems" class="w-1/2">
|
||||
<div className="flex flex-row items-center justify-end gap-3 mr-4">
|
||||
<RiPictureInPictureExitFill className="h-6 w-6 cursor-pointer transition-all duration-0500 text-navbar-text-color hover:scale-110 hover:brightness-125" onClick={() => setShowPopout(true)} />
|
||||
<RiFullscreenFill className="h-6 w-6 cursor-pointer transition-all duration-0500 text-navbar-text-color hover:scale-110 hover:brightness-125 active:rotate-90" onClick={() => setFullScreen(true)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
};
|
||||
import { useState } from "preact/hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "preact-router";
|
||||
import { RiPictureInPictureExitFill, RiFullscreenFill } from "react-icons/ri";
|
||||
|
||||
export function IframeHeader(props: { url: string }) {
|
||||
const { t } = useTranslation();
|
||||
const [showPopout, setShowPopout] = useState(false);
|
||||
const [showFullScreen, setFullScreen] = useState(false);
|
||||
if (showPopout) {
|
||||
window.location.replace(props.url);
|
||||
}
|
||||
if (showFullScreen) {
|
||||
document.getElementById("iframe").requestFullscreen();
|
||||
setFullScreen(false);
|
||||
}
|
||||
return (
|
||||
<div
|
||||
id="iframeNav"
|
||||
className="flex h-16 flex-row items-center justify-between bg-navbar-color px-4"
|
||||
>
|
||||
<Link href="/" class="w-1/2">
|
||||
<div className="flex flex-row items-center">
|
||||
<img
|
||||
src="/logo.png"
|
||||
className="h-16 w-16 transition-all duration-1000 hover:rotate-[360deg]"
|
||||
></img>
|
||||
<h1 className="font-roboto text-2xl font-bold text-navbar-text-color md:text-4xl">
|
||||
{" "}
|
||||
{t("header.title")}{" "}
|
||||
</h1>
|
||||
</div>
|
||||
</Link>
|
||||
<div id="navItems" class="w-1/2">
|
||||
<div className="mr-4 flex flex-row items-center justify-end gap-3">
|
||||
<RiPictureInPictureExitFill
|
||||
className="duration-0500 h-6 w-6 cursor-pointer text-navbar-text-color transition-all hover:scale-110 hover:brightness-125"
|
||||
onClick={() => setShowPopout(true)}
|
||||
/>
|
||||
<RiFullscreenFill
|
||||
className="duration-0500 h-6 w-6 cursor-pointer text-navbar-text-color transition-all hover:scale-110 hover:brightness-125 active:rotate-90"
|
||||
onClick={() => setFullScreen(true)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
64
src/i18n.ts
64
src/i18n.ts
|
@ -1,32 +1,32 @@
|
|||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
|
||||
import translationEN from "./locales/en.json";
|
||||
import translationES from "./locales/es.json";
|
||||
import translationJA from "./locales/ja.json";
|
||||
|
||||
const resources = {
|
||||
en: {
|
||||
translation: translationEN
|
||||
},
|
||||
es: {
|
||||
translation: translationES
|
||||
},
|
||||
ja: {
|
||||
translation: translationJA
|
||||
}
|
||||
};
|
||||
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.use(LanguageDetector)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: "en",
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
}
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
import i18n from "i18next";
|
||||
import { initReactI18next } from "react-i18next";
|
||||
import LanguageDetector from "i18next-browser-languagedetector";
|
||||
|
||||
import translationEN from "./locales/en.json";
|
||||
import translationES from "./locales/es.json";
|
||||
import translationJA from "./locales/ja.json";
|
||||
|
||||
const resources = {
|
||||
en: {
|
||||
translation: translationEN
|
||||
},
|
||||
es: {
|
||||
translation: translationES
|
||||
},
|
||||
ja: {
|
||||
translation: translationJA
|
||||
}
|
||||
};
|
||||
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.use(LanguageDetector)
|
||||
.init({
|
||||
resources,
|
||||
fallbackLng: "en",
|
||||
interpolation: {
|
||||
escapeValue: false
|
||||
}
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
|
|
|
@ -1,28 +1,27 @@
|
|||
import { render } from "preact";
|
||||
import { LocationProvider, Router, Route } from "preact-iso";
|
||||
import { Home } from "./pages/Home";
|
||||
import { NotFound } from "./pages/_404.jsx";
|
||||
import { DiscordPage } from "./pages/discord.jsx";
|
||||
import { ProxyFrame } from "./pages/ProxyFrame.js";
|
||||
import { Settings } from "./pages/Settings/index.js";
|
||||
|
||||
import "./style.css";
|
||||
import "./themes/main.css";
|
||||
import "./i18n";
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<LocationProvider>
|
||||
<Router>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/discord" component={DiscordPage} />
|
||||
<Route path="/go/:url" component={ProxyFrame} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route default component={NotFound} />
|
||||
</Router>
|
||||
</LocationProvider>
|
||||
);
|
||||
}
|
||||
|
||||
render(<App />, document.getElementById("app"));
|
||||
|
||||
import { render } from "preact";
|
||||
import { LocationProvider, Router, Route } from "preact-iso";
|
||||
import { Home } from "./pages/Home";
|
||||
import { NotFound } from "./pages/_404.jsx";
|
||||
import { DiscordPage } from "./pages/discord.jsx";
|
||||
import { ProxyFrame } from "./pages/ProxyFrame.js";
|
||||
import { Settings } from "./pages/Settings/index.js";
|
||||
|
||||
import "./style.css";
|
||||
import "./themes/main.css";
|
||||
import "./i18n";
|
||||
|
||||
export function App() {
|
||||
return (
|
||||
<LocationProvider>
|
||||
<Router>
|
||||
<Route path="/" component={Home} />
|
||||
<Route path="/discord" component={DiscordPage} />
|
||||
<Route path="/go/:url" component={ProxyFrame} />
|
||||
<Route path="/settings" component={Settings} />
|
||||
<Route default component={NotFound} />
|
||||
</Router>
|
||||
</LocationProvider>
|
||||
);
|
||||
}
|
||||
|
||||
render(<App />, document.getElementById("app"));
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
{
|
||||
"header": {
|
||||
"title": "Nebel",
|
||||
"games": "Spiele",
|
||||
"settings": "einstellungen",
|
||||
"discord": "Sie wollen mehr URL-Adresse?"
|
||||
},
|
||||
"404": {
|
||||
"text": "Dieser Nebel-Dienst wurde deaktiviert.",
|
||||
"return": "Zurück nach Hause."
|
||||
},
|
||||
"home": {
|
||||
"placeholder": "Suchen Sie frei im Web."
|
||||
},
|
||||
"discord": {
|
||||
"title": "Nebel's Discord Server",
|
||||
"sub": "Möchten Sie diesen über einen Proxy öffnen?",
|
||||
"button1": "Normal öffnen",
|
||||
"button2": "Proxy verwenden"
|
||||
},
|
||||
"setting": {
|
||||
"tabs": {
|
||||
"proxy": "Proxy",
|
||||
"tab": "tab",
|
||||
"custom": "Anpassung",
|
||||
"misc": "Misc"
|
||||
},
|
||||
"proxy": {
|
||||
"title": "Proxy",
|
||||
"subtitle": "Wählen Sie den Proxy, der Ihren Anforderungen entspricht",
|
||||
"automatic": "Automatisch",
|
||||
"buggyWarning": "(PROBLEME)"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Sprache",
|
||||
"subtitle": "Wählen Sie Ihre bevorzugte Sprache",
|
||||
"japanese": "Japanisch",
|
||||
"english": "Englisch",
|
||||
"spanish": "Spanisch",
|
||||
"german": "Deutsch",
|
||||
"greek": "Griechisch",
|
||||
"dutch": "Niederländisch"
|
||||
},
|
||||
"proxymodes": {
|
||||
"title": "Öffnen in",
|
||||
"subtitle": "Wählen Sie, wie Ihre Seiten geöffnet werden sollen",
|
||||
"embed": "Einbetten",
|
||||
"direct": "Direkt",
|
||||
"aboutblank": "About:Blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"header": {
|
||||
"title": "Nebel",
|
||||
"games": "Spiele",
|
||||
"settings": "einstellungen",
|
||||
"discord": "Sie wollen mehr URL-Adresse?"
|
||||
},
|
||||
"404": {
|
||||
"text": "Dieser Nebel-Dienst wurde deaktiviert.",
|
||||
"return": "Zurück nach Hause."
|
||||
},
|
||||
"home": {
|
||||
"placeholder": "Suchen Sie frei im Web."
|
||||
},
|
||||
"discord": {
|
||||
"title": "Nebel's Discord Server",
|
||||
"sub": "Möchten Sie diesen über einen Proxy öffnen?",
|
||||
"button1": "Normal öffnen",
|
||||
"button2": "Proxy verwenden"
|
||||
},
|
||||
"setting": {
|
||||
"tabs": {
|
||||
"proxy": "Proxy",
|
||||
"tab": "tab",
|
||||
"custom": "Anpassung",
|
||||
"misc": "Misc"
|
||||
},
|
||||
"proxy": {
|
||||
"title": "Proxy",
|
||||
"subtitle": "Wählen Sie den Proxy, der Ihren Anforderungen entspricht",
|
||||
"automatic": "Automatisch",
|
||||
"buggyWarning": "(PROBLEME)"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Sprache",
|
||||
"subtitle": "Wählen Sie Ihre bevorzugte Sprache",
|
||||
"japanese": "Japanisch",
|
||||
"english": "Englisch",
|
||||
"spanish": "Spanisch",
|
||||
"german": "Deutsch",
|
||||
"greek": "Griechisch",
|
||||
"dutch": "Niederländisch"
|
||||
},
|
||||
"proxymodes": {
|
||||
"title": "Öffnen in",
|
||||
"subtitle": "Wählen Sie, wie Ihre Seiten geöffnet werden sollen",
|
||||
"embed": "Einbetten",
|
||||
"direct": "Direkt",
|
||||
"aboutblank": "About:Blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
{
|
||||
"header": {
|
||||
"title": "nebula.",
|
||||
"games": "Games",
|
||||
"settings": "Settings",
|
||||
"discord": "Want more links?"
|
||||
},
|
||||
"404": {
|
||||
"text": "This Nebula Service has been disabled.",
|
||||
"return": "Back to home"
|
||||
},
|
||||
"home": {
|
||||
"placeholder": "Search the web freely"
|
||||
},
|
||||
"discord": {
|
||||
"title": "Nebula's Discord Server",
|
||||
"sub": "Would you like to open this via proxy?",
|
||||
"button1": "Open Normally",
|
||||
"button2": "Use Proxy"
|
||||
},
|
||||
"settings": {
|
||||
"tabs": {
|
||||
"proxy": "Proxy",
|
||||
"tab": "Tab",
|
||||
"custom": "Customization",
|
||||
"misc": "Misc"
|
||||
},
|
||||
"proxy": {
|
||||
"title": "Proxy",
|
||||
"subtitle": "Choose the proxy that fits your needs",
|
||||
"automatic": "Automatic",
|
||||
"buggyWarning": "(BUGGY)"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Language",
|
||||
"subtitle": "Choose your preferred language",
|
||||
"japanese": "Japanese",
|
||||
"english": "English",
|
||||
"spanish": "Spanish",
|
||||
"german": "German",
|
||||
"greek": "Greek",
|
||||
"dutch": "Dutch"
|
||||
},
|
||||
"proxymodes": {
|
||||
"title": "Open in",
|
||||
"subtitle": "Choose how to open your sites",
|
||||
"embed": "Embed",
|
||||
"direct": "Direct",
|
||||
"aboutblank": "About:Blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"header": {
|
||||
"title": "nebula.",
|
||||
"games": "Games",
|
||||
"settings": "Settings",
|
||||
"discord": "Want more links?"
|
||||
},
|
||||
"404": {
|
||||
"text": "This Nebula Service has been disabled.",
|
||||
"return": "Back to home"
|
||||
},
|
||||
"home": {
|
||||
"placeholder": "Search the web freely"
|
||||
},
|
||||
"discord": {
|
||||
"title": "Nebula's Discord Server",
|
||||
"sub": "Would you like to open this via proxy?",
|
||||
"button1": "Open Normally",
|
||||
"button2": "Use Proxy"
|
||||
},
|
||||
"settings": {
|
||||
"tabs": {
|
||||
"proxy": "Proxy",
|
||||
"tab": "Tab",
|
||||
"custom": "Customization",
|
||||
"misc": "Misc"
|
||||
},
|
||||
"proxy": {
|
||||
"title": "Proxy",
|
||||
"subtitle": "Choose the proxy that fits your needs",
|
||||
"automatic": "Automatic",
|
||||
"buggyWarning": "(BUGGY)"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Language",
|
||||
"subtitle": "Choose your preferred language",
|
||||
"japanese": "Japanese",
|
||||
"english": "English",
|
||||
"spanish": "Spanish",
|
||||
"german": "German",
|
||||
"greek": "Greek",
|
||||
"dutch": "Dutch"
|
||||
},
|
||||
"proxymodes": {
|
||||
"title": "Open in",
|
||||
"subtitle": "Choose how to open your sites",
|
||||
"embed": "Embed",
|
||||
"direct": "Direct",
|
||||
"aboutblank": "About:Blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
{
|
||||
"header": {
|
||||
"title": "nebula.",
|
||||
"games": "Juegos",
|
||||
"settings": "Ajustes",
|
||||
"discord": "¿Quieres más enlaces?"
|
||||
},
|
||||
"404": {
|
||||
"text": "Este servicio Nebula ha sido deshabilitado.",
|
||||
"return": "De vuelta a casa"
|
||||
},
|
||||
"home": {
|
||||
"placeholder": "Busca en la web libremente"
|
||||
},
|
||||
"discord": {
|
||||
"title": "Nebula's Discord Servidor",
|
||||
"sub": "¿Le gustaría abrir esto a través de proxy?",
|
||||
"button1": "Abierto normalmente",
|
||||
"button2": "Usa proxy"
|
||||
},
|
||||
"settings": {
|
||||
"tabs": {
|
||||
"proxy": "Proxy",
|
||||
"tab": "Pestaña",
|
||||
"custom": "Personalización",
|
||||
"misc": "Misc"
|
||||
},
|
||||
"proxy": {
|
||||
"title": "Proxy",
|
||||
"subtitle": "Elige el proxy que se ajuste a tus necesidades",
|
||||
"automatic": "Automática",
|
||||
"buggyWarning": "(CALESA)"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Idioma",
|
||||
"subtitle": "Elige tu idioma preferido",
|
||||
"japanese": "Japonés",
|
||||
"english": "Inglés",
|
||||
"spanish": "Español",
|
||||
"german": "",
|
||||
"greek": "",
|
||||
"dutch": ""
|
||||
},
|
||||
"proxymodes": {
|
||||
"title": "Abrir en",
|
||||
"subtitle": "Elige cómo abrir tus sitios",
|
||||
"embed": "Incrustar",
|
||||
"direct": "Directo",
|
||||
"aboutblank": "About:Blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"header": {
|
||||
"title": "nebula.",
|
||||
"games": "Juegos",
|
||||
"settings": "Ajustes",
|
||||
"discord": "¿Quieres más enlaces?"
|
||||
},
|
||||
"404": {
|
||||
"text": "Este servicio Nebula ha sido deshabilitado.",
|
||||
"return": "De vuelta a casa"
|
||||
},
|
||||
"home": {
|
||||
"placeholder": "Busca en la web libremente"
|
||||
},
|
||||
"discord": {
|
||||
"title": "Nebula's Discord Servidor",
|
||||
"sub": "¿Le gustaría abrir esto a través de proxy?",
|
||||
"button1": "Abierto normalmente",
|
||||
"button2": "Usa proxy"
|
||||
},
|
||||
"settings": {
|
||||
"tabs": {
|
||||
"proxy": "Proxy",
|
||||
"tab": "Pestaña",
|
||||
"custom": "Personalización",
|
||||
"misc": "Misc"
|
||||
},
|
||||
"proxy": {
|
||||
"title": "Proxy",
|
||||
"subtitle": "Elige el proxy que se ajuste a tus necesidades",
|
||||
"automatic": "Automática",
|
||||
"buggyWarning": "(CALESA)"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Idioma",
|
||||
"subtitle": "Elige tu idioma preferido",
|
||||
"japanese": "Japonés",
|
||||
"english": "Inglés",
|
||||
"spanish": "Español",
|
||||
"german": "",
|
||||
"greek": "",
|
||||
"dutch": ""
|
||||
},
|
||||
"proxymodes": {
|
||||
"title": "Abrir en",
|
||||
"subtitle": "Elige cómo abrir tus sitios",
|
||||
"embed": "Incrustar",
|
||||
"direct": "Directo",
|
||||
"aboutblank": "About:Blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
{
|
||||
"header": {
|
||||
"title": "νεφέλωμα",
|
||||
"games": "Παιχνίδια",
|
||||
"settings": "Ρυθμίσεις",
|
||||
"discord": "θέλετε περισσότερους συνδέσμους?"
|
||||
},
|
||||
"404": {
|
||||
"text": "Αυτή η υπηρεσία Νεφέλωμα έχει απενεργοποιηθεί.",
|
||||
"return": "Επιστροφή στο σπίτι."
|
||||
},
|
||||
"home": {
|
||||
"placeholder": "Ελεύθερη αναζήτηση στο διαδίκτυο."
|
||||
},
|
||||
"discord": {
|
||||
"title": "Discord Διακομιστής του Νεφελώματος",
|
||||
"sub": "Άνοιγμα μέσω διακομιστή μεσολάβησης;",
|
||||
"button1": "Ανοιχτό κανονικά",
|
||||
"button2": "Χρήση μεσολάβησης"
|
||||
},
|
||||
"setting": {
|
||||
"tabs": {
|
||||
"proxy": "Mεσολάβησης",
|
||||
"tab": "Καρτέλα",
|
||||
"custom": "Προσαρμογή",
|
||||
"misc": "Διάφορα"
|
||||
},
|
||||
"proxy": {
|
||||
"title": "Μεσολάβηση",
|
||||
"subtitle": "Επιλέξτε τη μεσολάβηση που ταιριάζει στις ανάγκες σας",
|
||||
"automatic": "Αυτόματο",
|
||||
"buggyWarning": "(ΠΡΟΒΛΗΜΑ)"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Γλώσσες",
|
||||
"subtitle": "Επιλέξτε τη γλώσσα που προτιμάτε",
|
||||
"japanese": "Ιαπωνικά",
|
||||
"english": "Αγγλικά",
|
||||
"spanish": "Ησπανικά",
|
||||
"german": "Γερμανικό",
|
||||
"greek": "Ελληνικά",
|
||||
"dutch": "Ολλανδικά"
|
||||
},
|
||||
"proxymodes": {
|
||||
"title": "Άνοιγμα σε",
|
||||
"subtitle": "Επιλέξτε τον τρόπο ανοίγματος των ιστοσελίδων σας",
|
||||
"embed": "εΕνσωμάτωση",
|
||||
"direct": "¨Αμεσα",
|
||||
"aboutblank": "About:Blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"header": {
|
||||
"title": "νεφέλωμα",
|
||||
"games": "Παιχνίδια",
|
||||
"settings": "Ρυθμίσεις",
|
||||
"discord": "θέλετε περισσότερους συνδέσμους?"
|
||||
},
|
||||
"404": {
|
||||
"text": "Αυτή η υπηρεσία Νεφέλωμα έχει απενεργοποιηθεί.",
|
||||
"return": "Επιστροφή στο σπίτι."
|
||||
},
|
||||
"home": {
|
||||
"placeholder": "Ελεύθερη αναζήτηση στο διαδίκτυο."
|
||||
},
|
||||
"discord": {
|
||||
"title": "Discord Διακομιστής του Νεφελώματος",
|
||||
"sub": "Άνοιγμα μέσω διακομιστή μεσολάβησης;",
|
||||
"button1": "Ανοιχτό κανονικά",
|
||||
"button2": "Χρήση μεσολάβησης"
|
||||
},
|
||||
"setting": {
|
||||
"tabs": {
|
||||
"proxy": "Mεσολάβησης",
|
||||
"tab": "Καρτέλα",
|
||||
"custom": "Προσαρμογή",
|
||||
"misc": "Διάφορα"
|
||||
},
|
||||
"proxy": {
|
||||
"title": "Μεσολάβηση",
|
||||
"subtitle": "Επιλέξτε τη μεσολάβηση που ταιριάζει στις ανάγκες σας",
|
||||
"automatic": "Αυτόματο",
|
||||
"buggyWarning": "(ΠΡΟΒΛΗΜΑ)"
|
||||
},
|
||||
"languages": {
|
||||
"title": "Γλώσσες",
|
||||
"subtitle": "Επιλέξτε τη γλώσσα που προτιμάτε",
|
||||
"japanese": "Ιαπωνικά",
|
||||
"english": "Αγγλικά",
|
||||
"spanish": "Ησπανικά",
|
||||
"german": "Γερμανικό",
|
||||
"greek": "Ελληνικά",
|
||||
"dutch": "Ολλανδικά"
|
||||
},
|
||||
"proxymodes": {
|
||||
"title": "Άνοιγμα σε",
|
||||
"subtitle": "Επιλέξτε τον τρόπο ανοίγματος των ιστοσελίδων σας",
|
||||
"embed": "εΕνσωμάτωση",
|
||||
"direct": "¨Αμεσα",
|
||||
"aboutblank": "About:Blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
{
|
||||
"header": {
|
||||
"title": "ネブラ。",
|
||||
"games": "ゲーム",
|
||||
"settings": "セッティング",
|
||||
"discord": "もっとリンクが欲しいですか?"
|
||||
},
|
||||
"404": {
|
||||
"text": "このネブラサービスは無効になっています",
|
||||
"return": "帰宅"
|
||||
},
|
||||
"home": {
|
||||
"placeholder": "由にウェブを検索"
|
||||
},
|
||||
"discord": {
|
||||
"title": "ネブラDiscordサーバー",
|
||||
"sub": "プロキシ経由で開きますか?",
|
||||
"button1": "通常通り開く",
|
||||
"button2": "プロキシを使用する"
|
||||
},
|
||||
"settings": {
|
||||
"tabs": {
|
||||
"proxy": "プロキシ",
|
||||
"tab": "タブ",
|
||||
"custom": "カスタマイズ",
|
||||
"misc": "その他"
|
||||
},
|
||||
"proxy": {
|
||||
"title": "プロキシ",
|
||||
"subtitle": "ニーズに合ったプロキシを選んでください",
|
||||
"automatic": "自動",
|
||||
"buggyWarning": "(バグ)"
|
||||
},
|
||||
"languages": {
|
||||
"title": "言語",
|
||||
"subtitle": "好きな言語を選んでください",
|
||||
"japanese": "日本語",
|
||||
"english": "英語",
|
||||
"spanish": "スペイン語",
|
||||
"german": "",
|
||||
"greek": "",
|
||||
"dutch": ""
|
||||
},
|
||||
"proxymodes": {
|
||||
"title": "開く",
|
||||
"subtitle": "サイトを開く方法を選択する",
|
||||
"embed": "埋め込む",
|
||||
"direct": "直接",
|
||||
"aboutblank": "About:Blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"header": {
|
||||
"title": "ネブラ。",
|
||||
"games": "ゲーム",
|
||||
"settings": "セッティング",
|
||||
"discord": "もっとリンクが欲しいですか?"
|
||||
},
|
||||
"404": {
|
||||
"text": "このネブラサービスは無効になっています",
|
||||
"return": "帰宅"
|
||||
},
|
||||
"home": {
|
||||
"placeholder": "由にウェブを検索"
|
||||
},
|
||||
"discord": {
|
||||
"title": "ネブラDiscordサーバー",
|
||||
"sub": "プロキシ経由で開きますか?",
|
||||
"button1": "通常通り開く",
|
||||
"button2": "プロキシを使用する"
|
||||
},
|
||||
"settings": {
|
||||
"tabs": {
|
||||
"proxy": "プロキシ",
|
||||
"tab": "タブ",
|
||||
"custom": "カスタマイズ",
|
||||
"misc": "その他"
|
||||
},
|
||||
"proxy": {
|
||||
"title": "プロキシ",
|
||||
"subtitle": "ニーズに合ったプロキシを選んでください",
|
||||
"automatic": "自動",
|
||||
"buggyWarning": "(バグ)"
|
||||
},
|
||||
"languages": {
|
||||
"title": "言語",
|
||||
"subtitle": "好きな言語を選んでください",
|
||||
"japanese": "日本語",
|
||||
"english": "英語",
|
||||
"spanish": "スペイン語",
|
||||
"german": "",
|
||||
"greek": "",
|
||||
"dutch": ""
|
||||
},
|
||||
"proxymodes": {
|
||||
"title": "開く",
|
||||
"subtitle": "サイトを開く方法を選択する",
|
||||
"embed": "埋め込む",
|
||||
"direct": "直接",
|
||||
"aboutblank": "About:Blank"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,55 +1,55 @@
|
|||
import { useState } from "preact/hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { HeaderRoute } from "../components/HeaderRoute";
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
export function Home() {
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
window.location.href = "/go/" + encodeURIComponent(inputValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<HeaderRoute>
|
||||
<Helmet>
|
||||
<title>Nebula</title>
|
||||
</Helmet>
|
||||
<div class="flex h-full flex-col items-center justify-center">
|
||||
<div class="font-inter absolute bottom-0 left-0 p-1 text-sm italic text-input-text">
|
||||
Nebula © Nebula Services {new Date().getUTCFullYear()}
|
||||
</div>
|
||||
<a href="https://github.com/NebulaServices/Nebula">
|
||||
<div class="font-inter absolute bottom-0 right-0 p-1 text-sm text-input-text">
|
||||
GitHub
|
||||
</div>
|
||||
</a>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
class="flex h-full w-full items-center justify-center"
|
||||
>
|
||||
<input
|
||||
onFocus={() => {
|
||||
setIsFocused(true);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setIsFocused(false);
|
||||
}}
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) =>
|
||||
setInputValue((e.target as HTMLInputElement).value)
|
||||
}
|
||||
className={`font-roboto h-14 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none ${
|
||||
isFocused ? "w-full md:w-3/12" : "w-full md:w-80"
|
||||
} transition-all duration-300`}
|
||||
placeholder={isFocused ? "" : t("home.placeholder")}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</HeaderRoute>
|
||||
);
|
||||
}
|
||||
import { useState } from "preact/hooks";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { HeaderRoute } from "../components/HeaderRoute";
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
export function Home() {
|
||||
const [isFocused, setIsFocused] = useState(false);
|
||||
const [inputValue, setInputValue] = useState("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
event.preventDefault();
|
||||
window.location.href = "/go/" + encodeURIComponent(inputValue);
|
||||
};
|
||||
|
||||
return (
|
||||
<HeaderRoute>
|
||||
<Helmet>
|
||||
<title>Nebula</title>
|
||||
</Helmet>
|
||||
<div class="flex h-full flex-col items-center justify-center">
|
||||
<div class="font-inter absolute bottom-0 left-0 p-1 text-sm italic text-input-text">
|
||||
Nebula © Nebula Services {new Date().getUTCFullYear()}
|
||||
</div>
|
||||
<a href="https://github.com/NebulaServices/Nebula">
|
||||
<div class="font-inter absolute bottom-0 right-0 p-1 text-sm text-input-text">
|
||||
GitHub
|
||||
</div>
|
||||
</a>
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
class="flex h-full w-full items-center justify-center"
|
||||
>
|
||||
<input
|
||||
onFocus={() => {
|
||||
setIsFocused(true);
|
||||
}}
|
||||
onBlur={() => {
|
||||
setIsFocused(false);
|
||||
}}
|
||||
type="text"
|
||||
value={inputValue}
|
||||
onChange={(e) =>
|
||||
setInputValue((e.target as HTMLInputElement).value)
|
||||
}
|
||||
className={`font-roboto h-14 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none ${
|
||||
isFocused ? "w-full md:w-3/12" : "w-full md:w-80"
|
||||
} transition-all duration-300`}
|
||||
placeholder={isFocused ? "" : t("home.placeholder")}
|
||||
/>
|
||||
</form>
|
||||
</div>
|
||||
</HeaderRoute>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,84 +1,85 @@
|
|||
import { RammerheadEncode } from "../util/RammerheadEncode";
|
||||
import { searchUtil } from "../util/searchUtil";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
//import our Iframe component
|
||||
import { Iframe } from "../components/iframe/Iframe";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__uv$config: any;
|
||||
__dynamic$config: any;
|
||||
}
|
||||
}
|
||||
|
||||
export function ProxyFrame(props: { url: string }) {
|
||||
// pass the URL encoded with encodeURIcomponent
|
||||
const localProxy = localStorage.getItem("proxy") || "automatic";
|
||||
const proxyMode = localStorage.getItem("proxyMode") || "direct";
|
||||
|
||||
const [ProxiedUrl, setProxiedUrl] = useState<string | undefined>(undefined);
|
||||
|
||||
let decodedUrl = decodeURIComponent(props.url);
|
||||
//attempt to convert to a valid url
|
||||
decodedUrl = searchUtil(decodedUrl, "https://google.com/search?q=%s");
|
||||
|
||||
let proxyRef;
|
||||
|
||||
useEffect(() => {
|
||||
// For now we can redirect to the results. In the future we will add an if statement looking for the users proxy display choice
|
||||
if (localProxy === "rammerhead") {
|
||||
RammerheadEncode(decodedUrl).then((result: string) => {
|
||||
setProxiedUrl(result);
|
||||
});
|
||||
} else if (localProxy === "ultraviolet") {
|
||||
setProxiedUrl(
|
||||
window.__uv$config.prefix + window.__uv$config.encodeUrl(decodedUrl)
|
||||
);
|
||||
} else if (localProxy === "dynamic") {
|
||||
setProxiedUrl(
|
||||
window.__dynamic$config.prefix + encodeURIComponent(decodedUrl)
|
||||
);
|
||||
} else {
|
||||
// use UV for automatic
|
||||
setProxiedUrl(
|
||||
window.__uv$config.prefix + window.__uv$config.encodeUrl(decodedUrl)
|
||||
);
|
||||
}
|
||||
}, [localProxy]);
|
||||
|
||||
if (proxyMode == "direct") {
|
||||
window.location.href = ProxiedUrl;
|
||||
}
|
||||
else if (proxyMode == "aboutblank") {
|
||||
const newWindow = window.open("about:blank", "_blank");
|
||||
const newDocument = newWindow.document.open();
|
||||
newDocument.write(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style type="text/css">
|
||||
body, html
|
||||
{
|
||||
margin: 0; padding: 0; height: 100%; overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe style="border: none; width: 100%; height: 100vh;" src="${
|
||||
window.location.origin + ProxiedUrl
|
||||
}"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
newDocument.close()
|
||||
window.location.replace("/");
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="h-screen w-screen bg-primary">
|
||||
{proxyMode === "direct" && <h1>Loading {localProxy}...</h1>}
|
||||
{proxyMode === "aboutblank" && <h1>Loading {localProxy}...</h1>}
|
||||
{proxyMode === "embed" && <Iframe url={ProxiedUrl} normalUrl={decodedUrl} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
import { RammerheadEncode } from "../util/RammerheadEncode";
|
||||
import { searchUtil } from "../util/searchUtil";
|
||||
import { useEffect, useState } from "preact/hooks";
|
||||
//import our Iframe component
|
||||
import { Iframe } from "../components/iframe/Iframe";
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
__uv$config: any;
|
||||
__dynamic$config: any;
|
||||
}
|
||||
}
|
||||
|
||||
export function ProxyFrame(props: { url: string }) {
|
||||
// pass the URL encoded with encodeURIcomponent
|
||||
const localProxy = localStorage.getItem("proxy") || "automatic";
|
||||
const proxyMode = localStorage.getItem("proxyMode") || "direct";
|
||||
|
||||
const [ProxiedUrl, setProxiedUrl] = useState<string | undefined>(undefined);
|
||||
|
||||
let decodedUrl = decodeURIComponent(props.url);
|
||||
//attempt to convert to a valid url
|
||||
decodedUrl = searchUtil(decodedUrl, "https://google.com/search?q=%s");
|
||||
|
||||
let proxyRef;
|
||||
|
||||
useEffect(() => {
|
||||
// For now we can redirect to the results. In the future we will add an if statement looking for the users proxy display choice
|
||||
if (localProxy === "rammerhead") {
|
||||
RammerheadEncode(decodedUrl).then((result: string) => {
|
||||
setProxiedUrl(result);
|
||||
});
|
||||
} else if (localProxy === "ultraviolet") {
|
||||
setProxiedUrl(
|
||||
window.__uv$config.prefix + window.__uv$config.encodeUrl(decodedUrl)
|
||||
);
|
||||
} else if (localProxy === "dynamic") {
|
||||
setProxiedUrl(
|
||||
window.__dynamic$config.prefix + encodeURIComponent(decodedUrl)
|
||||
);
|
||||
} else {
|
||||
// use UV for automatic
|
||||
setProxiedUrl(
|
||||
window.__uv$config.prefix + window.__uv$config.encodeUrl(decodedUrl)
|
||||
);
|
||||
}
|
||||
}, [localProxy]);
|
||||
|
||||
if (proxyMode == "direct") {
|
||||
window.location.href = ProxiedUrl;
|
||||
} else if (proxyMode == "aboutblank") {
|
||||
const newWindow = window.open("about:blank", "_blank");
|
||||
const newDocument = newWindow.document.open();
|
||||
newDocument.write(`
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style type="text/css">
|
||||
body, html
|
||||
{
|
||||
margin: 0; padding: 0; height: 100%; overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<iframe style="border: none; width: 100%; height: 100vh;" src="${
|
||||
window.location.origin + ProxiedUrl
|
||||
}"></iframe>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
newDocument.close();
|
||||
window.location.replace("/");
|
||||
}
|
||||
|
||||
return (
|
||||
<div class="h-screen w-screen bg-primary">
|
||||
{proxyMode === "direct" && <h1>Loading {localProxy}...</h1>}
|
||||
{proxyMode === "aboutblank" && <h1>Loading {localProxy}...</h1>}
|
||||
{proxyMode === "embed" && (
|
||||
<Iframe url={ProxiedUrl} normalUrl={decodedUrl} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import { motion } from "framer-motion";
|
||||
import { tabContentVariant, settingsPageVariant } from "./Variants";
|
||||
|
||||
const Customization = ({ id, active }) => (
|
||||
<motion.div
|
||||
role="tabpanel"
|
||||
id={id}
|
||||
className="tab-content"
|
||||
variants={tabContentVariant}
|
||||
animate={active ? "active" : "inactive"}
|
||||
initial="inactive"
|
||||
>
|
||||
<motion.div variants={settingsPageVariant} className="content-card">
|
||||
<h1>Customization</h1>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
export default Customization;
|
||||
import { motion } from "framer-motion";
|
||||
import { tabContentVariant, settingsPageVariant } from "./Variants";
|
||||
|
||||
const Customization = ({ id, active }) => (
|
||||
<motion.div
|
||||
role="tabpanel"
|
||||
id={id}
|
||||
className="tab-content"
|
||||
variants={tabContentVariant}
|
||||
animate={active ? "active" : "inactive"}
|
||||
initial="inactive"
|
||||
>
|
||||
<motion.div variants={settingsPageVariant} className="content-card">
|
||||
<h1>Customization</h1>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
export default Customization;
|
||||
|
|
|
@ -1,73 +1,73 @@
|
|||
import { useState, useEffect } from "preact/hooks";
|
||||
import { FaAngleDown } from "react-icons/fa";
|
||||
|
||||
interface Option {
|
||||
id: string;
|
||||
label: string; // Translations CAN be passed
|
||||
}
|
||||
|
||||
const Dropdown = ({
|
||||
storageKey,
|
||||
options,
|
||||
refresh
|
||||
}: {
|
||||
storageKey: string;
|
||||
options: Option[];
|
||||
refresh: boolean;
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const [choice, setChoice] = useState(() => {
|
||||
return localStorage.getItem(storageKey) || options[0]?.id || "";
|
||||
});
|
||||
|
||||
// update on localstorage change
|
||||
useEffect(() => {
|
||||
setChoice(localStorage.getItem(storageKey) || options[0]?.id || "");
|
||||
}, [storageKey, options]);
|
||||
|
||||
return (
|
||||
<div className="relative text-center">
|
||||
<div
|
||||
className={`font-roboto flex h-14 w-56 cursor-pointer flex-col items-center justify-center border border-input-border-color bg-input text-center text-xl ${
|
||||
isOpen ? "rounded-t-2xl" : "rounded-2xl"
|
||||
}`}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<div className="flex h-full w-full select-none flex-row items-center">
|
||||
<div class="h-full w-1/4"></div>
|
||||
<div class="flex w-2/4 flex-col items-center">
|
||||
{options.find((o) => o.id === choice)?.label}
|
||||
</div>
|
||||
<div class="flex w-1/4 flex-col items-center">
|
||||
<FaAngleDown />
|
||||
</div>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div className="absolute top-full w-full">
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
key={option.id}
|
||||
className={`border border-input-border-color bg-input p-2 hover:bg-dropdown-option-hover-color ${
|
||||
index === options.length - 1 ? "rounded-b-2xl" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
setChoice(option.id);
|
||||
localStorage.setItem(storageKey, option.id);
|
||||
if (refresh === true) {
|
||||
window.location.reload();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dropdown;
|
||||
import { useState, useEffect } from "preact/hooks";
|
||||
import { FaAngleDown } from "react-icons/fa";
|
||||
|
||||
interface Option {
|
||||
id: string;
|
||||
label: string; // Translations CAN be passed
|
||||
}
|
||||
|
||||
const Dropdown = ({
|
||||
storageKey,
|
||||
options,
|
||||
refresh
|
||||
}: {
|
||||
storageKey: string;
|
||||
options: Option[];
|
||||
refresh: boolean;
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
const [choice, setChoice] = useState(() => {
|
||||
return localStorage.getItem(storageKey) || options[0]?.id || "";
|
||||
});
|
||||
|
||||
// update on localstorage change
|
||||
useEffect(() => {
|
||||
setChoice(localStorage.getItem(storageKey) || options[0]?.id || "");
|
||||
}, [storageKey, options]);
|
||||
|
||||
return (
|
||||
<div className="relative text-center">
|
||||
<div
|
||||
className={`font-roboto flex h-14 w-56 cursor-pointer flex-col items-center justify-center border border-input-border-color bg-input text-center text-xl ${
|
||||
isOpen ? "rounded-t-2xl" : "rounded-2xl"
|
||||
}`}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
>
|
||||
<div className="flex h-full w-full select-none flex-row items-center">
|
||||
<div class="h-full w-1/4"></div>
|
||||
<div class="flex w-2/4 flex-col items-center">
|
||||
{options.find((o) => o.id === choice)?.label}
|
||||
</div>
|
||||
<div class="flex w-1/4 flex-col items-center">
|
||||
<FaAngleDown />
|
||||
</div>
|
||||
</div>
|
||||
{isOpen && (
|
||||
<div className="absolute top-full w-full">
|
||||
{options.map((option, index) => (
|
||||
<div
|
||||
key={option.id}
|
||||
className={`border border-input-border-color bg-input p-2 hover:bg-dropdown-option-hover-color ${
|
||||
index === options.length - 1 ? "rounded-b-2xl" : ""
|
||||
}`}
|
||||
onClick={() => {
|
||||
setIsOpen(false);
|
||||
setChoice(option.id);
|
||||
localStorage.setItem(storageKey, option.id);
|
||||
if (refresh === true) {
|
||||
window.location.reload();
|
||||
}
|
||||
}}
|
||||
>
|
||||
{option.label}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Dropdown;
|
||||
|
|
|
@ -1,41 +1,41 @@
|
|||
import { motion } from "framer-motion";
|
||||
import { tabContentVariant, settingsPageVariant } from "./Variants";
|
||||
import Dropdown from "./Dropdown";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Misc = ({ id, active }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const languages = [
|
||||
{ id: "en-US", label: t("settings.languages.english") },
|
||||
{ id: "es", label: t("settings.languages.spanish") },
|
||||
{ id: "ja", label: t("settings.languages.japanese") }
|
||||
];
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
role="tabpanel"
|
||||
id={id}
|
||||
className="tab-content"
|
||||
variants={tabContentVariant}
|
||||
animate={active ? "active" : "inactive"}
|
||||
initial="inactive"
|
||||
>
|
||||
<motion.div
|
||||
variants={settingsPageVariant}
|
||||
className="content-card flex flex-row flex-wrap justify-around"
|
||||
>
|
||||
<div class="flex h-64 w-80 flex-col flex-wrap content-center items-center rounded-lg border border-input-border-color bg-lighter p-7 text-center">
|
||||
<div class="text-3xl">{t("settings.languages.title")}</div>
|
||||
<div class="text-md">{t("settings.languages.subtitle")}</div>
|
||||
<Dropdown
|
||||
storageKey="i18nextLng"
|
||||
options={languages}
|
||||
refresh={true}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
export default Misc;
|
||||
import { motion } from "framer-motion";
|
||||
import { tabContentVariant, settingsPageVariant } from "./Variants";
|
||||
import Dropdown from "./Dropdown";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Misc = ({ id, active }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const languages = [
|
||||
{ id: "en-US", label: t("settings.languages.english") },
|
||||
{ id: "es", label: t("settings.languages.spanish") },
|
||||
{ id: "ja", label: t("settings.languages.japanese") }
|
||||
];
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
role="tabpanel"
|
||||
id={id}
|
||||
className="tab-content"
|
||||
variants={tabContentVariant}
|
||||
animate={active ? "active" : "inactive"}
|
||||
initial="inactive"
|
||||
>
|
||||
<motion.div
|
||||
variants={settingsPageVariant}
|
||||
className="content-card flex flex-row flex-wrap justify-around"
|
||||
>
|
||||
<div class="flex h-64 w-80 flex-col flex-wrap content-center items-center rounded-lg border border-input-border-color bg-lighter p-7 text-center">
|
||||
<div class="text-3xl">{t("settings.languages.title")}</div>
|
||||
<div class="text-md">{t("settings.languages.subtitle")}</div>
|
||||
<Dropdown
|
||||
storageKey="i18nextLng"
|
||||
options={languages}
|
||||
refresh={true}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
export default Misc;
|
||||
|
|
|
@ -1,49 +1,53 @@
|
|||
import { motion } from "framer-motion";
|
||||
import { tabContentVariant, settingsPageVariant } from "./Variants";
|
||||
import Dropdown from "./Dropdown";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Proxy = ({ id, active }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const engines = [
|
||||
{ id: "automatic", label: t("settings.proxy.automatic") },
|
||||
{ id: "ultraviolet", label: "Ultraviolet" },
|
||||
{ id: "rammerhead", label: "Rammerhead" },
|
||||
{ id: "dynamic", label: "Dynamic " + t("settings.proxy.buggyWarning") }
|
||||
];
|
||||
|
||||
const proxyModes = [
|
||||
{ id: "direct", label: t("settings.proxymodes.direct") },
|
||||
{ id: "embed", label: t("settings.proxymodes.embed") },
|
||||
{ id: "aboutblank", label: t("settings.proxymodes.aboutblank") }
|
||||
];
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
role="tabpanel"
|
||||
id={id}
|
||||
className="tab-content"
|
||||
variants={tabContentVariant}
|
||||
animate={active ? "active" : "inactive"}
|
||||
initial="inactive"
|
||||
>
|
||||
<motion.div
|
||||
variants={settingsPageVariant}
|
||||
className="content-card flex flex-row flex-wrap justify-left w-full gap-4"
|
||||
>
|
||||
<div class="flex h-64 w-80 flex-col flex-wrap content-center items-center rounded-lg border border-input-border-color bg-lighter p-7 text-center">
|
||||
<div class="p-2 text-3xl">{t("settings.proxy.title")}</div>
|
||||
<div class="text-md p-4">{t("settings.proxy.subtitle")}</div>
|
||||
<Dropdown storageKey="proxy" options={engines} refresh={false} />
|
||||
</div>
|
||||
<div class="flex h-64 w-80 flex-col flex-wrap content-center items-center rounded-lg border border-input-border-color bg-lighter p-7 text-center">
|
||||
<div class="p-2 text-3xl">{t("settings.proxymodes.title")}</div>
|
||||
<div class="text-md p-4">{t("settings.proxymodes.subtitle")}</div>
|
||||
<Dropdown storageKey="proxyMode" options={proxyModes} refresh={false} />
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
export default Proxy;
|
||||
import { motion } from "framer-motion";
|
||||
import { tabContentVariant, settingsPageVariant } from "./Variants";
|
||||
import Dropdown from "./Dropdown";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const Proxy = ({ id, active }) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const engines = [
|
||||
{ id: "automatic", label: t("settings.proxy.automatic") },
|
||||
{ id: "ultraviolet", label: "Ultraviolet" },
|
||||
{ id: "rammerhead", label: "Rammerhead" },
|
||||
{ id: "dynamic", label: "Dynamic " + t("settings.proxy.buggyWarning") }
|
||||
];
|
||||
|
||||
const proxyModes = [
|
||||
{ id: "direct", label: t("settings.proxymodes.direct") },
|
||||
{ id: "embed", label: t("settings.proxymodes.embed") },
|
||||
{ id: "aboutblank", label: t("settings.proxymodes.aboutblank") }
|
||||
];
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
role="tabpanel"
|
||||
id={id}
|
||||
className="tab-content"
|
||||
variants={tabContentVariant}
|
||||
animate={active ? "active" : "inactive"}
|
||||
initial="inactive"
|
||||
>
|
||||
<motion.div
|
||||
variants={settingsPageVariant}
|
||||
className="content-card justify-left flex w-full flex-row flex-wrap gap-4"
|
||||
>
|
||||
<div class="flex h-64 w-80 flex-col flex-wrap content-center items-center rounded-lg border border-input-border-color bg-lighter p-7 text-center">
|
||||
<div class="p-2 text-3xl">{t("settings.proxy.title")}</div>
|
||||
<div class="text-md p-4">{t("settings.proxy.subtitle")}</div>
|
||||
<Dropdown storageKey="proxy" options={engines} refresh={false} />
|
||||
</div>
|
||||
<div class="flex h-64 w-80 flex-col flex-wrap content-center items-center rounded-lg border border-input-border-color bg-lighter p-7 text-center">
|
||||
<div class="p-2 text-3xl">{t("settings.proxymodes.title")}</div>
|
||||
<div class="text-md p-4">{t("settings.proxymodes.subtitle")}</div>
|
||||
<Dropdown
|
||||
storageKey="proxyMode"
|
||||
options={proxyModes}
|
||||
refresh={false}
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
export default Proxy;
|
||||
|
|
|
@ -1,106 +1,106 @@
|
|||
import { useState, useEffect } from "preact/hooks";
|
||||
import cn from "classnames";
|
||||
import { motion } from "framer-motion";
|
||||
import "./styles.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const tabVariant = {
|
||||
active: {
|
||||
width: "55%",
|
||||
transition: {
|
||||
type: "tween",
|
||||
duration: 0.4
|
||||
}
|
||||
},
|
||||
inactive: {
|
||||
width: "15%",
|
||||
transition: {
|
||||
type: "tween",
|
||||
duration: 0.4
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const tabTextVariant = {
|
||||
active: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
display: "block",
|
||||
transition: {
|
||||
type: "tween",
|
||||
duration: 0.3,
|
||||
delay: 0.3
|
||||
}
|
||||
},
|
||||
inactive: {
|
||||
opacity: 0,
|
||||
x: -30,
|
||||
transition: {
|
||||
type: "tween",
|
||||
duration: 0.3,
|
||||
delay: 0.1
|
||||
},
|
||||
transitionEnd: { display: "none" }
|
||||
}
|
||||
};
|
||||
|
||||
const TabComponent = ({ tabs, defaultIndex = 0 }) => {
|
||||
const [activeTabIndex, setActiveTabIndex] = useState(defaultIndex);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.style.setProperty(
|
||||
"--active-color",
|
||||
tabs[activeTabIndex].color
|
||||
);
|
||||
}, [activeTabIndex, tabs]);
|
||||
|
||||
// Default to a tab based on the URL hash value
|
||||
useEffect(() => {
|
||||
const tabFromHash = tabs.findIndex(
|
||||
(tab) => `#${tab.id}` === window.location.hash
|
||||
);
|
||||
setActiveTabIndex(tabFromHash !== -1 ? tabFromHash : defaultIndex);
|
||||
}, [tabs, defaultIndex]);
|
||||
|
||||
const onTabClick = (index) => {
|
||||
setActiveTabIndex(index);
|
||||
};
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div class="flex flex-col items-center w-full h-full">
|
||||
<div className="container h-full w-full">
|
||||
<div className="tabs-component">
|
||||
<ul className="tab-links" role="tablist">
|
||||
{tabs.map((tab, index) => (
|
||||
<motion.li
|
||||
key={tab.id}
|
||||
className={cn("tab", { active: activeTabIndex === index })}
|
||||
role="presentation"
|
||||
variants={tabVariant}
|
||||
animate={activeTabIndex === index ? "active" : "inactive"}
|
||||
>
|
||||
<a href={`#${tab.id}`} onClick={() => onTabClick(index)}>
|
||||
{tab.icon}
|
||||
<motion.span variants={tabTextVariant}>
|
||||
{t(tab.title)}
|
||||
</motion.span>
|
||||
</a>
|
||||
</motion.li>
|
||||
))}
|
||||
</ul>
|
||||
{tabs.map((tab, index) => (
|
||||
<tab.content
|
||||
key={tab.id}
|
||||
id={`${tab.id}-content`}
|
||||
active={activeTabIndex === index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabComponent;
|
||||
import { useState, useEffect } from "preact/hooks";
|
||||
import cn from "classnames";
|
||||
import { motion } from "framer-motion";
|
||||
import "./styles.css";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const tabVariant = {
|
||||
active: {
|
||||
width: "55%",
|
||||
transition: {
|
||||
type: "tween",
|
||||
duration: 0.4
|
||||
}
|
||||
},
|
||||
inactive: {
|
||||
width: "15%",
|
||||
transition: {
|
||||
type: "tween",
|
||||
duration: 0.4
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const tabTextVariant = {
|
||||
active: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
display: "block",
|
||||
transition: {
|
||||
type: "tween",
|
||||
duration: 0.3,
|
||||
delay: 0.3
|
||||
}
|
||||
},
|
||||
inactive: {
|
||||
opacity: 0,
|
||||
x: -30,
|
||||
transition: {
|
||||
type: "tween",
|
||||
duration: 0.3,
|
||||
delay: 0.1
|
||||
},
|
||||
transitionEnd: { display: "none" }
|
||||
}
|
||||
};
|
||||
|
||||
const TabComponent = ({ tabs, defaultIndex = 0 }) => {
|
||||
const [activeTabIndex, setActiveTabIndex] = useState(defaultIndex);
|
||||
|
||||
useEffect(() => {
|
||||
document.documentElement.style.setProperty(
|
||||
"--active-color",
|
||||
tabs[activeTabIndex].color
|
||||
);
|
||||
}, [activeTabIndex, tabs]);
|
||||
|
||||
// Default to a tab based on the URL hash value
|
||||
useEffect(() => {
|
||||
const tabFromHash = tabs.findIndex(
|
||||
(tab) => `#${tab.id}` === window.location.hash
|
||||
);
|
||||
setActiveTabIndex(tabFromHash !== -1 ? tabFromHash : defaultIndex);
|
||||
}, [tabs, defaultIndex]);
|
||||
|
||||
const onTabClick = (index) => {
|
||||
setActiveTabIndex(index);
|
||||
};
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div class="flex h-full w-full flex-col items-center">
|
||||
<div className="container h-full w-full">
|
||||
<div className="tabs-component">
|
||||
<ul className="tab-links" role="tablist">
|
||||
{tabs.map((tab, index) => (
|
||||
<motion.li
|
||||
key={tab.id}
|
||||
className={cn("tab", { active: activeTabIndex === index })}
|
||||
role="presentation"
|
||||
variants={tabVariant}
|
||||
animate={activeTabIndex === index ? "active" : "inactive"}
|
||||
>
|
||||
<a href={`#${tab.id}`} onClick={() => onTabClick(index)}>
|
||||
{tab.icon}
|
||||
<motion.span variants={tabTextVariant}>
|
||||
{t(tab.title)}
|
||||
</motion.span>
|
||||
</a>
|
||||
</motion.li>
|
||||
))}
|
||||
</ul>
|
||||
{tabs.map((tab, index) => (
|
||||
<tab.content
|
||||
key={tab.id}
|
||||
id={`${tab.id}-content`}
|
||||
active={activeTabIndex === index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default TabComponent;
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
import { motion } from "framer-motion";
|
||||
import { tabContentVariant, settingsPageVariant } from "./Variants";
|
||||
|
||||
const TabSettings = ({ id, active }) => (
|
||||
<motion.div
|
||||
role="tabpanel"
|
||||
id={id}
|
||||
className="tab-content"
|
||||
variants={tabContentVariant}
|
||||
animate={active ? "active" : "inactive"}
|
||||
initial="inactive"
|
||||
>
|
||||
<motion.div variants={settingsPageVariant} className="content-card">
|
||||
<h1>Tab settings</h1>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
export default TabSettings;
|
||||
import { motion } from "framer-motion";
|
||||
import { tabContentVariant, settingsPageVariant } from "./Variants";
|
||||
|
||||
const TabSettings = ({ id, active }) => (
|
||||
<motion.div
|
||||
role="tabpanel"
|
||||
id={id}
|
||||
className="tab-content"
|
||||
variants={tabContentVariant}
|
||||
animate={active ? "active" : "inactive"}
|
||||
initial="inactive"
|
||||
>
|
||||
<motion.div variants={settingsPageVariant} className="content-card">
|
||||
<h1>Tab settings</h1>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
export default TabSettings;
|
||||
|
|
|
@ -1,30 +1,30 @@
|
|||
const tabContentVariant = {
|
||||
active: {
|
||||
display: "block",
|
||||
transition: {
|
||||
staggerChildren: 0.2
|
||||
}
|
||||
},
|
||||
inactive: {
|
||||
display: "none"
|
||||
}
|
||||
};
|
||||
|
||||
const settingsPageVariant = {
|
||||
active: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.5
|
||||
}
|
||||
},
|
||||
inactive: {
|
||||
opacity: 0,
|
||||
y: 10,
|
||||
transition: {
|
||||
duration: 0.5
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { settingsPageVariant, tabContentVariant };
|
||||
const tabContentVariant = {
|
||||
active: {
|
||||
display: "block",
|
||||
transition: {
|
||||
staggerChildren: 0.2
|
||||
}
|
||||
},
|
||||
inactive: {
|
||||
display: "none"
|
||||
}
|
||||
};
|
||||
|
||||
const settingsPageVariant = {
|
||||
active: {
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration: 0.5
|
||||
}
|
||||
},
|
||||
inactive: {
|
||||
opacity: 0,
|
||||
y: 10,
|
||||
transition: {
|
||||
duration: 0.5
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { settingsPageVariant, tabContentVariant };
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
import TabComponent from "./TabComponent";
|
||||
import { HeaderRoute } from "../../components/HeaderRoute";
|
||||
import tabs from "./tabs";
|
||||
import { Helmet } from "react-helmet"
|
||||
|
||||
export function Settings() {
|
||||
return (
|
||||
<HeaderRoute>
|
||||
<Helmet>
|
||||
<title>Settings</title>
|
||||
</Helmet>
|
||||
<TabComponent tabs={tabs} />
|
||||
</HeaderRoute>
|
||||
);
|
||||
}
|
||||
import TabComponent from "./TabComponent";
|
||||
import { HeaderRoute } from "../../components/HeaderRoute";
|
||||
import tabs from "./tabs";
|
||||
import { Helmet } from "react-helmet";
|
||||
|
||||
export function Settings() {
|
||||
return (
|
||||
<HeaderRoute>
|
||||
<Helmet>
|
||||
<title>Settings</title>
|
||||
</Helmet>
|
||||
<TabComponent tabs={tabs} />
|
||||
</HeaderRoute>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,128 +1,128 @@
|
|||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--white: #fff;
|
||||
--black: #333;
|
||||
--active-color: #f1f1f1;
|
||||
--border-radius: 40px;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background: var(--active-color);
|
||||
transition: background 1.5s ease;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.tabs-component {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
padding: 40px;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.tab-links {
|
||||
padding: 0;
|
||||
margin: 0 auto 20px;
|
||||
list-style: none;
|
||||
max-width: 400px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tab {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab a {
|
||||
text-decoration: none;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.tab::before {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.2;
|
||||
position: absolute;
|
||||
border-radius: var(--border-radius);
|
||||
background: none;
|
||||
transition: background 0.5s ease;
|
||||
}
|
||||
|
||||
.tab svg {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
min-width: 30px;
|
||||
fill: var(--black);
|
||||
transition: fill 0.5s ease;
|
||||
}
|
||||
|
||||
.tab.active::before {
|
||||
background: var(--active-color);
|
||||
}
|
||||
|
||||
.tab span {
|
||||
font-weight: 700;
|
||||
margin-left: 10px;
|
||||
transition: color 0.5s ease;
|
||||
}
|
||||
|
||||
.tab.active span {
|
||||
color: var(--active-color);
|
||||
}
|
||||
|
||||
.tab.active svg {
|
||||
fill: var(--active-color);
|
||||
}
|
||||
|
||||
.tab a {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
width: 48%;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.content-card .info::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
bottom: -5px;
|
||||
background: var(--active-color);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.content-card img {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.content-card h3 {
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
|
||||
.content-card .info {
|
||||
padding: 10px 0;
|
||||
}
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
:root {
|
||||
--white: #fff;
|
||||
--black: #333;
|
||||
--active-color: #f1f1f1;
|
||||
--border-radius: 40px;
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
background: var(--active-color);
|
||||
transition: background 1.5s ease;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.tabs-component {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
margin: auto;
|
||||
padding: 40px;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.tab-links {
|
||||
padding: 0;
|
||||
margin: 0 auto 20px;
|
||||
list-style: none;
|
||||
max-width: 400px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.tab {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab a {
|
||||
text-decoration: none;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.tab::before {
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
opacity: 0.2;
|
||||
position: absolute;
|
||||
border-radius: var(--border-radius);
|
||||
background: none;
|
||||
transition: background 0.5s ease;
|
||||
}
|
||||
|
||||
.tab svg {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
min-width: 30px;
|
||||
fill: var(--black);
|
||||
transition: fill 0.5s ease;
|
||||
}
|
||||
|
||||
.tab.active::before {
|
||||
background: var(--active-color);
|
||||
}
|
||||
|
||||
.tab span {
|
||||
font-weight: 700;
|
||||
margin-left: 10px;
|
||||
transition: color 0.5s ease;
|
||||
}
|
||||
|
||||
.tab.active span {
|
||||
color: var(--active-color);
|
||||
}
|
||||
|
||||
.tab.active svg {
|
||||
fill: var(--active-color);
|
||||
}
|
||||
|
||||
.tab a {
|
||||
padding: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 20px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.cards {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
width: 48%;
|
||||
margin-bottom: 26px;
|
||||
}
|
||||
|
||||
.content-card .info::after {
|
||||
content: "";
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 3px;
|
||||
bottom: -5px;
|
||||
background: var(--active-color);
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.content-card img {
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.content-card h3 {
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
|
||||
.content-card .info {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
import Proxy from "./Proxy";
|
||||
import TabSettings from "./TabSettings";
|
||||
import Misc from "./Misc";
|
||||
import Customization from "./Customization";
|
||||
|
||||
import { GoBrowser } from "react-icons/go";
|
||||
import { AiOutlineLaptop } from "react-icons/ai";
|
||||
import { FaPalette } from "react-icons/fa";
|
||||
import { FaGear } from "react-icons/fa6";
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: "settings.tabs.proxy",
|
||||
id: "proxy",
|
||||
icon: <AiOutlineLaptop />,
|
||||
color: "#5d5dff",
|
||||
content: Proxy
|
||||
},
|
||||
{
|
||||
title: "settings.tabs.tab",
|
||||
id: "tab",
|
||||
icon: <GoBrowser />,
|
||||
color: "#67bb67",
|
||||
content: TabSettings
|
||||
},
|
||||
{
|
||||
title: "settings.tabs.custom",
|
||||
id: "custom",
|
||||
icon: <FaPalette />,
|
||||
color: "#63a7c7",
|
||||
content: Customization
|
||||
},
|
||||
{
|
||||
title: "settings.tabs.misc",
|
||||
id: "misc",
|
||||
icon: <FaGear />,
|
||||
color: "#f56868",
|
||||
content: Misc
|
||||
}
|
||||
];
|
||||
|
||||
export default tabs;
|
||||
import Proxy from "./Proxy";
|
||||
import TabSettings from "./TabSettings";
|
||||
import Misc from "./Misc";
|
||||
import Customization from "./Customization";
|
||||
|
||||
import { GoBrowser } from "react-icons/go";
|
||||
import { AiOutlineLaptop } from "react-icons/ai";
|
||||
import { FaPalette } from "react-icons/fa";
|
||||
import { FaGear } from "react-icons/fa6";
|
||||
|
||||
const tabs = [
|
||||
{
|
||||
title: "settings.tabs.proxy",
|
||||
id: "proxy",
|
||||
icon: <AiOutlineLaptop />,
|
||||
color: "#5d5dff",
|
||||
content: Proxy
|
||||
},
|
||||
{
|
||||
title: "settings.tabs.tab",
|
||||
id: "tab",
|
||||
icon: <GoBrowser />,
|
||||
color: "#67bb67",
|
||||
content: TabSettings
|
||||
},
|
||||
{
|
||||
title: "settings.tabs.custom",
|
||||
id: "custom",
|
||||
icon: <FaPalette />,
|
||||
color: "#63a7c7",
|
||||
content: Customization
|
||||
},
|
||||
{
|
||||
title: "settings.tabs.misc",
|
||||
id: "misc",
|
||||
icon: <FaGear />,
|
||||
color: "#f56868",
|
||||
content: Misc
|
||||
}
|
||||
];
|
||||
|
||||
export default tabs;
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "preact-router";
|
||||
import { HeaderRoute } from "../components/HeaderRoute";
|
||||
|
||||
export function NotFound() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<HeaderRoute>
|
||||
<section class="h-full">
|
||||
<div class="flex h-full flex-col items-center justify-center">
|
||||
<img src="/404.png" class="h-72"></img>
|
||||
<div class="flex flex-col items-center p-6">
|
||||
<p class="font-roboto text-4xl font-bold">{t("404.text")}</p>
|
||||
<span class="font-roboto text-3xl">404</span>
|
||||
</div>
|
||||
<Link href="/">
|
||||
<button class="font-roboto h-14 w-44 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none">
|
||||
{t("404.return")}
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</HeaderRoute>
|
||||
);
|
||||
}
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link } from "preact-router";
|
||||
import { HeaderRoute } from "../components/HeaderRoute";
|
||||
|
||||
export function NotFound() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<HeaderRoute>
|
||||
<section class="h-full">
|
||||
<div class="flex h-full flex-col items-center justify-center">
|
||||
<img src="/404.png" class="h-72"></img>
|
||||
<div class="flex flex-col items-center p-6">
|
||||
<p class="font-roboto text-4xl font-bold">{t("404.text")}</p>
|
||||
<span class="font-roboto text-3xl">404</span>
|
||||
</div>
|
||||
<Link href="/">
|
||||
<button class="font-roboto h-14 w-44 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none">
|
||||
{t("404.return")}
|
||||
</button>
|
||||
</Link>
|
||||
</div>
|
||||
</section>
|
||||
</HeaderRoute>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,29 +1,32 @@
|
|||
import { useTranslation } from "react-i18next";
|
||||
import { HeaderRoute } from "../components/HeaderRoute";
|
||||
|
||||
export function DiscordPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<HeaderRoute>
|
||||
<section class="h-full">
|
||||
<div class="flex h-full flex-col items-center justify-center">
|
||||
<div class="flex flex-col items-center p-6">
|
||||
<p class="font-roboto text-4xl font-bold">{t("discord.title")}</p>
|
||||
<span class="font-roboto text-3xl">{t("discord.sub")}</span>
|
||||
</div>
|
||||
<a href="https://discord.gg/unblocker" class="p-6">
|
||||
<button class="font-roboto h-14 w-56 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none">
|
||||
{t("discord.button1")}
|
||||
</button>
|
||||
</a>
|
||||
<a href="${window.location.href = '/go/' + encodeURIComponent(https://discord.gg/unblocker)}" class="p-6">
|
||||
<button class="font-roboto h-14 w-56 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none">
|
||||
{t("discord.button2")}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</HeaderRoute>
|
||||
);
|
||||
}
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { HeaderRoute } from "../components/HeaderRoute";
|
||||
|
||||
export function DiscordPage() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<HeaderRoute>
|
||||
<section class="h-full">
|
||||
<div class="flex h-full flex-col items-center justify-center">
|
||||
<div class="flex flex-col items-center p-6">
|
||||
<p class="font-roboto text-4xl font-bold">{t("discord.title")}</p>
|
||||
<span class="font-roboto text-3xl">{t("discord.sub")}</span>
|
||||
</div>
|
||||
<a href="https://discord.gg/unblocker" class="p-6">
|
||||
<button class="font-roboto h-14 w-56 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none">
|
||||
{t("discord.button1")}
|
||||
</button>
|
||||
</a>
|
||||
<a
|
||||
href="${window.location.href = '/go/' + encodeURIComponent(https://discord.gg/unblocker)}"
|
||||
class="p-6"
|
||||
>
|
||||
<button class="font-roboto h-14 w-56 rounded-2xl border border-input-border-color bg-input p-2 text-center text-xl placeholder:text-input-text focus:outline-none">
|
||||
{t("discord.button2")}
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
</HeaderRoute>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;700;900&display=swap");
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;700;900&display=swap");
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
|
|
@ -1,28 +1,28 @@
|
|||
@import url("https://fonts.googleapis.com/css2?family=Dongle&family=Roboto:wght@100&display=swap");
|
||||
|
||||
:root {
|
||||
--background-primary: #191724;
|
||||
--background-lighter: #16121f;
|
||||
--navbar-color: #26233a;
|
||||
--navbar-height: 60px;
|
||||
--navbar-text-color: #7967dd;
|
||||
--navbar-link-color: #e0def4;
|
||||
--navbar-link-hover-color: gray;
|
||||
--navbar-font: "Roboto";
|
||||
--input-text-color: #e0def4;
|
||||
--input-placeholder-color: white;
|
||||
--input-background-color: #1f1d2e;
|
||||
--input-border-color: #eb6f92;
|
||||
--input-border-size: 1.3px;
|
||||
--navbar-logo-filter: none;
|
||||
--dropdown-option-hover-color: #312a49;
|
||||
}
|
||||
|
||||
.font-inter {
|
||||
font-family: "Inter", sans-serif;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.font-roboto {
|
||||
font-family: "Roboto";
|
||||
}
|
||||
@import url("https://fonts.googleapis.com/css2?family=Dongle&family=Roboto:wght@100&display=swap");
|
||||
|
||||
:root {
|
||||
--background-primary: #191724;
|
||||
--background-lighter: #16121f;
|
||||
--navbar-color: #26233a;
|
||||
--navbar-height: 60px;
|
||||
--navbar-text-color: #7967dd;
|
||||
--navbar-link-color: #e0def4;
|
||||
--navbar-link-hover-color: gray;
|
||||
--navbar-font: "Roboto";
|
||||
--input-text-color: #e0def4;
|
||||
--input-placeholder-color: white;
|
||||
--input-background-color: #1f1d2e;
|
||||
--input-border-color: #eb6f92;
|
||||
--input-border-size: 1.3px;
|
||||
--navbar-logo-filter: none;
|
||||
--dropdown-option-hover-color: #312a49;
|
||||
}
|
||||
|
||||
.font-inter {
|
||||
font-family: "Inter", sans-serif;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.font-roboto {
|
||||
font-family: "Roboto";
|
||||
}
|
||||
|
|
|
@ -1,176 +1,177 @@
|
|||
export function RammerheadEncode(baseUrl) { // Hellhead
|
||||
const mod = (n, m) => ((n % m) + m) % m;
|
||||
const baseDictionary =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-";
|
||||
const shuffledIndicator = "_rhs";
|
||||
const generateDictionary = function () {
|
||||
let str = "";
|
||||
const split = baseDictionary.split("");
|
||||
while (split.length > 0) {
|
||||
str += split.splice(Math.floor(Math.random() * split.length), 1)[0];
|
||||
}
|
||||
return str;
|
||||
};
|
||||
interface StrShuffler {
|
||||
dictionary: any;
|
||||
}
|
||||
class StrShuffler {
|
||||
constructor(dictionary = generateDictionary()) {
|
||||
this.dictionary = dictionary;
|
||||
}
|
||||
shuffle(str) {
|
||||
if (str.startsWith(shuffledIndicator)) {
|
||||
return str;
|
||||
}
|
||||
let shuffledStr = "";
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charAt(i);
|
||||
const idx = baseDictionary.indexOf(char);
|
||||
if (char === "%" && str.length - i >= 3) {
|
||||
shuffledStr += char;
|
||||
shuffledStr += str.charAt(++i);
|
||||
shuffledStr += str.charAt(++i);
|
||||
} else if (idx === -1) {
|
||||
shuffledStr += char;
|
||||
} else {
|
||||
shuffledStr += this.dictionary.charAt(
|
||||
mod(idx + i, baseDictionary.length)
|
||||
);
|
||||
}
|
||||
}
|
||||
return shuffledIndicator + shuffledStr;
|
||||
}
|
||||
unshuffle(str) {
|
||||
if (!str.startsWith(shuffledIndicator)) {
|
||||
return str;
|
||||
}
|
||||
|
||||
str = str.slice(shuffledIndicator.length);
|
||||
|
||||
let unshuffledStr = "";
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charAt(i);
|
||||
const idx = this.dictionary.indexOf(char);
|
||||
if (char === "%" && str.length - i >= 3) {
|
||||
unshuffledStr += char;
|
||||
unshuffledStr += str.charAt(++i);
|
||||
unshuffledStr += str.charAt(++i);
|
||||
} else if (idx === -1) {
|
||||
unshuffledStr += char;
|
||||
} else {
|
||||
unshuffledStr += baseDictionary.charAt(
|
||||
mod(idx - i, baseDictionary.length)
|
||||
);
|
||||
}
|
||||
}
|
||||
return unshuffledStr;
|
||||
}
|
||||
}
|
||||
function get(url, callback, shush = false) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open("GET", url, true);
|
||||
request.send();
|
||||
|
||||
request.onerror = function () {
|
||||
if (!shush) console.log("Cannot communicate with the server");
|
||||
};
|
||||
request.onload = function () {
|
||||
if (request.status === 200) {
|
||||
callback(request.responseText);
|
||||
} else {
|
||||
if (!shush)
|
||||
console.log(
|
||||
'unexpected server response to not match "200". Server says "' +
|
||||
request.responseText +
|
||||
'"'
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
var api = {
|
||||
newsession(callback) {
|
||||
get("/newsession", callback);
|
||||
},
|
||||
sessionexists(id, callback) {
|
||||
get("/sessionexists?id=" + encodeURIComponent(id), function (res) {
|
||||
if (res === "exists") return callback(true);
|
||||
if (res === "not found") return callback(false);
|
||||
console.log("unexpected response from server. received" + res);
|
||||
});
|
||||
},
|
||||
shuffleDict(id, callback) {
|
||||
console.log("Shuffling", id);
|
||||
get("/api/shuffleDict?id=" + encodeURIComponent(id), function (res) {
|
||||
callback(JSON.parse(res));
|
||||
});
|
||||
}
|
||||
};
|
||||
var localStorageKey = "rammerhead_sessionids";
|
||||
var localStorageKeyDefault = "rammerhead_default_sessionid";
|
||||
var sessionIdsStore = {
|
||||
get() {
|
||||
var rawData = localStorage.getItem(localStorageKey);
|
||||
if (!rawData) return [];
|
||||
try {
|
||||
var data = JSON.parse(rawData);
|
||||
if (!Array.isArray(data)) throw "getout";
|
||||
return data;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
set(data) {
|
||||
if (!data || !Array.isArray(data)) throw new TypeError("must be array");
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(data));
|
||||
},
|
||||
getDefault() {
|
||||
var sessionId = localStorage.getItem(localStorageKeyDefault);
|
||||
if (sessionId) {
|
||||
var data = sessionIdsStore.get();
|
||||
data.filter(function (e) {
|
||||
return e.id === sessionId;
|
||||
});
|
||||
if (data.length) return data[0];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
setDefault(id) {
|
||||
localStorage.setItem(localStorageKeyDefault, id);
|
||||
}
|
||||
};
|
||||
function addSession(id) {
|
||||
var data = sessionIdsStore.get();
|
||||
data.unshift({ id: id, createdOn: new Date().toLocaleString() });
|
||||
sessionIdsStore.set(data);
|
||||
}
|
||||
function getSessionId() {
|
||||
return new Promise((resolve) => {
|
||||
var id = localStorage.getItem("session-string");
|
||||
api.sessionexists(id, function (value) {
|
||||
if (!value) {
|
||||
console.log("Session validation failed");
|
||||
api.newsession(function (id) {
|
||||
addSession(id);
|
||||
localStorage.setItem("session-string", id);
|
||||
console.log(id);
|
||||
console.log("^ new id");
|
||||
resolve(id);
|
||||
});
|
||||
} else {
|
||||
resolve(id);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
var ProxyHref;
|
||||
|
||||
return getSessionId().then((id) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
api.shuffleDict(id, function (shuffleDict) {
|
||||
var shuffler = new StrShuffler(shuffleDict);
|
||||
ProxyHref = "/" + id + "/" + shuffler.shuffle(baseUrl);
|
||||
resolve(ProxyHref);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
export function RammerheadEncode(baseUrl) {
|
||||
// Hellhead
|
||||
const mod = (n, m) => ((n % m) + m) % m;
|
||||
const baseDictionary =
|
||||
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~-";
|
||||
const shuffledIndicator = "_rhs";
|
||||
const generateDictionary = function () {
|
||||
let str = "";
|
||||
const split = baseDictionary.split("");
|
||||
while (split.length > 0) {
|
||||
str += split.splice(Math.floor(Math.random() * split.length), 1)[0];
|
||||
}
|
||||
return str;
|
||||
};
|
||||
interface StrShuffler {
|
||||
dictionary: any;
|
||||
}
|
||||
class StrShuffler {
|
||||
constructor(dictionary = generateDictionary()) {
|
||||
this.dictionary = dictionary;
|
||||
}
|
||||
shuffle(str) {
|
||||
if (str.startsWith(shuffledIndicator)) {
|
||||
return str;
|
||||
}
|
||||
let shuffledStr = "";
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charAt(i);
|
||||
const idx = baseDictionary.indexOf(char);
|
||||
if (char === "%" && str.length - i >= 3) {
|
||||
shuffledStr += char;
|
||||
shuffledStr += str.charAt(++i);
|
||||
shuffledStr += str.charAt(++i);
|
||||
} else if (idx === -1) {
|
||||
shuffledStr += char;
|
||||
} else {
|
||||
shuffledStr += this.dictionary.charAt(
|
||||
mod(idx + i, baseDictionary.length)
|
||||
);
|
||||
}
|
||||
}
|
||||
return shuffledIndicator + shuffledStr;
|
||||
}
|
||||
unshuffle(str) {
|
||||
if (!str.startsWith(shuffledIndicator)) {
|
||||
return str;
|
||||
}
|
||||
|
||||
str = str.slice(shuffledIndicator.length);
|
||||
|
||||
let unshuffledStr = "";
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charAt(i);
|
||||
const idx = this.dictionary.indexOf(char);
|
||||
if (char === "%" && str.length - i >= 3) {
|
||||
unshuffledStr += char;
|
||||
unshuffledStr += str.charAt(++i);
|
||||
unshuffledStr += str.charAt(++i);
|
||||
} else if (idx === -1) {
|
||||
unshuffledStr += char;
|
||||
} else {
|
||||
unshuffledStr += baseDictionary.charAt(
|
||||
mod(idx - i, baseDictionary.length)
|
||||
);
|
||||
}
|
||||
}
|
||||
return unshuffledStr;
|
||||
}
|
||||
}
|
||||
function get(url, callback, shush = false) {
|
||||
var request = new XMLHttpRequest();
|
||||
request.open("GET", url, true);
|
||||
request.send();
|
||||
|
||||
request.onerror = function () {
|
||||
if (!shush) console.log("Cannot communicate with the server");
|
||||
};
|
||||
request.onload = function () {
|
||||
if (request.status === 200) {
|
||||
callback(request.responseText);
|
||||
} else {
|
||||
if (!shush)
|
||||
console.log(
|
||||
'unexpected server response to not match "200". Server says "' +
|
||||
request.responseText +
|
||||
'"'
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
var api = {
|
||||
newsession(callback) {
|
||||
get("/newsession", callback);
|
||||
},
|
||||
sessionexists(id, callback) {
|
||||
get("/sessionexists?id=" + encodeURIComponent(id), function (res) {
|
||||
if (res === "exists") return callback(true);
|
||||
if (res === "not found") return callback(false);
|
||||
console.log("unexpected response from server. received" + res);
|
||||
});
|
||||
},
|
||||
shuffleDict(id, callback) {
|
||||
console.log("Shuffling", id);
|
||||
get("/api/shuffleDict?id=" + encodeURIComponent(id), function (res) {
|
||||
callback(JSON.parse(res));
|
||||
});
|
||||
}
|
||||
};
|
||||
var localStorageKey = "rammerhead_sessionids";
|
||||
var localStorageKeyDefault = "rammerhead_default_sessionid";
|
||||
var sessionIdsStore = {
|
||||
get() {
|
||||
var rawData = localStorage.getItem(localStorageKey);
|
||||
if (!rawData) return [];
|
||||
try {
|
||||
var data = JSON.parse(rawData);
|
||||
if (!Array.isArray(data)) throw "getout";
|
||||
return data;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
set(data) {
|
||||
if (!data || !Array.isArray(data)) throw new TypeError("must be array");
|
||||
localStorage.setItem(localStorageKey, JSON.stringify(data));
|
||||
},
|
||||
getDefault() {
|
||||
var sessionId = localStorage.getItem(localStorageKeyDefault);
|
||||
if (sessionId) {
|
||||
var data = sessionIdsStore.get();
|
||||
data.filter(function (e) {
|
||||
return e.id === sessionId;
|
||||
});
|
||||
if (data.length) return data[0];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
setDefault(id) {
|
||||
localStorage.setItem(localStorageKeyDefault, id);
|
||||
}
|
||||
};
|
||||
function addSession(id) {
|
||||
var data = sessionIdsStore.get();
|
||||
data.unshift({ id: id, createdOn: new Date().toLocaleString() });
|
||||
sessionIdsStore.set(data);
|
||||
}
|
||||
function getSessionId() {
|
||||
return new Promise((resolve) => {
|
||||
var id = localStorage.getItem("session-string");
|
||||
api.sessionexists(id, function (value) {
|
||||
if (!value) {
|
||||
console.log("Session validation failed");
|
||||
api.newsession(function (id) {
|
||||
addSession(id);
|
||||
localStorage.setItem("session-string", id);
|
||||
console.log(id);
|
||||
console.log("^ new id");
|
||||
resolve(id);
|
||||
});
|
||||
} else {
|
||||
resolve(id);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
var ProxyHref;
|
||||
|
||||
return getSessionId().then((id) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
api.shuffleDict(id, function (shuffleDict) {
|
||||
var shuffler = new StrShuffler(shuffleDict);
|
||||
ProxyHref = "/" + id + "/" + shuffler.shuffle(baseUrl);
|
||||
resolve(ProxyHref);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
export function searchUtil(input: string, template: string) {
|
||||
try {
|
||||
return new URL(input).toString();
|
||||
} catch (error) {}
|
||||
try {
|
||||
const url = new URL(`http://${input}`);
|
||||
if (url.hostname.includes(".")) return url.toString();
|
||||
} catch (error) {}
|
||||
return template.replace("%s", encodeURIComponent(input));
|
||||
}
|
||||
export function searchUtil(input: string, template: string) {
|
||||
try {
|
||||
return new URL(input).toString();
|
||||
} catch (error) {}
|
||||
try {
|
||||
const url = new URL(`http://${input}`);
|
||||
if (url.hostname.includes(".")) return url.toString();
|
||||
} catch (error) {}
|
||||
return template.replace("%s", encodeURIComponent(input));
|
||||
}
|
||||
|
|
|
@ -1,21 +1,20 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
colors: {
|
||||
primary: "var(--background-primary)",
|
||||
lighter: "var(--background-lighter)",
|
||||
"navbar-text-color": "var(--navbar-text-color)",
|
||||
"navbar-color": "var(--navbar-color)",
|
||||
"text-color": "var(--navbar-link-color)",
|
||||
"text-hover-color": "var(--navbar-link-hover-color)",
|
||||
input: "var(--input-background-color)",
|
||||
"input-text": "var(--input-text-color)",
|
||||
"input-border-color": "var(--input-border-color)",
|
||||
"dropdown-option-hover-color": "var(--dropdown-option-hover-color)",
|
||||
},
|
||||
extend: {}
|
||||
},
|
||||
plugins: []
|
||||
};
|
||||
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
export default {
|
||||
content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
|
||||
theme: {
|
||||
colors: {
|
||||
primary: "var(--background-primary)",
|
||||
lighter: "var(--background-lighter)",
|
||||
"navbar-text-color": "var(--navbar-text-color)",
|
||||
"navbar-color": "var(--navbar-color)",
|
||||
"text-color": "var(--navbar-link-color)",
|
||||
"text-hover-color": "var(--navbar-link-hover-color)",
|
||||
input: "var(--input-background-color)",
|
||||
"input-text": "var(--input-text-color)",
|
||||
"input-border-color": "var(--input-border-color)",
|
||||
"dropdown-option-hover-color": "var(--dropdown-option-hover-color)"
|
||||
},
|
||||
extend: {}
|
||||
},
|
||||
plugins: []
|
||||
};
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact",
|
||||
"skipLibCheck": true,
|
||||
"paths": {}
|
||||
}
|
||||
}
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "preact",
|
||||
"skipLibCheck": true,
|
||||
"paths": {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,31 @@
|
|||
import million from "million/compiler";
|
||||
import { defineConfig } from "vite";
|
||||
import preact from "@preact/preset-vite";
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||
import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
// .replace fixes weird paths on Windows
|
||||
src: `${uvPath}/uv.*.js`.replace(/\\/g, "/"),
|
||||
dest: "uv",
|
||||
overwrite: false
|
||||
}
|
||||
]
|
||||
}),
|
||||
million.vite({ auto: true }),
|
||||
preact()
|
||||
],
|
||||
server: {
|
||||
proxy: {
|
||||
"/bare": {
|
||||
target: "http://localhost:8080/",
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/bare/, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
import million from "million/compiler";
|
||||
import { defineConfig } from "vite";
|
||||
import preact from "@preact/preset-vite";
|
||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||
import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
viteStaticCopy({
|
||||
targets: [
|
||||
{
|
||||
// .replace fixes weird paths on Windows
|
||||
src: `${uvPath}/uv.*.js`.replace(/\\/g, "/"),
|
||||
dest: "uv",
|
||||
overwrite: false
|
||||
}
|
||||
]
|
||||
}),
|
||||
million.vite({ auto: true }),
|
||||
preact()
|
||||
],
|
||||
server: {
|
||||
proxy: {
|
||||
"/bare": {
|
||||
target: "http://localhost:8080/",
|
||||
changeOrigin: true,
|
||||
rewrite: (path) => path.replace(/^\/bare/, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue