Time to properly archive v7

This commit is contained in:
MotorTruck1221 2024-11-04 23:55:30 -07:00
parent 50a33d4f8d
commit 64194f6ee3
No known key found for this signature in database
GPG key ID: 08F417E2B8B61EA4
108 changed files with 0 additions and 13037 deletions

View file

@ -1,11 +0,0 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": ["@changesets/changelog-github", { "repo": "nebulaservices/nebula" }],
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}

View file

@ -1,16 +0,0 @@
node_modules/
.vscode
npm-debug.log
yarn-error.log
.github/
.env.example
.env
dist/
.git/
.astro/
~/
.gitignore
biome.json
docker-compose.yml
Dockerfile
README.md

View file

@ -1,48 +0,0 @@
name: Build Docker image
on:
push:
tags:
- v*
workflow_dispatch:
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
build-and-push:
name: Build and push Docker image to registry
runs-on: ubuntu-latest
if: github.repository_owner == 'nebulaservices'
permissions:
contents: write
packages: write
steps:
- name: Checkout repo
uses: actions/checkout@v3
- name: Setup docker buildx
uses: docker/setup-buildx-action@v3
- name: Login To registry ${{ env.REGISTRY }}
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v3
with:
images: ${{ env.REGISTRY }}/nebulaservice/nebula
- name: Build and push
id: build-and-push
uses: docker/build-push-action@v4
with:
context: .
platforms: linux/amd64,linux/arm64
file: ./Dockerfile
name: nebula
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

View file

@ -1,36 +0,0 @@
name: Release
on:
push:
branches:
- main
workflow_dispatch:
defaults:
run:
shell: bash
env:
FORCE_COLOR: true
jobs:
changelog:
name: Release TAG
if: ${{ github.repository_owner == 'nebulaservices' }}
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
steps:
- uses: actions/checkout@v4
- name: Create Release Pull Request or Publish
id: changesets
uses: changesets/action@v1
with:
version: pnpm run version
publish: pnpm exec changeset publish
commit: "[ci] release"
title: "[ci] release"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

43
.gitignore vendored
View file

@ -1,43 +0,0 @@
# build output
dist/
server/*.js
# generated types
.astro/
# dependencies
node_modules/
package-lock.json
#external assets
database_assets/
!database_assets/com.nebula.gruvbox/
!database_assets/com.nebula.lightTheme/
!database_assets/com.nebula.oled/
!database_assets/com.nebula.retro/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# jetbrains setting folder
.idea/
# nebula catalog database
database.sqlite
# YOUR config
config.toml
# Goofy PNPM problem
~/

3
.gitmodules vendored
View file

@ -1,3 +0,0 @@
[submodule "workerware"]
path = workerware
url = https://github.com/mercuryworkshop/workerware

View file

@ -1,4 +0,0 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
.vscode/launch.json vendored
View file

@ -1,11 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

View file

@ -1,7 +0,0 @@
# v9.0.0
- The first release of Nebula V9! And with it bring a whole host of changes:
- More stable the V8
- Adds a Marketplace where users can create their own themes & plugins
- Switches to Astro for speed
- Other general bug fixes

View file

@ -1,19 +0,0 @@
FROM node:22-alpine
WORKDIR /app
COPY package*.json .
COPY . .
RUN apk update
RUN apk add python3 py3-pip alpine-sdk openssl-dev build-base python3-dev
RUN python3 -m pip install setuptools --break-system-packages
RUN cp -n config.example.toml config.toml
RUN npm i -g pnpm
RUN pnpm install
RUN pnpm run build
RUN export TERM=xterm-256color
VOLUME /app
EXPOSE 8080
ENTRYPOINT ["pnpm"]
CMD ["start", "--color"]

363
README.md
View file

@ -1,363 +0,0 @@
<div align="center">
<img src="https://socialify.git.ci/nebulaservices/nebula/image?description=1&font=Inter&forks=1&issues=1&language=1&name=1&owner=1&pattern=Circuit%20Board&pulls=1&stargazers=1&theme=Dark" alt="ruby" width="640" height="320" />
<img alt="repo size" src="https://img.shields.io/github/repo-size/nebulaservices/nebula?style=for-the-badge"></img>
<img alt="website status" src="https://img.shields.io/website?url=https%3A%2F%2Fnebulaproxy.io&style=for-the-badge"></img>
<img alt="commit a week" src="https://img.shields.io/github/commit-activity/w/nebulaservices/nebula?style=for-the-badge"></img>
</div>
<div align="center">
<h2>Get Started</h2>
<a>To get started, press one of the buttons below to deploy Incog</a>
<br />
<br />
<a href="#terminal">
<img src="https://img.shields.io/badge/terminal-%23121011.svg?style=for-the-badge&logo=gnu-bash&logoColor=white" alt="Terminal">
</img>
</a>
<a href="#docker">
<img src="https://img.shields.io/badge/docker-%230db7ed.svg?style=for-the-badge&logo=docker&logoColor=white" alt="Docker">
</img>
</a>
</div>
## NOTE:
- This will **NOT** deploy on Github Pages, Netlify, Vercel, Gitlab Pages or any other _static_ host
- This will **NOT** work on Render
---
## How to get links
[![Nebula Services Discord](https://invidget.switchblade.xyz/unblocker?theme=darl)](https://discord.gg/unblocker)
[![Titanium Network Discord](https://invidget.switchblade.xyz/unblock?theme=dark)](https://discord.gg/unblock)
---
## Features
- Multiple Proxy "Backends":
- [Ultraviolet](https://github.com/titaniumnetwork-dev/ultraviolet)
- [RammerHead](https://github.com/binary-person/rammerhead)
---
## Contributors
- [Rifting](https://github.com/rifting) - Owner & Maintainer
- [MotorTruck1221](https://motortruck1221.com) - Maintainer
---
## Tech Stack
- [Astro](https://astro.build)
- [Fastify](https://fastify.dev)
- [Ultraviolet](https://github.com/titaniumnetwork-dev/ultraviolet)
- [RammerHead](https://github.com/binary-person/rammerhead)
- [Epoxy](https://github.com/mercuryworkshop/epoxy-tls)
- [Libcurl.js](https://github.com/ading2210/libcurl.js)
- HTML, CSS, and JavaScript (DUH)
---
## Catalog/Marketplace
- By default the marketplace is enabled, and uses SQLite
- If you would like to disable the catalog, see [#config](#config)
- For big production instances I would recommend using Postgres over SQLite. To do this see [#config](#config)
- To use postgres via the provided docker-compose files, see [#docker](#docker)
### How to make a theme
- Themes allow you to customize the *look* of Nebula.
#### Prerequisites:
- Make sure you have our [Discord server](https://discord.gg/unblocker) so you can submit your theme
##### Making the themes:
1. Firstly, copy the css vars:
```css
:root {
--background-primary: /*Your stuff here */;
--background-lighter: ;
--navbar-color: ;
--navbar-text-color: ;
--navbar-link-color: ;
--navbar-link-hover-color: ;
--input-text-color: ;
--input-placeholder-color: ;
--input-background-color: ;
--input-border-color: ;
--tab-color: ;
--border-color: ;
}
```
> [!NOTE]
>
> You can add a custom font as well! To do so add this to your :root
>
> ```css
> --font-family: /* Font family name */;
> ```
>
> And this to the bottom of your css file/submition:
> ```css
> @font-face {
> font-family: /* Name */;
> src: url(/* Where the font is located! Local or external work! */);
> }
> ```
>
> A good example of using a custom font is the built in `retro` theme [here](./database_assets/com.nebula.retro)
2. Add your colors and test! (Either with a self hosted version of Nebula OR via a live preview (no clue when this will happen)
3. Once your satisfied with said colors, submit your theme in the [Discord Server](https://discord.gg/unblocker)!
---
### How to make a plugin
- Plugins extend the functionality of either the proxied page(s) or the service worker.
- This guide provides an incredibly basic example of how to make either.
#### Prerequisites:
- Make sure you have joined our [Discord server](https://discord.gg/unblocker) so you can submit your plugin.
- Some knowledge of JS/TS
##### Serviceworker plugin:
- These plugins are handled by Workerware see [here](https://github.com/mercuryworkshop/workerware) for docs.
1. Create an index.js (or other file name) file:
```bash
touch index.js
```
2. Edit that file to include the either of these:
- Code encased in a string:
```js
function setup() {
// This function MUST return the following attributes:
return {
function: `console.log('Example code.')`,
name: 'com.example', // Technically could be named anything. Recommended to use the same name for everything (name when submitting and this)
events: ['fetch'] // See: https://github.com/mercuryworkshop/workerware for the event types you can use. (Also typed if your using typescript)
}
}
//This can be named anything. However, it's recommended to use `entryFunc` (with types, the naming IS enforced)
self.entryFunc = setup; //DO NOT call the function here. Only assign the reference otherwise, it will error.
```
- Code in an arrow function:
```js
const example = () => {
console.log('Example code')
}
function setup() {
//This function MUST return the following attributes:
return {
function: example, //Do not call the function, only assign the reference to the function.
name: 'com.example', // Technicall could be name anything. Recommended to use the same name for everything (name when submitting and this)
event: ['fetch'] // Se https://github.com/mercuryworkshop/workerware for the event types you can use. (Also typed if using typescript)
}
}
//This can be named anything. However, it's recommended to use `entryFunc` (with types, the naming IS enforced)
self.entryFunc = setup; //DO NOT call the function here. Only assign the reference otherwise, it will error.
```
> [!WARNING]
> The only *allowed* way to pass code to the `function` param is either a string or an arrow function. Named functions ***WILL NOT WORK***.
>
> Example of a named function: `function example() {/* Some form of code */}`.
>
> If a named function is used where it shouldn't be, your plugin will not be approved nor will it work properly.
3. Submit your plugin in the [Discord](https://discord.gg/unblocker)!
##### Proxied page plugins
- The allow modification of websites that UV proxies, (EX: you could add vencord to discord with this)
1. Create an index.js file (or another file name)
```bash
touch index.js
```
2. Edit that file with your code and the following:
```js
//Name this whatever.
function example() {
//You MUST return the following
return {
host: "example.com", //The host to match (so if the user visits example.com it will inject the html below.
html: "<script>console.log('Example')</script>", //Must return a string (and be valid HTML or your plugin will break). How you get that string is up to you
injectTo: "head" // Can be "head" or "body"
}
}
//Technically this could be named anything, recommended to call it `entryFunc`
self.entryFunc = example; //DO NOT run the function here. That will cause errors. Only assign the reference to the function here.
```
3. Submit it in our [Discord](https://discord.gg/unblocker)!
---
## Deployment
### Terminal
Prerequisites:
- Node & npm
- Git
1. Clone the repo:
```bash
git clone https://github.com/nebulaservices/nebula --recursive && cd nebula
```
2. Install all of the dependencies:
```bash
npm i
```
3. Create a `config.toml` file
```bash
cp config.example.toml config.toml
```
4. Modify the `config.toml` file to you liking (docs [here](#environment))
```
nano config.toml
```
5. Build the frontend & server:
```bash
npm run build
```
6. Start the server
```bash
npm start
```
> [!NOTE]
> You can run `npm run bstart` to build and start together
---
### Docker
- There are two ways to deploy with docker:
- [Normal docker](#normal-docker)
- [Docker Compose](#docker-compose)
#### Normal Docker
Prerequisites:
- Git
- Docker
1. Clone the repo (skip if using prebuilt image):
```bash
git clone https://github.com/nebulaservices/nebula --recursive && cd nebula
```
2. Create an `config.toml` file (if using prebuilt image, copy the example from the repo):
```bash
cp config.example.toml config.toml
```
3. Modify the `config.toml` file to your liking (docs [here](#environment))
```bash
nano config.toml
```
4. Build the docker image (skip if using prebuilt):
```bash
docker build nebula:latest
```
5. Run the docker images:
- Prebuilt:
```bash
docker run -v ./config.toml:/app/config.toml ghcr.io/nebulaservices/nebula:latest
```
- Image you built yourself:
```bash
docker run -v ./config.toml:/app/config.toml nebula:latest
```
#### Docker Compose
Prerequisites:
- Git
- Docker w/compose
1. Clone the repo (skip if using prebuilt image):
```bash
git clone https://github.com/nebulaservices/nebula --recursive
```
2. Create an `config.toml` file (if using prebuilt image, copy the example from the repo):
```bash
cp config.example.toml config.toml
```
3. Modify the `config.toml` file to your liking (docs on that [here](#environment)]
```bash
nano config.toml
```
4. Build the docker image (skip if using prebuilt):
```bash
docker compose -f ./docker-compose.build.yml build
```
5. Run the docker image:
- Prebuilt:
```bash
docker compose up
```
- Image you built yourself:
```bash
docker compose -f ./docker-compose.build.yml up
```
#### Extra (Postgres)
- To use Postgres over SQlite, uncomment the DB section in the `docker-compose` file (or use your own postgres DB!). Then, modify the `config.toml` (See: [#config](#config) for knowledge on how to do this)
- To use Postgres over SQlite in a normal docker environment (no compose), you'll have to set one up and then modify the `config.toml` to use it. (See: [#config](#config) for knowledge on how to do this)
---
## Config
- There are a couple of configuration options for nebula. Most of the time, the defaults are fine, but there are instances where you may not want certain options enabled or certain things running.
- An example config file is located [here](./config.example.toml).
- Config format is in TOML
| Variable | Description | Type | Default |
|:----------:|:-------------:|:------:|:---------:|
| `marketplace` | The options below are for the marketplace section | `object` | N/A |
| `enabled` | Enable marketplace functionality | `boolean` | `true` |
| `psk` | The password and authentication key for the marketplace. ***CHANGE FROM DEFAULT*** | `string` | `CHANGEME` |
|----------------------------| ----------------------------------------------------------------------------|------------|--------------|
| `db` | The below options are for the db (database) section | `object` | N/A |
| `name` | The database name to use | `string` | `database` |
| `username` | The username for the DB | `string` | `username` |
| `password` | The database password. ***CHANGE FROM DEFAULT VALUE*** | `string` | `password` |
| `postgres` | Whether to use postgres over sqlite *(recommended for large production instances)* | `boolean` | `false` |
|----------------------------| ----------------------------------------------------------------------------|------------|--------------|
| `postgres` | The below options are for the postgres section. (Only worry about this if you enabled postgres in the db section.) | `object` | N/A |
| `domain` | Either the TLD or the IP address of your postgres server. | `string` | `''` |
| `port` | The port your postgres server is listening on | `number` | `5432` |
|----------------------------| ----------------------------------------------------------------------------|------------|--------------|
| `server.server` | The below options are to configure the server. | `object` | N/A |
| `port` | What port the server should listen on. *(Note: Can also be configured via environment variable `PORT`)* | `number` | `8080` |
| `wisp` | Whether the server should use the inbuilt wisp server. (Disabled if your using an external wisp server) | `boolean` | `true` |
| `logging` | Whether or not to enable logging. *Note: Logs are massive* | `boolean` | `true` |
|----------------------------| ----------------------------------------------------------------------------|------------|--------------|

View file

@ -1,118 +0,0 @@
import { fileURLToPath } from "node:url";
import node from "@astrojs/node";
import svelte from "@astrojs/svelte";
import tailwind from "@astrojs/tailwind";
import { baremuxPath } from "@mercuryworkshop/bare-mux/node";
import { epoxyPath } from "@mercuryworkshop/epoxy-transport";
import { libcurlPath } from "@mercuryworkshop/libcurl-transport";
import playformCompress from "@playform/compress";
import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
import icon from "astro-icon";
import { defineConfig, envField } from "astro/config";
import { viteStaticCopy } from "vite-plugin-static-copy";
import { version } from "./package.json";
import { parsedDoc } from "./server/config.js";
const workerwarePath = fileURLToPath(new URL("./workerware/src", import.meta.url));
export default defineConfig({
experimental: {
env: {
schema: {
VERSION: envField.string({
context: "client",
access: "public",
optional: true,
default: version
}),
MARKETPLACE_ENABLED: envField.boolean({
context: "client",
access: "public",
optional: true,
default: parsedDoc.marketplace.enabled
})
}
}
},
integrations: [
tailwind(),
icon(),
svelte(),
playformCompress({
CSS: false,
HTML: true,
Image: true,
JavaScript: true,
SVG: true
})
],
vite: {
plugins: [
viteStaticCopy({
targets: [
{
src: `${uvPath}/**/*`.replace(/\\/g, "/"),
dest: "uv",
overwrite: false
},
{
src: `${epoxyPath}/**/*`.replace(/\\/g, "/"),
dest: "epoxy",
overwrite: false
},
{
src: `${libcurlPath}/**/*`.replace(/\\/g, "/"),
dest: "libcurl",
overwrite: false
},
{
src: `${baremuxPath}/**/*`.replace(/\\/g, "/"),
dest: "baremux",
overwrite: false
},
{
src: `${workerwarePath}/**/*`.replace(/\\/g, "/"),
dest: "workerware",
overwrite: false
}
]
})
],
server: {
proxy: {
"/api/catalog-stats": {
target: "http://localhost:8080/api/catalog-stats",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/catalog-stats/, "")
},
"/api/catalog-assets": {
target: "http://localhost:8080/api/catalog-assets",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/catalog-assets/, "")
},
"/api/packages": {
target: "http://localhost:8080/api/packages",
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api\/packages/, "")
},
"/packages": {
target: "http://localhost:8080",
changeOrigin: true
},
"/wisp/": {
target: "ws://localhost:8080/wisp/",
changeOrigin: true,
ws: true,
rewrite: (path) => path.replace(/^\/wisp\//, "")
},
"/styles": {
target: "http://localhost:8080",
changeOrigin: true
}
}
}
},
output: "server",
adapter: node({
mode: "middleware"
})
});

View file

@ -1,32 +0,0 @@
{
"$schema": "https://biomejs.dev/schemas/1.9.3/schema.json",
"files": {
"ignore": ["~/", "**/dist/**", ".github/**"],
"include": ["**/**", "server/**"]
},
"formatter": {
"indentStyle": "space",
"indentWidth": 4,
"lineWidth": 100,
"ignore": ["pnpm-lock.yaml", "package.json"]
},
"organizeImports": { "enabled": true },
"linter": { "enabled": false },
"javascript": {
"formatter": {
"trailingCommas": "none",
"quoteStyle": "double",
"semicolons": "always"
}
},
"json": {
"parser": {
"allowComments": true,
"allowTrailingCommas": true
},
"formatter": {
"indentStyle": "space",
"trailingCommas": "none"
}
}
}

View file

@ -1,19 +0,0 @@
[marketplace]
enabled = true # Turn on or off the marketplace entirely
psk = "CHANGEME" # Change this to something more secure.
level = 1
[db]
name = "database" # Your databsae name
username = "username" # The username of your DB (SQLITE just ignores this)
password = "password" # The password to your DB (SQLITE ignores this)
postgres = false # Enable to use postgres over sqlite (recommended for large prod instances)
[postgres] # Set the "domain" to either and ip address or a actual domain
domain = ""
port = 5432
[server.server]
port = 8080
wisp = true
logging = true # Disable for the tons & tons of logs to go away (useful for debugging but otherwise eh)

View file

@ -1,22 +0,0 @@
:root {
--background-primary: #282828;
--background-lighter: #3c3836;
--navbar-color: #504945;
--navbar-height: 60px;
--navbar-text-color: #fbf1c7;
--navbar-link-color: #ebdbb2;
--navbar-link-hover-color: #fabd2f;
--navbar-font: "Roboto", sans-serif;
--input-text-color: #b8bb26;
--input-placeholder-color: #928374;
--input-background-color: #1d2021;
--input-border-color: #b8bb26;
--input-border-size: 1.3px;
--navbar-logo-filter: none;
--dropdown-option-hover-color: #665c54;
--tab-color: #1d2021;
--border-color: #b8bb26;
--highlight-color: #fe8019;
--accent-color: #83a598;
--secondary-text-color: #d3869b;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

View file

@ -1,18 +0,0 @@
:root {
--background-primary: hsl(310 50% 90%);
--background-lighter: hsl(310 50% 90%);
--navbar-color: hsl(310 50% 100%);
--navbar-height: 60px;
--navbar-text-color: hsl(310 50% 15%);
--navbar-link-color: hsl(310 50% 15%);
--navbar-link-hover-color: hsl(310 50% 90%);
--navbar-font: "Roboto";
--input-text-color: hsl(310 50% 15%);
--input-placeholder-color: white;
--input-background-color: hsl(310 50% 100%);
--input-border-color: hsl(310 50% 25%);
--input-border-size: 1.3px;
--navbar-logo-filter: none;
--tab-color: var(--black);
--border-color: hsl(310 50% 25%);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

View file

@ -1,19 +0,0 @@
:root {
--background-primary: #000000;
--background-lighter: #000000;
--navbar-color: #00000f;
--navbar-height: 60px;
--navbar-text-color: #4763ff;
--navbar-link-color: #4763ff;
--navbar-link-hover-color: gray;
--navbar-font: "Roboto";
--input-text-color: #4763ff;
--input-placeholder-color: white;
--input-background-color: #000000;
--input-border-color: #4763ff;
--input-border-size: 1.3px;
--navbar-logo-filter: none;
--dropdown-option-hover-color: #000000;
--tab-color: #000000;
--border-color: #4763ff;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -1,24 +0,0 @@
:root {
--background-primary: #000000;
--background-lighter: #000000;
--navbar-color: #020805;
--navbar-height: 60px;
--navbar-text-color: #3cb371;
--navbar-link-color: #3cb371;
--navbar-link-hover-color: white;
--input-text-color: #3cb371;
--input-placeholder-color: white;
--input-background-color: #000000;
--input-border-color: #3cb371;
--input-border-size: 1.3px;
--navbar-logo-filter: none;
--dropdown-option-hover-color: #000000;
--tab-color: #000000;
--border-color: #3cb371;
--font-family: "terminal";
}
@font-face {
font-family: terminal;
src: url("./terminal.ttf");
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

View file

@ -1,21 +0,0 @@
services:
nebula:
image: ghcr.io/nebulaservices/nebula:latest
container_name: nebula
build: .
restart: unless-stopped
ports:
# HOST:CONTAINER (DO NOT CHANGE THE CONTAINER PORT, UNLESS EDITED IN THE config.toml FILE)
- 8080:8080
volumes:
- ./config.toml:/app/config.toml
# Uncomment the the below stuff to use POSTGRES!
# db:
# image: postgres
# restart: unless-stopped
# environment:
# POSTGRES_PASSWORD: password #CHANGE THIS
# POSTGRES_USER: username
# POSTGRES_DB: db
# volumes:
# - ./db:/var/lib/postgresql/data

View file

@ -1,20 +0,0 @@
services:
nebula:
image: ghcr.io/nebulaservices/nebula:latest
container_name: nebula
restart: unless-stopped
ports:
# HOST:CONTAINER (DO NOT CHANGE THE CONTAINER PORT, UNLESS EDITED IN THE config.toml FILE)
- 8080:8080
volumes:
- ./config.toml:/app/config.toml
# Uncomment the the below stuff to use POSTGRES!
# db:
# image: postgres
# restart: unless-stopped
# environment:
# POSTGRES_PASSWORD: password #CHANGE THIS
# POSTGRES_USER: username
# POSTGRES_DB: db
# volumes:
# - ./db:/var/lib/postgresql/data

View file

@ -1,68 +0,0 @@
{
"name": "nebula",
"type": "module",
"version": "9.0.0",
"private": true,
"scripts": {
"dev": "concurrently \"astro dev --host 0.0.0.0\" \"tsx --watch server/server.ts\"",
"start": "node server/server.js",
"build:server": "tsc -p server",
"build:client": "astro check && astro build",
"build": "concurrently \"npm:build:server\" \"npm:build:client\"",
"bstart": "npm run build && npm run start",
"preview": "astro preview",
"astro": "astro",
"format:code": "biome format . --write",
"format:imports": "biome check . --write",
"format": "concurrently -m 1 \"npm:format:code\" \"npm:format:imports\"",
"version": "changeset version"
},
"dependencies": {
"@astrojs/check": "^0.8.3",
"@astrojs/node": "^8.3.4",
"@astrojs/svelte": "^5.7.2",
"@astrojs/tailwind": "^5.1.2",
"@fastify/compress": "^8.0.1",
"@fastify/helmet": "^12.0.1",
"@fastify/middie": "^9.0.2",
"@fastify/multipart": "^9.0.1",
"@fastify/static": "^8.0.1",
"@iconify-json/ph": "^1.2.1",
"@mercuryworkshop/bare-mux": "^2.1.6",
"@mercuryworkshop/epoxy-transport": "2.1.13",
"@mercuryworkshop/libcurl-transport": "^1.3.10",
"@playform/compress": "^0.1.4",
"@svelte-drama/suspense": "0.5.1",
"@titaniumnetwork-dev/ultraviolet": "^3.2.7",
"@types/node": "^22.7.5",
"@types/sequelize": "^4.28.20",
"astro": "^4.16.2",
"astro-icon": "^1.1.1",
"chalk": "^5.3.0",
"concurrently": "^8.2.2",
"fastify": "^5.0.0",
"form-data": "^4.0.1",
"gradient-string": "^3.0.0",
"libcurl.js-new": "npm:libcurl.js@^0.6.16",
"nanostores": "^0.10.3",
"ora": "^8.1.0",
"pg": "^8.13.0",
"pg-hstore": "^2.3.4",
"sequelize": "^6.37.4",
"smol-toml": "^1.3.0",
"sqlite3": "^5.1.7",
"svelte": "^4.2.19",
"svelte-french-toast": "^1.2.0",
"tailwindcss": "^3.4.13",
"typescript": "^5.6.3",
"vite-plugin-static-copy": "^1.0.6",
"wisp-server-node": "^1.1.7"
},
"devDependencies": {
"@biomejs/biome": "^1.9.3",
"@changesets/cli": "^2.27.9",
"bufferutil": "^4.0.8",
"sharp": "^0.33.5",
"tsx": "^4.19.1"
}
}

8722
pnpm-lock.yaml generated

File diff suppressed because it is too large Load diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 615 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

View file

@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

Before

Width:  |  Height:  |  Size: 749 B

View file

@ -1,19 +0,0 @@
: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;
--tab-color: var(--black);
--border-color: #16121f;
}

View file

@ -1,57 +0,0 @@
importScripts("/uv/uv.bundle.js");
importScripts("/uv/uv.config.js");
importScripts("/workerware/workerware.js");
importScripts(__uv$config.sw || "/uv/uv.sw.js");
const uv = new UVServiceWorker();
const ww = new WorkerWare({ debug: false });
//where we handle our plugins!!!
self.addEventListener("message", function (event) {
console.log(event.data);
uv.config.inject = [];
//loop over the required data (we don't verify here as types will take care of us :D)
event.data.forEach((data) => {
if (data.remove) {
if (data.type === "page") {
const idx = uv.config.inject.indexOf(data.host);
uv.config.inject.splice(idx, 1);
} else if (data.type === "serviceWorker") {
ww.deleteByName(data.name);
}
} else {
if (data.type === "page") {
uv.config.inject.push({
host: data.host,
html: data.html,
injectTo: data.injectTo
});
} else if (data.type === "serviceWorker") {
const wwFunction = eval(data.function);
ww.use({
function: wwFunction ? wwFunction : new Function(data.function),
name: data.name,
events: data.events
});
} else {
console.error("NO type exists for that. Only serviceWorker & page exist.");
return;
}
}
});
});
self.addEventListener("fetch", function (event) {
event.respondWith(
(async () => {
const wwRes = await ww.run(event)();
if (wwRes.includes(null)) {
return;
}
if (event.request.url.startsWith(location.origin + __uv$config.prefix)) {
return await uv.fetch(event);
} else {
return await fetch(event.request);
}
})()
);
});

View file

@ -1,30 +0,0 @@
self.__uv$config = {
prefix: "/~/uv/",
bare: "/bare/",
encodeUrl: function encode(str) {
if (!str) return str;
return encodeURIComponent(
str
.toString()
.split("")
.map((char, ind) => (ind % 2 ? String.fromCharCode(char.charCodeAt() ^ 3) : char))
.join("")
);
},
decodeUrl: function decode(str) {
if (!str) return str;
let [input, ...search] = str.split("?");
return (
decodeURIComponent(input)
.split("")
.map((char, ind) => (ind % 2 ? String.fromCharCode(char.charCodeAt(0) ^ 3) : char))
.join("") + (search.length ? "?" + search.join("?") : "")
);
},
handler: "/uv/uv.handler.js",
client: "/uv/uv.client.js",
bundle: "/uv/uv.bundle.js",
config: "/uv/uv.config.js",
sw: "/uv/uv.sw.js"
};

View file

@ -1,171 +0,0 @@
importScripts("/workerware/WWError.js");
const dbg = console.log.bind(console, "[WorkerWare]");
const time = console.time.bind(console, "[WorkerWare]");
const timeEnd = console.timeEnd.bind(console, "[WorkerWare]");
/*
OPTS:
debug - Enables debug logging.
randomNames - Generate random names for middlewares.
timing - Logs timing for each middleware.
*/
const defaultOpt = {
debug: false,
randomNames: false,
timing: false
};
const validEvents = [
"abortpayment",
"activate",
"backgroundfetchabort",
"backgroundfetchclick",
"backgroundfetchfail",
"backgroundfetchsuccess",
"canmakepayment",
"contentdelete",
"cookiechange",
"fetch",
"install",
"message",
"messageerror",
"notificationclick",
"notificationclose",
"paymentrequest",
"periodicsync",
"push",
"pushsubscriptionchange",
"sync"
];
class WorkerWare {
constructor(opt) {
this._opt = Object.assign({}, defaultOpt, opt);
this._middlewares = [];
}
info() {
return {
version: "0.1.0",
middlewares: this._middlewares,
options: this._opt
};
}
use(middleware) {
let validateMW = this.validateMiddleware(middleware);
if (validateMW.error) throw new WWError(validateMW.error);
// This means the middleware is an anonymous function, or the user is silly and named their function "function"
if (middleware.function.name == "function") middleware.name = crypto.randomUUID();
if (!middleware.name) middleware.name = middleware.function.name;
if (this._opt.randomNames) middleware.name = crypto.randomUUID();
if (this._opt.debug) dbg("Adding middleware:", middleware.name);
this._middlewares.push(middleware);
}
// Run all middlewares for the event type passed in.
run(event) {
const middlewares = this._middlewares;
const returnList = [];
let fn = async () => {
for (let i = 0; i < middlewares.length; i++) {
if (middlewares[i].events.includes(event.type)) {
if (this._opt.timing) console.time(middlewares[i].name);
// Add the configuration to the event object.
event.workerware = {
config: middlewares[i].configuration || {}
};
if (!middlewares[i].explicitCall) {
let res = await middlewares[i].function(event);
if (this._opt.timing) console.timeEnd(middlewares[i].name);
returnList.push(res);
}
}
}
return returnList;
};
return fn;
}
deleteByName(middlewareID) {
if (this._opt.debug) dbg("Deleting middleware:", middlewareID);
this._middlewares = this._middlewares.filter((mw) => mw.name !== middlewareID);
}
deleteByEvent(middlewareEvent) {
if (this._opt.debug) dbg("Deleting middleware by event:", middlewareEvent);
this._middlewares = this._middlewares.filter((mw) => !mw.events.includes(middlewareEvent));
}
get() {
return this._middlewares;
}
/*
Run a single middleware by ID.
This assumes that the user knows what they're doing, and is running the middleware on an event that it's supposed to run on.
*/
runMW(name, event) {
const middlewares = this._middlewares;
if (this._opt.debug) dbg("Running middleware:", name);
// if (middlewares.includes(name)) {
// return middlewares[name](event);
// } else {
// throw new WWError("Middleware not found!");
// }
let didCall = false;
for (let i = 0; i < middlewares.length; i++) {
if (middlewares[i].name == name) {
didCall = true;
event.workerware = {
config: middlewares[i].configuration || {}
};
if (this._opt.timing) console.time(middlewares[i].name);
let call = middlewares[i].function(event);
if (this._opt.timing) console.timeEnd(middlewares[i].name);
return call;
}
}
if (!didCall) {
throw new WWError("Middleware not found!");
}
}
// type middlewareManifest = {
// function: Function,
// name?: string,
// events: string[], // Should be a union of validEvents.
// configuration?: Object // Optional configuration for the middleware.
// }
validateMiddleware(middleware) {
if (!middleware.function)
return {
error: "middleware.function is required"
};
if (typeof middleware.function !== "function")
return {
error: "middleware.function must be typeof function"
};
if (
typeof middleware.configuration !== "object" &&
middleware.configuration !== undefined
) {
return {
error: "middleware.configuration must be typeof object"
};
}
if (!middleware.events)
return {
error: "middleware.events is required"
};
if (!Array.isArray(middleware.events))
return {
error: "middleware.events must be an array"
};
if (middleware.events.some((ev) => !validEvents.includes(ev)))
return {
error: "Invalid event type! Must be one of the following: " + validEvents.join(", ")
};
if (middleware.explicitCall && typeof middleware.explicitCall !== "boolean") {
return {
error: "middleware.explicitCall must be typeof boolean"
};
}
return {
error: undefined
};
}
}

View file

@ -1,73 +0,0 @@
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import chalk from "chalk";
import { TomlPrimitive, parse } from "smol-toml";
interface TomlData {
marketplace: {
enabled: boolean;
psk: String;
};
server: {
server: {
port: number;
wisp: boolean;
logging: boolean;
};
};
db: {
name: string;
username: string;
password: string;
postgres: boolean;
};
postgres: {
domain: string;
port: number;
};
}
interface Verify {
name: string;
typeOF: any;
type: any;
}
let doc = readFileSync(fileURLToPath(new URL("../config.toml", import.meta.url))).toString();
const parsedDoc = parse(doc) as unknown as TomlData;
function verify(t: Verify[]) {
for (let i: number = 0; i !== t.length; i++) {
if (typeof t[i].typeOF !== t[i].type) {
throw new Error(`Invalid structure: "${t[i].name}" should be a(n) ${t[i].type}`);
}
}
}
verify([
{ name: "marketplace", typeOF: parsedDoc.marketplace, type: "object" },
{ name: "marketplace.enabled", typeOF: parsedDoc.marketplace.enabled, type: "boolean" },
{ name: "marketplace.psk", typeOF: parsedDoc.marketplace.psk, type: "string" },
{ name: "server", typeOF: parsedDoc.server, type: "object" },
{ name: "server.server", typeOF: parsedDoc.server.server, type: "object" },
{ name: "server.server.port", typeOF: parsedDoc.server.server.port, type: "number" },
{ name: "server.server.wisp", typeOF: parsedDoc.server.server.wisp, type: "boolean" },
{ name: "server.server.logging", typeOF: parsedDoc.server.server.logging, type: "boolean" },
{ name: "db", typeOF: parsedDoc.db, type: "object" },
{ name: "db.name", typeOF: parsedDoc.db.name, type: "string" },
{ name: "db.username", typeOF: parsedDoc.db.username, type: "string" },
{ name: "db.password", typeOF: parsedDoc.db.password, type: "string" },
{ name: "db.postgres", typeOF: parsedDoc.db.postgres, type: "boolean" },
{ name: "postgres", typeOF: parsedDoc.postgres, type: "object" },
{ name: "postgres.domain", typeOF: parsedDoc.postgres.domain, type: "string" },
{ name: "postgres.port", typeOF: parsedDoc.postgres.port, type: "number" }
]);
if (parsedDoc.marketplace.psk === "CHANGEME") {
console.warn(chalk.yellow.bold('PSK should be changed from "CHANGEME"'));
}
if (parsedDoc.db.password === "password") {
console.warn(chalk.red.bold("You should change your DB password!!"));
}
export { TomlData, parsedDoc };

View file

@ -1,87 +0,0 @@
import { fileURLToPath } from "node:url";
import chalk from "chalk";
import ora from "ora";
import { ModelStatic } from "sequelize";
import { Catalog, CatalogModel } from "./marketplace.js";
interface Items extends Omit<Catalog, "background_video" | "background_image"> {
background_video?: string;
background_image?: string;
}
async function installItems(db: ModelStatic<CatalogModel>, items: Items[]) {
items.forEach(async (item) => {
await db.create({
package_name: item.package_name,
title: item.title,
image: item.image,
author: item.author,
version: item.version,
description: item.description,
tags: item.tags,
payload: item.payload,
background_video: item.background_video,
background_image: item.background_image,
type: item.type
});
});
}
async function setupDB(db: ModelStatic<CatalogModel>) {
//We have some packages that need to be installed if they aren't.
const items: Items[] = [
{
package_name: "com.nebula.gruvbox",
title: "Gruvbox",
image: "gruvbox.jpeg",
author: "Nebula Services",
version: "1.0.0",
description: "The gruvbox theme",
tags: ["Theme", "Simple"],
payload: "gruvbox.css",
type: "theme"
},
{
package_name: "com.nebula.oled",
title: "Oled theme",
image: "oled.jpg",
author: "Nebula Services",
version: "1.0.0",
description: "A sleek & simple Oled theme for Nebula",
tags: ["Theme", "Simple", "Sleek"],
payload: "oled.css",
type: "theme"
},
{
package_name: "com.nebula.lightTheme",
title: "Light Theme",
image: "light.png",
author: "Nebula Services",
version: "1.0.0",
description: "A sleek light theme for Nebula",
tags: ["Theme", "Simple", "Light"],
payload: "light.css",
type: "theme"
},
{
package_name: "com.nebula.retro",
title: "Retro Theme",
image: "retro.png",
author: "Nebula Services",
version: "1.0.0",
description: "Give a retro look to Nebula",
tags: ["Theme", "Simple", "Dark", "Retro"],
payload: "retro.css",
type: "theme"
}
//To add plugins: plugin types consist of plugin-sw (workerware) & plugin-page (uv.config.inject)
];
const dbItems = await db.findAll();
if (dbItems.length === 0) {
const spinner = ora(chalk.hex("#7967dd")("Performing DB setup...")).start();
await installItems(db, items);
spinner.succeed(chalk.hex("#eb6f92")("DB setup complete!"));
}
}
export { setupDB };

1
server/env.d.ts vendored
View file

@ -1 +0,0 @@
declare module "@rubynetwork/rammerhead/src/server/index.js";

View file

@ -1,229 +0,0 @@
import { createWriteStream } from "node:fs";
import { constants, access, mkdir } from "node:fs/promises";
import { pipeline } from "node:stream/promises";
import { fileURLToPath } from "node:url";
import { FastifyInstance, FastifyRequest } from "fastify";
import { DataTypes, InferAttributes, InferCreationAttributes, Model, Sequelize } from "sequelize";
import { parsedDoc } from "./config.js";
const db = new Sequelize(parsedDoc.db.name, parsedDoc.db.username, parsedDoc.db.password, {
host: parsedDoc.db.postgres ? `${parsedDoc.postgres.domain}` : "localhost",
port: parsedDoc.db.postgres ? parsedDoc.postgres.port : undefined,
dialect: parsedDoc.db.postgres ? "postgres" : "sqlite",
logging: parsedDoc.server.server.logging,
storage: "database.sqlite" //this is sqlite only
});
type CatalogType = "theme" | "plugin-page" | "plugin-sw";
interface Catalog {
package_name: string;
title: string;
description: string;
author: string;
image: string;
tags: object;
version: string;
background_image: string;
background_video: string;
payload: string;
type: CatalogType;
}
interface CatalogModel
extends Catalog,
Model<InferAttributes<CatalogModel>, InferCreationAttributes<CatalogModel>> {}
const catalogAssets = db.define<CatalogModel>("catalog_assets", {
package_name: { type: DataTypes.STRING, unique: true },
title: { type: DataTypes.TEXT },
description: { type: DataTypes.TEXT },
author: { type: DataTypes.TEXT },
image: { type: DataTypes.TEXT },
tags: { type: DataTypes.JSON, allowNull: true },
version: { type: DataTypes.TEXT },
background_image: { type: DataTypes.TEXT, allowNull: true },
background_video: { type: DataTypes.TEXT, allowNull: true },
payload: { type: DataTypes.TEXT },
type: { type: DataTypes.TEXT }
});
function marketplaceAPI(app: FastifyInstance) {
app.get("/api/catalog-stats/", (request, reply) => {
reply.send({
version: "1.0.0",
spec: "Nebula Services",
enabled: true
});
});
// This API returns a list of the assets in the database (SW plugins and themes).
// It also returns the number of pages in the database.
// It can take a `?page=x` argument to display a different page, with a limit of 20 assets per page.
type CatalogAssetsReq = FastifyRequest<{ Querystring: { page: string } }>;
app.get("/api/catalog-assets/", async (request: CatalogAssetsReq, reply) => {
try {
const { page } = request.query;
const pageNum: number = parseInt(page, 10) || 1;
if (pageNum < 1) {
reply.status(400).send({ error: "Page must be a positive number!" });
}
const offset = (pageNum - 1) * 20;
const totalItems = await catalogAssets.count();
const dbAssets = await catalogAssets.findAll({ offset: offset, limit: 20 });
const assets = dbAssets.reduce((acc, asset) => {
acc[asset.package_name] = {
title: asset.title,
description: asset.description,
author: asset.author,
image: asset.image,
tags: asset.tags,
version: asset.version,
background_image: asset.background_image,
background_video: asset.background_video,
payload: asset.payload,
type: asset.type
};
return acc;
}, {});
return reply.send({ assets, pages: Math.ceil(totalItems / 20) });
} catch (error) {
return reply.status(500).send({ error: "An error occured" });
}
});
type PackageReq = FastifyRequest<{ Params: { package: string } }>;
app.get("/api/packages/:package", async (request: PackageReq, reply) => {
try {
const packageRow = await catalogAssets.findOne({
where: { package_name: request.params.package }
});
if (!packageRow) return reply.status(404).send({ error: "Package not found!" });
const details = {
title: packageRow.get("title"),
description: packageRow.get("description"),
image: packageRow.get("image"),
author: packageRow.get("author"),
tags: packageRow.get("tags"),
version: packageRow.get("version"),
background_image: packageRow.get("background_image"),
background_video: packageRow.get("background_video"),
payload: packageRow.get("payload"),
type: packageRow.get("type")
};
reply.send(details);
} catch (error) {
reply.status(500).send({ error: "An unexpected error occured" });
}
});
type UploadReq = FastifyRequest<{ Headers: { psk: string; packagename: string } }>;
type CreateReq = FastifyRequest<{
Headers: { psk: string };
Body: {
uuid: string;
title: string;
image: string;
author: string;
version: string;
description: string;
tags: object | any;
payload: string;
background_video: string;
background_image: string;
type: CatalogType;
};
}>;
interface VerifyStatus {
status: number;
error?: Error;
}
async function verifyReq(
request: UploadReq | CreateReq,
upload: Boolean,
data: any
): Promise<VerifyStatus> {
if (request.headers.psk !== parsedDoc.marketplace.psk) {
return { status: 403, error: new Error("PSK isn't correct!") };
} else if (upload && !request.headers.packagename) {
return { status: 500, error: new Error("No packagename defined!") };
} else if (upload && !data) {
return { status: 400, error: new Error("No file uploaded!") };
} else {
return { status: 200 };
}
}
app.post("/api/upload-asset", async (request: UploadReq, reply) => {
const data = await request.file();
const verify: VerifyStatus = await verifyReq(request, true, data);
if (verify.error !== undefined) {
reply.status(verify.status).send({ status: verify.error.message });
} else {
try {
await pipeline(
data.file,
createWriteStream(
fileURLToPath(
new URL(
`../database_assets/${request.headers.packagename}/${data.filename}`,
import.meta.url
)
)
)
);
} catch (error) {
return reply.status(500).send({
status: `File couldn't be uploaded! (Package most likely doesn't exist)`
});
}
return reply.status(verify.status).send({ status: "File uploaded successfully!" });
}
});
app.post("/api/create-package", async (request: CreateReq, reply) => {
const verify: VerifyStatus = await verifyReq(request, false, undefined);
if (verify.error !== undefined) {
reply.status(verify.status).send({ status: verify.error.message });
} else {
const body: Catalog = {
package_name: request.body.uuid,
title: request.body.title,
image: request.body.image,
author: request.body.author,
version: request.body.version,
description: request.body.description,
tags: request.body.tags,
payload: request.body.payload,
background_video: request.body.background_video,
background_image: request.body.background_image,
type: request.body.type as CatalogType
};
await catalogAssets.create({
package_name: body.package_name,
title: body.title,
image: body.image,
author: body.author,
version: body.version,
description: body.description,
tags: body.tags,
payload: body.payload,
background_video: body.background_video,
background_image: body.background_image,
type: body.type
});
const assets = fileURLToPath(new URL("../database_assets", import.meta.url));
try {
await access(`${assets}/${body.package_name}/`, constants.F_OK);
return reply.status(500).send({ status: "Package already exists!" });
} catch (err) {
await mkdir(`${assets}/${body.package_name}/`);
return reply
.status(verify.status)
.send({ status: "Package created successfully!" });
}
}
});
}
export { marketplaceAPI, db, catalogAssets, Catalog, CatalogModel };

View file

@ -1,88 +0,0 @@
import { createWriteStream } from "node:fs";
import { constants, access, mkdir } from "node:fs/promises";
import { pipeline } from "node:stream/promises";
import { fileURLToPath } from "node:url";
import fastifyCompress from "@fastify/compress";
import fastifyHelmet from "@fastify/helmet";
import fastifyMiddie from "@fastify/middie";
import fastifyMultipart from "@fastify/multipart";
import fastifyStatic from "@fastify/static";
import chalk from "chalk";
import Fastify, { FastifyReply, FastifyRequest } from "fastify";
import gradient from "gradient-string";
//@ts-ignore WHY would I want this typechecked AT ALL
import { handler as ssrHandler } from "../dist/server/entry.mjs";
import { parsedDoc } from "./config.js";
import { setupDB } from "./dbSetup.js";
import { catalogAssets, marketplaceAPI } from "./marketplace.js";
import { serverFactory } from "./serverFactory.js";
const app = Fastify({
logger: parsedDoc.server.server.logging,
ignoreDuplicateSlashes: true,
ignoreTrailingSlash: true,
serverFactory: serverFactory
});
await app.register(fastifyCompress, {
encodings: ["br", "gzip", "deflate"]
});
await app.register(fastifyMultipart);
await app.register(fastifyHelmet, {
xPoweredBy: false,
crossOriginEmbedderPolicy: true,
crossOriginOpenerPolicy: true,
contentSecurityPolicy: false //Disabled because astro DOES NOT LIKE IT
});
await app.register(fastifyStatic, {
root: fileURLToPath(new URL("../dist/client", import.meta.url))
});
//Our marketplace API. Not middleware as I don't want to deal with that LOL. Just a function that passes our app to it.
if (parsedDoc.marketplace.enabled) {
await app.register(fastifyStatic, {
root: fileURLToPath(new URL("../database_assets", import.meta.url)),
prefix: "/packages/",
decorateReply: false
});
marketplaceAPI(app);
}
await app.register(fastifyMiddie);
app.use(ssrHandler);
const port: number =
parseInt(process.env.PORT as string) || parsedDoc.server.server.port || parseInt("8080");
const titleText = `
_ _ _ _ ____ _
| \\ | | ___| |__ _ _| | __ _ / ___| ___ _ ____ _(_) ___ ___ ___
| \\| |/ _ \\ '_ \\| | | | |/ _' | \\___ \\ / _ \\ '__\\ \\ / / |/ __/ _ \\/ __|
| |\\ | __/ |_) | |_| | | (_| | ___) | __/ | \\ V /| | (_| __/\\__ \\
|_| \\_|\\___|_.__/ \\__,_|_|\\__,_| |____/ \\___|_| \\_/ |_|\\___\\___||___/
`;
const titleColors = {
purple: "#7967dd",
pink: "#eb6f92"
};
console.log(gradient(Object.values(titleColors)).multiline(titleText as string));
app.listen({ port: port, host: "0.0.0.0" }).then(async () => {
console.log(
chalk.hex("#7967dd")(
`Server listening on ${chalk.hex("#eb6f92").bold("http://localhost:" + port + "/")}`
)
);
console.log(
chalk.hex("#7967dd")(
`Server also listening on ${chalk.hex("#eb6f92").bold("http://0.0.0.0:" + port + "/")}`
)
);
if (parsedDoc.marketplace.enabled) {
await catalogAssets.sync();
await setupDB(catalogAssets);
}
});

View file

@ -1,33 +0,0 @@
import { createServer } from "node:http";
import {
FastifyServerFactory,
FastifyServerFactoryHandler,
RawServerDefault,
} from "fastify";
import wisp from "wisp-server-node";
import { LOG_LEVEL, WispOptions } from "wisp-server-node/dist/Types.js";
import { parsedDoc } from "./config.js";
const wispOptions: WispOptions = {
logLevel: parsedDoc.server.server.logging ? LOG_LEVEL.DEBUG : LOG_LEVEL.NONE,
pingInterval: 30,
};
const serverFactory: FastifyServerFactory = (
handler: FastifyServerFactoryHandler
): RawServerDefault => {
const httpServer = createServer();
httpServer.on("request", (req, res) => {
handler(req, res);
});
httpServer.on("upgrade", (req, socket, head) => {
if (parsedDoc.server.server.wisp) {
if (req.url?.endsWith("/wisp/")) {
wisp.routeRequest(req, socket as any, head, wispOptions);
}
}
});
return httpServer;
};
export { serverFactory };

View file

@ -1,11 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"noEmit": false,
"esModuleInterop": true,
"skipLibCheck": true,
"paths": {}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View file

@ -1,61 +0,0 @@
---
interface Props {
title: string;
body: string;
href: string;
}
const { href, title, body } = Astro.props;
---
<li class="link-card">
<a href={href}>
<h2>
{title}
<span>&rarr;</span>
</h2>
<p>
{body}
</p>
</a>
</li>
<style>
.link-card {
list-style: none;
display: flex;
padding: 1px;
background-color: #23262d;
background-image: none;
background-size: 400%;
border-radius: 7px;
background-position: 100%;
transition: background-position 0.6s cubic-bezier(0.22, 1, 0.36, 1);
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.1);
}
.link-card > a {
width: 100%;
text-decoration: none;
line-height: 1.4;
padding: calc(1.5rem - 1px);
border-radius: 8px;
color: white;
background-color: #23262d;
opacity: 0.8;
}
h2 {
margin: 0;
font-size: 1.25rem;
transition: color 0.6s cubic-bezier(0.22, 1, 0.36, 1);
}
p {
margin-top: 0.5rem;
margin-bottom: 0;
}
.link-card:is(:hover, :focus-within) {
background-position: 0;
background-image: var(--accent-gradient);
}
.link-card:is(:hover, :focus-within) h2 {
color: rgb(var(--accent-light));
}
</style>

View file

@ -1,119 +0,0 @@
---
import { MARKETPLACE_ENABLED } from "astro:env/client";
import { Icon } from "astro-icon/components";
import { getLangFromUrl, useTranslations } from "../i18n/utils";
import { isMobileNavOpen } from "../store.js";
import HeaderButton from "./HeaderButton.astro";
import Logo from "./Logo.astro";
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
---
<div
id="navbar"
class="flex h-16 flex-row items-center justify-end border-b-2 border-border-color bg-navbar-color px-4 z-30 relative"
>
<div class="w-1/8">
{/* Typical desktop menu */}
<div class="relative flex-row hidden lg:flex">
<HeaderButton text={t("header.home")} route={`/${lang}/`}>
<Icon
name="ph:house-bold"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
<HeaderButton text={t("header.games")} route={`/${lang}/games/`}>
<Icon
name="ph:cube"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
{
/* Astro won't let us pass the icon as a prop so it's going into the outlet here. */
}
</HeaderButton>
<HeaderButton
text={t("header.settings")}
route={`/${lang}/settings/appearance`}
>
<Icon
name="ph:wrench-fill"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
{MARKETPLACE_ENABLED &&
<HeaderButton text={t("header.catalog")} route={`/${lang}/catalog/1`}>
<Icon
name="ph:shopping-bag-open-fill"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
}
<HeaderButton text={t("header.morelinks")}>
<Icon
name="ph:link-bold"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
</div>
{/* Mobile hamburger menu */}
<div class="flex lg:hidden" id="mobileNavTrigger" transition:persist>
<Icon
name="ph:text-align-justify-bold"
class="h-9 w-9 text-text-color"
id="hamburger_menu"
/>
<Icon
name="ph:caret-right-bold"
class="h-9 w-9 text-text-color hidden"
id="right_caret"
/>
</div>
</div>
</div>
<script>
import { isMobileNavOpen } from "../store.js";
let isMobileNavOpenLocal = true;
const right_caret = document.getElementById("right_caret");
const hamburger_menu = document.getElementById("hamburger_menu");
const mobileNavTrigger = document.getElementById("mobileNavTrigger");
// Create a copy of the nano store so we can make this a toggle
// Set the store to true when the button is clicked
function openDialog() {
if (isMobileNavOpenLocal == false) {
isMobileNavOpen.set(true);
if (hamburger_menu && right_caret) {
hamburger_menu.style.display = "none";
right_caret.style.display = "block";
}
} else {
isMobileNavOpen.set(false);
if (hamburger_menu && right_caret) {
hamburger_menu.style.display = "block";
right_caret.style.display = "none";
}
}
}
isMobileNavOpen.subscribe((open) => {
if (open) {
isMobileNavOpenLocal = true;
if (hamburger_menu && right_caret) {
hamburger_menu.style.display = "none";
right_caret.style.display = "block";
}
} else {
isMobileNavOpenLocal = false;
if (hamburger_menu && right_caret) {
hamburger_menu.style.display = "block";
right_caret.style.display = "none";
}
}
});
// Add an event listener to the button
if (mobileNavTrigger) {
mobileNavTrigger.addEventListener("click", openDialog);
}
</script>

View file

@ -1,16 +0,0 @@
---
const { text, route } = Astro.props;
---
<a
class="group flex w-full flex-row items-center justify-center border-t-2 border-solid border-navbar-text-color p-4 lg:border-none h-1/3 lg:h-fit"
id="header_anchor"
href={route}
>
<slot />
<span
class="font-roboto pl-2 text-center text-3xl font-bold text-text-color roboto transition duration-500 group-hover:text-text-hover-color lg:text-xl text-nowrap"
>
{text}
</span>
</a>

View file

@ -1,23 +0,0 @@
<div class="w-full h-full bg-primary text-navbar-text-color flex justify-center items-center">
<svg xmlns="http://www.w3.org/2000/svg" version="1.2" viewBox="0 0 400 400" width="400" height="400" fill="#5e17eb" class="animate-pulse-brighter w-48 h-48">
<g id="svgg">
<path id="path0" fill-rule="evenodd" class="s0" d="m213.6 84c1 0.3 3.4 0.7 5.1 1 1.8 0.2 4.1 0.7 5.2 1 13.2 4.1 20.3 6.8 24.5 9.1 0.6 0.3 2.3 1.2 3.8 2 2.8 1.4 13.1 8 14.4 9.2 0.5 0.3 2.3 1.9 4.2 3.5 6.7 5.5 15.5 14.9 19.2 20.4 1 1.4 2 2.7 2.2 2.8 0.3 0.1 0.5 0.5 0.5 0.8 0 0.3 1.2 2.2 2.5 4.3 2.3 3.4 7.8 14.3 9.8 19.3 0.8 2.1 0.9 2.2 10 4.9 5.6 1.6 11.1 3.4 11.7 3.8 0.3 0.2 2.4 1.1 4.7 1.9 11.1 4.1 23 12.5 27.3 19.4 5.5 8.7 3.6 20.5-4.5 28.5-3.1 3-7.5 6.4-8.4 6.4-0.3 0-0.7 0.2-0.8 0.5-1.1 2.3-23.3 11.2-35.9 14.3-3.2 0.9-3.5 1.2-5.7 6.8-7.5 19-25.5 40.6-42.3 51.1-1.6 1-3.1 2-3.3 2.2-0.1 0.2-0.9 0.7-1.7 1.2-0.8 0.4-2.2 1.2-2.9 1.6-0.8 0.4-1.6 1-1.8 1.1-0.5 0.5-4.1 2.2-8.1 3.9-1.8 0.8-3.8 1.6-4.5 2-0.7 0.3-3.1 1.1-5.2 1.8-8.3 2.6-9.8 3-21 4.9-6.4 1.1-25.3 1.3-30.5 0.3-1.9-0.3-5.8-1.1-8.6-1.6-6.8-1.3-12.7-3-20-5.7-3.3-1.2-18-8.8-19.7-10.1-0.9-0.7-4.1-3.1-7.1-5.2-5.7-4.1-17.9-15.9-20.7-20-0.9-1.2-2-2.7-2.5-3.2-3.2-3.3-13.7-21.7-13.7-24.1 0-0.6-0.2-1.2-0.6-1.4-0.3-0.2-0.8-1.2-1-2.3-0.4-1.9-1.7-2.7-6.5-3.8-23.2-5.6-43.1-17.2-48.6-28.5-7.1-14.4 4.5-31.3 27.3-39.7 1.8-0.7 4.1-1.6 5.2-2 3.7-1.5 8-2.9 19.5-6.2 1.6-0.5 2.8-1.2 2.8-1.7 0-2.4 9.8-21.6 13.1-25.7 0.2-0.4 1.4-1.8 2.5-3.2 13.8-18.1 30.2-30.6 50.6-38.8 4.3-1.7 6-2.3 14.3-4.5 5.5-1.6 11.2-2.4 18.4-2.9 7.9-0.5 24-0.2 26.8 0.6zm-29.7 17.3c-0.2 0.1-7.3 1.7-12.9 2.7-1.7 0.4-4.3 1.2-5.8 1.9-1.5 0.6-3.9 1.5-5.5 2-1.5 0.4-3.3 1.3-4 2-0.7 0.7-1.7 1.2-2.3 1.2-1.2 0-9.5 4.5-9.8 5.3-0.2 0.3-0.5 0.6-0.9 0.6-1.9 0-19.6 16-23.8 21.6-9.3 12.2-16.1 27.4-19.2 42.7-2 10.1-1.1 37.5 1.4 41.4 0 0.1 3.7 0.9 8.1 1.8 9.5 1.9 12.8 2.4 34.6 4.9 38.5 4.5 107.9 2.2 138.3-4.5 1.4-0.3 4.1-0.9 6.1-1.3 4.2-0.8 3.4 0.2 4.9-7.1 1.6-8.3 1.7-27 0.1-34.6-1.5-7.1-3.2-13.4-3.7-14.2-0.3-0.4-0.7-1.5-0.9-2.6-2.8-12.1-19.2-34.1-33-44.3-2.9-2.1-5.5-4-5.8-4.3-2.8-2.2-4.9-3.1-7.2-3.1-2.1 0-2.6-0.2-2.7-1.3-0.2-1.5-5.7-4.5-6.3-3.5-0.7 1.1-2.4 0.6-2.7-0.7-0.4-1.3-1.2-1.6-5.8-2.1-1.6-0.2-4-0.9-5.5-1.6-3.9-1.8-5.3-2.2-10.2-2.6-4.6-0.4-25.2-0.7-25.5-0.3zm74.3 42.2c7.4 9.8 4.8 23.5-4.6 24.9-6.9 1-20.9-5.8-21-10.2 0-0.2-0.3-0.8-0.8-1.3-6.4-6.8-5-20.8 2.4-24.1 6.7-2.9 17.2 1.8 24 10.7zm-176.4 36.4c-0.1-0.1-4.6 1.1-5.9 1.6-0.7 0.3-3 1.2-5.1 2-9.9 3.8-15.1 6.8-19.6 11.5-3.4 3.5-3.3 4.5 0.5 8.7 1 1 11.3 7.6 12 7.6 0.2 0 1.7 0.6 3.4 1.3 1.6 0.8 3.6 1.6 4.3 1.9 1.8 0.8 9.3 3.3 9.9 3.3 0.3 0 0.3-2 0-4.4-0.6-5.6-0.6-24.5 0.1-29.6 0.3-2.1 0.5-3.9 0.4-3.9zm229.3-0.3c-0.2 0 0 1 0.2 2.1 0.6 2.8 0.6 31.3 0 34-0.5 2.2-0.4 2.2 1.3 1.8 3.1-0.7 12.9-4.5 18.3-7 8.5-4 14.3-10.1 12.6-13.3-1.1-2.1-6.7-7.2-7.9-7.2-0.4 0-0.9-0.2-1-0.5-0.4-1.1-11.8-6.1-19.2-8.5-2.3-0.7-4.2-1.4-4.3-1.4zm-199.4 63.4l-3.1-0.4 1.8 3.2c0.9 1.8 1.9 3.4 2.2 3.5 0.3 0.1 0.5 0.6 0.5 1.1 0 0.4 0.6 1.5 1.3 2.3 0.7 0.9 1.5 1.9 1.8 2.2 0.3 0.4 0.8 1.2 1.1 1.7 6.2 10.7 35.6 33.5 43.3 33.5 0.2 0 1.3 0.4 2.5 0.9 2.5 1.2 10.6 3.4 15.3 4.2 9.5 1.8 11.6 2.1 17.4 2.1 6.6 0 16.4-1.3 22.9-3 2.2-0.5 5.2-1.3 6.8-1.7 1.6-0.3 3.2-0.9 3.5-1.2 0.4-0.3 1.1-0.6 1.6-0.6 2.3 0 22-10.6 24-12.9 0.2-0.2 2.2-1.9 4.5-3.7 5.7-4.5 11.8-11 17.1-18.4 1.6-2.3 3.2-4.5 3.6-4.9 0.4-0.4 0.7-1 0.7-1.2 0-0.2 0.8-1.9 1.9-3.6 1.1-1.7 1.9-3.2 1.9-3.4 0-0.2-3.8 0.4-11 1.6-31.7 5.4-85.1 6.7-126.9 3.1-9.6-0.8-23.1-2.3-27.8-3.2-2.2-0.4-5.3-0.9-6.9-1.2z"/>
</g>
</svg>
</div>
<style>
@keyframes pulse-brighter {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.animate-pulse-brighter {
animation: pulse-brighter 2s infinite;
height: 11rem;
}
</style>

View file

@ -1,15 +0,0 @@
<svg
version="1.2"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 400 400"
width="400"
height="400"
style="width: 100%; height: 100%;"
><title>nebula</title><g id="svgg"
><path
id="path0"
fill-rule="evenodd"
d="m213.6 84c1 0.3 3.4 0.7 5.1 1 1.8 0.2 4.1 0.7 5.2 1 13.2 4.1 20.3 6.8 24.5 9.1 0.6 0.3 2.3 1.2 3.8 2 2.8 1.4 13.1 8 14.4 9.2 0.5 0.3 2.3 1.9 4.2 3.5 6.7 5.5 15.5 14.9 19.2 20.4 1 1.4 2 2.7 2.2 2.8 0.3 0.1 0.5 0.5 0.5 0.8 0 0.3 1.2 2.2 2.5 4.3 2.3 3.4 7.8 14.3 9.8 19.3 0.8 2.1 0.9 2.2 10 4.9 5.6 1.6 11.1 3.4 11.7 3.8 0.3 0.2 2.4 1.1 4.7 1.9 11.1 4.1 23 12.5 27.3 19.4 5.5 8.7 3.6 20.5-4.5 28.5-3.1 3-7.5 6.4-8.4 6.4-0.3 0-0.7 0.2-0.8 0.5-1.1 2.3-23.3 11.2-35.9 14.3-3.2 0.9-3.5 1.2-5.7 6.8-7.5 19-25.5 40.6-42.3 51.1-1.6 1-3.1 2-3.3 2.2-0.1 0.2-0.9 0.7-1.7 1.2-0.8 0.4-2.2 1.2-2.9 1.6-0.8 0.4-1.6 1-1.8 1.1-0.5 0.5-4.1 2.2-8.1 3.9-1.8 0.8-3.8 1.6-4.5 2-0.7 0.3-3.1 1.1-5.2 1.8-8.3 2.6-9.8 3-21 4.9-6.4 1.1-25.3 1.3-30.5 0.3-1.9-0.3-5.8-1.1-8.6-1.6-6.8-1.3-12.7-3-20-5.7-3.3-1.2-18-8.8-19.7-10.1-0.9-0.7-4.1-3.1-7.1-5.2-5.7-4.1-17.9-15.9-20.7-20-0.9-1.2-2-2.7-2.5-3.2-3.2-3.3-13.7-21.7-13.7-24.1 0-0.6-0.2-1.2-0.6-1.4-0.3-0.2-0.8-1.2-1-2.3-0.4-1.9-1.7-2.7-6.5-3.8-23.2-5.6-43.1-17.2-48.6-28.5-7.1-14.4 4.5-31.3 27.3-39.7 1.8-0.7 4.1-1.6 5.2-2 3.7-1.5 8-2.9 19.5-6.2 1.6-0.5 2.8-1.2 2.8-1.7 0-2.4 9.8-21.6 13.1-25.7 0.2-0.4 1.4-1.8 2.5-3.2 13.8-18.1 30.2-30.6 50.6-38.8 4.3-1.7 6-2.3 14.3-4.5 5.5-1.6 11.2-2.4 18.4-2.9 7.9-0.5 24-0.2 26.8 0.6zm-29.7 17.3c-0.2 0.1-7.3 1.7-12.9 2.7-1.7 0.4-4.3 1.2-5.8 1.9-1.5 0.6-3.9 1.5-5.5 2-1.5 0.4-3.3 1.3-4 2-0.7 0.7-1.7 1.2-2.3 1.2-1.2 0-9.5 4.5-9.8 5.3-0.2 0.3-0.5 0.6-0.9 0.6-1.9 0-19.6 16-23.8 21.6-9.3 12.2-16.1 27.4-19.2 42.7-2 10.1-1.1 37.5 1.4 41.4 0 0.1 3.7 0.9 8.1 1.8 9.5 1.9 12.8 2.4 34.6 4.9 38.5 4.5 107.9 2.2 138.3-4.5 1.4-0.3 4.1-0.9 6.1-1.3 4.2-0.8 3.4 0.2 4.9-7.1 1.6-8.3 1.7-27 0.1-34.6-1.5-7.1-3.2-13.4-3.7-14.2-0.3-0.4-0.7-1.5-0.9-2.6-2.8-12.1-19.2-34.1-33-44.3-2.9-2.1-5.5-4-5.8-4.3-2.8-2.2-4.9-3.1-7.2-3.1-2.1 0-2.6-0.2-2.7-1.3-0.2-1.5-5.7-4.5-6.3-3.5-0.7 1.1-2.4 0.6-2.7-0.7-0.4-1.3-1.2-1.6-5.8-2.1-1.6-0.2-4-0.9-5.5-1.6-3.9-1.8-5.3-2.2-10.2-2.6-4.6-0.4-25.2-0.7-25.5-0.3zm74.3 42.2c7.4 9.8 4.8 23.5-4.6 24.9-6.9 1-20.9-5.8-21-10.2 0-0.2-0.3-0.8-0.8-1.3-6.4-6.8-5-20.8 2.4-24.1 6.7-2.9 17.2 1.8 24 10.7zm-176.4 36.4c-0.1-0.1-4.6 1.1-5.9 1.6-0.7 0.3-3 1.2-5.1 2-9.9 3.8-15.1 6.8-19.6 11.5-3.4 3.5-3.3 4.5 0.5 8.7 1 1 11.3 7.6 12 7.6 0.2 0 1.7 0.6 3.4 1.3 1.6 0.8 3.6 1.6 4.3 1.9 1.8 0.8 9.3 3.3 9.9 3.3 0.3 0 0.3-2 0-4.4-0.6-5.6-0.6-24.5 0.1-29.6 0.3-2.1 0.5-3.9 0.4-3.9zm229.3-0.3c-0.2 0 0 1 0.2 2.1 0.6 2.8 0.6 31.3 0 34-0.5 2.2-0.4 2.2 1.3 1.8 3.1-0.7 12.9-4.5 18.3-7 8.5-4 14.3-10.1 12.6-13.3-1.1-2.1-6.7-7.2-7.9-7.2-0.4 0-0.9-0.2-1-0.5-0.4-1.1-11.8-6.1-19.2-8.5-2.3-0.7-4.2-1.4-4.3-1.4zm-199.4 63.4l-3.1-0.4 1.8 3.2c0.9 1.8 1.9 3.4 2.2 3.5 0.3 0.1 0.5 0.6 0.5 1.1 0 0.4 0.6 1.5 1.3 2.3 0.7 0.9 1.5 1.9 1.8 2.2 0.3 0.4 0.8 1.2 1.1 1.7 6.2 10.7 35.6 33.5 43.3 33.5 0.2 0 1.3 0.4 2.5 0.9 2.5 1.2 10.6 3.4 15.3 4.2 9.5 1.8 11.6 2.1 17.4 2.1 6.6 0 16.4-1.3 22.9-3 2.2-0.5 5.2-1.3 6.8-1.7 1.6-0.3 3.2-0.9 3.5-1.2 0.4-0.3 1.1-0.6 1.6-0.6 2.3 0 22-10.6 24-12.9 0.2-0.2 2.2-1.9 4.5-3.7 5.7-4.5 11.8-11 17.1-18.4 1.6-2.3 3.2-4.5 3.6-4.9 0.4-0.4 0.7-1 0.7-1.2 0-0.2 0.8-1.9 1.9-3.6 1.1-1.7 1.9-3.2 1.9-3.4 0-0.2-3.8 0.4-11 1.6-31.7 5.4-85.1 6.7-126.9 3.1-9.6-0.8-23.1-2.3-27.8-3.2-2.2-0.4-5.3-0.9-6.9-1.2z"
class="s0"></path></g
></svg
>

Before

Width:  |  Height:  |  Size: 3.4 KiB

View file

@ -1,58 +0,0 @@
---
import { Icon } from "astro-icon/components";
import { getLangFromUrl, useTranslations } from "../i18n/utils";
import HeaderButton from "./HeaderButton.astro";
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
import { MARKETPLACE_ENABLED } from "astro:env/client";
---
<div
class="h-full mt-16 flex w-full flex-col justify-evenly bg-navbar-color m-auto"
id="mobileNavMenu"
>
<HeaderButton text={t("header.home")} route={`/${lang}/`}>
<Icon
name="ph:house-bold"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
<HeaderButton text={t("header.games")} route={`/${lang}/games/`}>
<Icon
name="ph:cube"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
<HeaderButton
text={t("header.settings")}
route={`/${lang}/settings/appearance`}
>
<Icon
name="ph:wrench-fill"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
{MARKETPLACE_ENABLED &&
<HeaderButton text={t("header.catalog")} route={`/${lang}/catalog/1`}>
<Icon
name="ph:shopping-bag-open-fill"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
}
<HeaderButton text={t("header.morelinks")}>
<Icon
name="ph:link-bold"
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
/>
</HeaderButton>
</div>
<script>
import { fade } from "astro:transitions";
import { isMobileNavOpen } from "../store.js";
function closeMobileNav() {
isMobileNavOpen.set(false);
}
const mobileNavMenu = document.getElementById("mobileNavMenu");
mobileNavMenu!.addEventListener("click", closeMobileNav);
</script>

View file

@ -1,17 +0,0 @@
---
const { title, route } = Astro.props;
---
<a
href={route}
class="snap-center snap-always group flex flex-col items-center md:p-0 max-sm:p-3 sm:p-3 bg-navbar-color w-full rounded-3xl md:flex-row md:bg-none md:rounded-none"
>
<div class="xl:p-2 max-sm:p-2 sm:p-2 md:p-0">
<slot />
</div>
<div
class="max-md:min-w-24 font-roboto text-center font-bold text-text-color roboto transition duration-500 group-hover:text-text-hover-color md:text-xl text-nowrap"
>
{title}
</div>
</a>

View file

@ -1,47 +0,0 @@
<script lang="ts">
import { Suspense } from "@svelte-drama/suspense";
import { Settings } from "@utils/settings/index";
export let page;
export let lang;
async function getAssets() {
const response = await fetch("/api/catalog-assets?page=" + page);
const data = await response.json();
return data.assets;
}
const assets = getAssets();
</script>
<div class="text-3xl font-roboto font-bold text-text-color p-10">
<Suspense let:suspend>
<div slot="loading">
<p class="text-4xl"> Loading... </p>
</div>
{#await suspend(assets) then data}
{#if Object.keys(data).length > 0}
<div class="flex flex-row gap-6 flex-wrap justify-center">
{#each Object.entries(data) as [key, asset]}
<a href={`/${lang}/catalog/package/${key}`}>
<div class="bg-navbar-color w-64 rounded-3xl shadow-lg overflow-hidden transition-transform duration-300 hover:scale-105">
<img src={`/packages/${key}/${asset.image}`} alt={asset.title} class="w-full h-40 object-cover" />
<div class="p-6 text-sm">
<p class="font-semibold text-2xl mb-2"> {asset.title} </p>
<p class="mb-4"> {asset.description} </p>
<div class="flex flex-wrap gap-2 mb-4 w-full">
{#each asset.tags as tag}
<p class="bg-navbar-text-color text-navbar-color font-bold px-3 py-1 rounded-md text-center"> {tag} </p>
{/each}
</div>
<div>
<strong>Version:</strong>
{asset.version}
</div>
<div><strong>Type:</strong> {asset.type === "plugin-page" || asset.type === "plugin-sw" ? "plugin" : asset.type}</div>
</div>
</div>
</a>
{/each}
</div>
{/if}
{/await}
</Suspense>
</div>

View file

@ -1,57 +0,0 @@
<script lang="ts">
import { Suspense } from "@svelte-drama/suspense";
import { Settings, settings } from "@utils/settings/index";
import Parent from "./Parent.svelte";
async function getItem(item) {
try {
const response = await fetch(`/api/packages/${item.name}`);
const data = await response.json();
return {
...data,
package_name: item.name
};
} catch (error) {
console.error("error: failed to fetch", error);
return null;
}
}
async function getAssets() {
const items = JSON.parse(localStorage.getItem(Settings.PluginSettings.plugins)) || [];
const promises = items.map(getItem);
const dataArray = await Promise.all(promises);
let accumulatedData = dataArray.filter((data) => data !== null);
accumulatedData = accumulatedData.filter(({ remove }) => remove !== true);
console.log(JSON.stringify(accumulatedData));
return accumulatedData;
}
let assets = getAssets();
let compRef = [];
</script>
<Suspense let:suspend>
{#await suspend(assets) then data}
{#each Object.entries(data) as [key, asset]}
<Parent bind:this={compRef[key]}>
<div class="rounded-3xl bg-navbar-color w-64 flex flex-col cursor-pointer">
<div class="w-full">
<img src={`/packages/${asset.package_name}/${asset.image}`} alt="plugin" class="aspect-[16/9] rounded-t-3xl"/>
</div>
<div class="h-2/6 text-center content-center p-3 font-semibold items-center flex flex-col">
<div class="text-2xl"> {asset.title} </div>
<div class="flex flex-row">
<div class="h-8 w-8 cursor-pointer" on:click={() => {settings.marketPlaceSettings.uninstall(asset.type === "page" ? "plugin-page" : "plugin-sw", asset.package_name); compRef[key].$destroy()}}>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256" {...$$props}>
<path fill="currentColor" d="M216 48h-40v-8a24 24 0 0 0-24-24h-48a24 24 0 0 0-24 24v8H40a8 8 0 0 0 0 16h8v144a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16V64h8a8 8 0 0 0 0-16M112 168a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm48 0a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm0-120H96v-8a8 8 0 0 1 8-8h48a8 8 0 0 1 8 8Z" />
</svg>
</div>
<a class="h-8 w-8 cursor-pointer" href={`../catalog/package/${asset.package_name}`}>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256" {...$$props}>
<path fill="currentColor" d="M192 136v72a16 16 0 0 1-16 16H48a16 16 0 0 1-16-16V80a16 16 0 0 1 16-16h72a8 8 0 0 1 0 16H48v128h128v-72a8 8 0 0 1 16 0m32-96a8 8 0 0 0-8-8h-64a8 8 0 0 0-5.66 13.66L172.69 72l-42.35 42.34a8 8 0 0 0 11.32 11.32L184 83.31l26.34 26.35A8 8 0 0 0 224 104Z" />
</svg>
</a>
</div>
</div>
</div>
</Parent>
{/each}
{/await}
</Suspense>

View file

@ -1,64 +0,0 @@
<script lang="ts">
import { Suspense } from "@svelte-drama/suspense";
import { Settings, settings } from "@utils/settings/index";
import Parent from "./Parent.svelte";
async function getItem(item) {
try {
const response = await fetch(`/api/packages/${item}`);
const data = await response.json();
return {
...data,
package_name: item
};
} catch (error) {
console.error("error: failed to fetch", error);
return null;
}
}
async function getAssets() {
const items = JSON.parse(localStorage.getItem(Settings.AppearanceSettings.themes)) || [];
const promises = items.map(getItem);
const dataArray = await Promise.all(promises);
const accumulatedData = dataArray.filter((data) => data !== null);
console.log(JSON.stringify(accumulatedData));
return accumulatedData;
}
let assets = getAssets();
let compRef = [];
</script>
<Suspense let:suspend>
<div class="rounded-3xl bg-navbar-color w-64 flex flex-col cursor-pointer">
<div class="w-full" on:click={() => {settings.marketPlaceSettings.changeTheme(true)}}>
<img src='/classic_theme.png' alt="Classic Nebula" class="aspect-[16/9] rounded-t-3xl"/>
</div>
<div class="h-2/6 text-center content-center p-3 font-semibold items-center flex flex-col text-2xl">
Classic Nebula
</div>
</div>
{#await suspend(assets) then data}
{#each Object.entries(data) as [key, asset]}
<Parent bind:this={compRef[key]}>
<div class="rounded-3xl bg-navbar-color w-64 flex flex-col cursor-pointer">
<div class="w-full" on:click={() => {settings.marketPlaceSettings.changeTheme(false, asset.payload, asset.background_video, asset.background_image, asset.package_name)}}>
<img src={`/packages/${asset.package_name}/${asset.image}`} alt="theme" class="aspect-[16/9] rounded-t-3xl"/>
</div>
<div class="h-2/6 text-center content-center p-3 font-semibold items-center flex flex-col">
<div class="text-2xl"> {asset.title} </div>
<div class="flex flex-row">
<div class="h-8 w-8 cursor-pointer" on:click={() => {settings.marketPlaceSettings.uninstall("theme", asset.package_name); settings.marketPlaceSettings.changeTheme(true); compRef[key].$destroy()}}>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256" {...$$props}>
<path fill="currentColor" d="M216 48h-40v-8a24 24 0 0 0-24-24h-48a24 24 0 0 0-24 24v8H40a8 8 0 0 0 0 16h8v144a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16V64h8a8 8 0 0 0 0-16M112 168a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm48 0a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm0-120H96v-8a8 8 0 0 1 8-8h48a8 8 0 0 1 8 8Z" />
</svg>
</div>
<a class="h-8 w-8 cursor-pointer" href={`../catalog/package/${asset.package_name}`}>
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256" {...$$props}>
<path fill="currentColor" d="M192 136v72a16 16 0 0 1-16 16H48a16 16 0 0 1-16-16V80a16 16 0 0 1 16-16h72a8 8 0 0 1 0 16H48v128h128v-72a8 8 0 0 1 16 0m32-96a8 8 0 0 0-8-8h-64a8 8 0 0 0-5.66 13.66L172.69 72l-42.35 42.34a8 8 0 0 0 11.32 11.32L184 83.31l26.34 26.35A8 8 0 0 0 224 104Z" />
</svg>
</a>
</div>
</div>
</div>
</Parent>
{/each}
{/await}
</Suspense>

View file

@ -1,2 +0,0 @@
<!-- Legit just so we can use $destroy() -->
<slot />

View file

@ -1,19 +0,0 @@
---
import { Image } from "astro:assets";
import { type ImageMetadata } from "astro";
const images = import.meta.glob<{ default: ImageMetadata }>(
"/src/assets/credits/*.{jpeg,jpg,png,gif,webp}"
);
interface Props {
image?: string;
name: string;
link: string;
}
const { image, name, link } = Astro.props;
---
<a class="rounded-md bg-navbar-color h-50 w-50 p-2 flex flex-col items-center" href={link} target="_blank" rel="noopener noreferrer">
{image && <Image loading='lazy' class='w-32 h-32 object-cover rounded-md' src={images[image]()} alt={name} />}
<p class="h-12 w-full text-text-color flex items-center justify-center text-xl font-semibold">{name}</p>
</a>

View file

@ -1,15 +0,0 @@
<script>
import { settings, Settings } from "@utils/settings/index";
// This loads the settings in a nice way
settings.tabSettings.cloakTab(localStorage.getItem(Settings.TabSettings.tabCloak) as string || "default");
settings.proxySettings.changeProxy(localStorage.getItem(Settings.ProxySettings.proxy) as string || "automatic");
settings.proxySettings.openIn(localStorage.getItem(Settings.ProxySettings.openIn) as string || "embed");
settings.proxySettings.setSearchEngine(localStorage.getItem(Settings.ProxySettings.searchEngine) as string || "ddg");
settings.proxySettings.setWispURL(localStorage.getItem(Settings.ProxySettings.wispServerURL) as string || "default");
settings.proxySettings.setTransport(localStorage.getItem(Settings.ProxySettings.transport) as string || "libcurl");
settings.marketPlaceSettings.changeTheme(false, undefined, localStorage.getItem(Settings.AppearanceSettings.video) as string, localStorage.getItem(Settings.AppearanceSettings.image) as string, localStorage.getItem(Settings.AppearanceSettings.themeName) as string);
document.addEventListener("astro:after-swap", function() {
settings.tabSettings.cloakTab(localStorage.getItem(Settings.TabSettings.tabCloak) as string || "default");
//settings.marketPlaceSettings.changeTheme(false);
});
</script>

View file

@ -1,53 +0,0 @@
---
interface Inputs {
input: boolean;
required?: boolean;
placeholder?: string;
}
interface SelectOptions {
value: string;
name: string;
disabled: boolean;
}
interface Selects {
select: boolean;
name?: string;
multiple?: boolean;
options?: SelectOptions[];
}
interface Buttons {
name: string;
id: string;
}
interface Props {
title: string;
description: string;
input: Inputs;
select: Selects;
button: Buttons;
}
const { title, description, input, select, button } = Astro.props;
---
<div class="w-64 rounded-3xl bg-navbar-color h-64 flex flex-col items-center p-4">
<h1 class="text-3xl font-bold mb-2"> { title } </h1>
<p class="text-md w-full text-ellipsis text-center"> { description } </p>
<div class="w-full h-full flex-grow flex justify-center items-center flex-col gap-4">
<!-- We only want to render an input if it's enabled -->
{input.input &&
<input class="text-md w-full h-10 p-2 bg-input border border-input-border-color rounded-md text-input-text" required={input.required} placeholder={input.placeholder}></input>
}
<!-- Same with dropdown selections -->
{select.select &&
<select id={select.name?.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()} class="text-md w-full h-10 p-2 bg-input border border-input-border-color rounded-md text-input-text" multiple={select.multiple} name={select.name}>
{select.options!.map((option) => (
<option disabled={option.disabled} value={option.value}>{option.name}</option>
))}
</select>
}
<button id={button.id.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()} class="w-36 h-10 rounded-md border border-input-border-color text-input-text bg-input hover:border-input hover:bg-input-border-color hover:text-input hover:font-bold active:bg-input active:text-input-text transition-all duration-200">
{button.name}
</button>
</div>
</div>

View file

@ -1,13 +0,0 @@
---
const { image, title } = Astro.props;
import { Image } from "astro:assets";
---
<div class="rounded-3xl bg-navbar-color w-64 flex flex-col">
<div class="w-full">
<Image src={image} alt="Theme" class="aspect-[16/9] rounded-t-3xl" />
</div>
<div class="h-2/6 text-center content-center p-3 font-semibold">
{title}
</div>
</div>

View file

@ -1,41 +0,0 @@
<script lang="ts">
import { type Position, type Props, type ToastType } from "@utils/toast.ts";
import toast from "svelte-french-toast";
export let toastProp: Props;
function handleToast(toastProp: Props) {
switch (toastProp.toastType) {
case "success":
toast.success(toastProp.text, {
style: "background: var(--navbar-color); color: var(--input-text-color);",
icon: toastProp.emoji,
position: toastProp.position ?? "bottom-right",
duration: toastProp.duration
});
break;
case "error":
toast.error(toastProp.text, {
style: "background: var(--navbar-color); color: var(--input-text-color);",
icon: toastProp.emoji,
position: toastProp.position ?? "bottom-right",
duration: toastProp.duration
});
break;
case "promise":
throw new Error("Due to the way astro renders promise toasts are not available (ish)");
break;
case "multiline":
toast(toastProp.text, {
style: "background: var(--navbar-color); color: var(--input-text-color);",
icon: toastProp.emoji,
position: toastProp.position ?? "bottom-right",
duration: toastProp.duration
});
break;
default:
throw new Error("Something isn't right...");
break;
}
}
</script>
<!-- A hacky way to get this to be called. Just click this button (preferably via an EVENT) (see ../../utils/toast.ts) -->
<button id={toastProp.id} class:invisible={'invisible'} class:hidden={'hidden'} class={toastProp.class} on:click={() => {return handleToast(toastProp)}}>Auto clicked for toast notifs</button>

View file

@ -1,8 +0,0 @@
<script lang="ts">
import { Toaster } from "svelte-french-toast";
</script>
<div class="hidden" id="toastwrapper">
<Toaster />
<slot />
</div>

3
src/env.d.ts vendored
View file

@ -1,3 +0,0 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />
/// <reference types="@titaniumnetwork-dev/ultraviolet/client" />

View file

@ -1,12 +0,0 @@
{
"header.home": "Home",
"header.games": "Games",
"header.settings": "Settings",
"header.morelinks": "Want more links?",
"header.catalog": "Nebula Catalog",
"home.placeholder": "Search the web freely",
"settings.settings": "Settings",
"settings.appearance": "Appearance",
"settings.proxy": "Proxy",
"settings.tab": "Tab"
}

View file

@ -1,12 +0,0 @@
{
"header.home": "ホーム",
"header.games": "ゲーム",
"header.settings": "設定",
"header.morelinks": "リンク一覧",
"header.catalog": "Nebula Catalog",
"home.placeholder": "検索欄",
"settings.settings": "Settings",
"settings.appearance": "Appearance",
"settings.proxy": "Proxy",
"settings.tab": "Tab"
}

View file

@ -1,9 +0,0 @@
import en_US from "./en_US.json";
import jp from "./jp.json";
export const defaultLang = "en_US";
export const ui = {
en_US,
jp
};

View file

@ -1,15 +0,0 @@
import { defaultLang, ui } from "./ui";
export const STATIC_PATHS = [{ params: { lang: "en_US" } }, { params: { lang: "jp" } }];
export function getLangFromUrl(url: URL) {
const [, lang] = url.pathname.split("/");
if (lang in ui) return lang as keyof typeof ui;
return defaultLang;
}
export function useTranslations(lang: keyof typeof ui) {
return function t(key: keyof (typeof ui)[typeof defaultLang]) {
return ui[lang][key] || ui[defaultLang][key];
};
}

View file

@ -1,159 +0,0 @@
---
import { ViewTransitions } from "astro:transitions";
import Header from "@components/Header.astro";
import MobileNavigation from "@components/MobileNavigation.astro";
import SettingsLoader from "@components/settings/Loader.astro";
interface Props {
title: string;
noHeader?: string;
}
const { title, noHeader } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<SettingsLoader transition:persist />
<meta charset="UTF-8" />
<meta name="description" content="Astro description" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" id="favicon" />
<link
rel="stylesheet"
href="/nebula.css"
id="stylesheet"
transition:persist
/>
<link
rel="preload"
href="https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500;1,700;1,900&display=swap"
as="style"
crossorigin="anonymous"
/>
<link
rel="preload"
href="https://fonts.gstatic.com/s/roboto/v32/KFOmCnqEu92Fr1Mu4mxK.woff2"
as="font"
type="font/woff2"
crossorigin="anonymous"
/>
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<ViewTransitions />
</head>
<body class="h-full bg-primary">
{!noHeader && <Header />}
<div class="h-full z-10 w-full fixed">
<slot />
</div>
<div
id="mobileNavMenu"
class="w-full fixed inset-0 h-[calc(100%-4rem)] z-20"
transition:persist
>
<MobileNavigation />
</div>
<video
autoplay
muted
loop
id="nebulaVideo"
class="absolute z-0 h-[calc(100%-4rem)] w-full object-cover"
transition:persist
>
<source type="video/mp4" id="videosource" transition:persist />
</video>
<img
src=""
class="absolute z-0 h-[calc(100%-4rem)] w-full object-cover hidden"
id="nebulaImage"
transition:persist
/>
<script>
import { isMobileNavOpen } from "../store.js";
const mobileNavMenu = document.getElementById("mobileNavMenu");
// Listen to changes in the store, and show/hide the mobile navigation accordingly
isMobileNavOpen.subscribe((open) => {
if (open) {
if (mobileNavMenu) {
mobileNavMenu.style.display = "block";
mobileNavMenu.style.transform = "translateX(0%)";
}
} else {
if (mobileNavMenu) {
mobileNavMenu.style.transform = "translateX(100%)";
}
}
});
</script>
<style>
#mobileNavMenu {
-webkit-transition-duration: 600ms;
transition-duration: 600ms;
transform: translateX(100%);
}
</style>
<style is:global>
.roboto {
font-family: var(--font-family), Roboto, sans-serif;
font-weight: 200;
font-style: normal;
}
/* Custom scrollbar because the default ones look like ASS */
::-webkit-scrollbar {
width: 0.5rem;
height: 0.5rem;
}
::-webkit-scrollbar-track {
background: var(--navbar-color);
}
::-webkit-scrollbar-thumb {
background: var(--navbar-text-color);
}
::-webkit-scrollbar-thumb:hover {
background: var(--navbar-link-color);
}
::-moz-scrollbar {
width: 0.5rem;
height: 0.5rem;
}
::-moz-scrollbar-track {
background: var(--navbar-color);
}
::-moz-scrollbar-thumb {
background: var(--navbar-text-color);
}
::-moz-scrollbar-thumb:hover {
background: var(--navbar-link-color);
}
::-ms-scrollbar {
width: 0.5rem;
height: 0.5rem;
}
::-ms-scrollbar-track {
background: var(--navbar-color);
}
::-ms-scrollbar-thumb {
background: var(--navbar-text-color);
}
::-ms-scrollbar-thumb:hover {
background: var(--navbar-link-color);
}
::-o-scrollbar {
width: 0.5rem;
height: 0.5rem;
}
::-o-scrollbar-track {
background: var(--navbar-color);
}
::-o-scrollbar-thumb {
background: var(--navbar-text-color);
}
::-o-scrollbar-thumb:hover {
background: var(--navbar-link-color);
}
</style>
</body>
</html>

View file

@ -1,35 +0,0 @@
---
import { getLangFromUrl, useTranslations } from "../i18n/utils";
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
const { title } = Astro.props;
import SidebarButton from "@components/SidebarButton.astro";
import { Icon } from "astro-icon/components";
---
<div class="flex flex-row font-roboto">
<div class="text-text-color mt-16 fixed inset-0 h-[calc(100%-4rem)] z-0 bg-primary flex-col flex md:flex-row">
<div class="items-center md:p-3 sm:p-3 flex flex-row border-border-color md:gap-10 xl:gap-10 max-sm:gap-5 sm:gap-5 md:border-r-2 max:md:w-2/12 md:flex-col md:bg-navbar-color md:gap-0 overflow-x-auto md:overflow-x-hidden overflow-y-hidden max-md:ml-1 max-md:mr-1 max-md:max-h-24 max-md:min-h-24 max-md:justify-left max-md:scroll-ml-3 max-md:scroll-mr-3 max-md:snap-x max-md:snap-mandatory">
<SidebarButton title={t("settings.appearance")} route={`/${lang}/settings/appearance`}>
<Icon name="ph:palette" class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6" />
</SidebarButton>
<SidebarButton title={t("settings.proxy")} route={`/${lang}/settings/pr`}>
<Icon name="ph:globe-hemisphere-east-fill" class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6" />
</SidebarButton>
<SidebarButton title={t("settings.tab")} route={`/${lang}/settings/tab/`}>
<Icon name="ph:laptop-fill" class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6" />
</SidebarButton>
<SidebarButton title="Misc" route={`/${lang}/settings/misc`}>
<Icon name="ph:gear" class="h-6 w-6 text-text-color transistion duration-500 group-hover:text-text-hover-color md:h-6 md:w=6" />
</SidebarButton>
<SidebarButton title="Credits" route={`/${lang}/settings/credits`}>
<Icon name="ph:user" class="h-6 w-6 text-text-color transistion duration-500 group-hover:text-text-hover-color md:h-6 md:w=6" />
</SidebarButton>
</div>
<div class="p-8 md:pb-2 flex-grow overflow-y-auto">
<p class="text-5xl font-semibold mb-5">{t("settings.settings")}</p>
<p class="text-2xl">{title}</p>
<slot />
</div>
</div>
</div>

View file

@ -1,12 +0,0 @@
---
const { title, subtitle } = Astro.props;
---
<div class="mt-5 font-roboto">
<hr />
<div class="mt-3">
<div class="text-2xl font-semibold">{title}</div>
<div class="text-lg mb-5">{subtitle}</div>
<slot />
</div>
</div>

View file

@ -1,54 +0,0 @@
---
import CatalogCard from "@components/catalog/CatalogCard.svelte";
import Layout from "@layouts/Layout.astro";
import { getLangFromUrl, useTranslations } from "../../../i18n/utils";
import Pagnation from "./pagnation.astro";
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
const { page } = Astro.params;
const response = await fetch(new URL("/api/catalog-assets/", Astro.url));
//console.log(new URL("/api/catalog-assets/", Astro.url));
const assetsJson = await response.json();
const nextPage = parseInt(page!) + 1;
const previousPage = parseInt(page!) - 1;
const lastPage = assetsJson.pages;
---
<Layout title="Catalog">
<div class="flex mt-16 w-full fixed inset-0 h-[calc(100%-4rem)] z-0 bg-primary flex-col items-center overflow-auto font-roboto">
<div class="w-full flex flex-col gap-4 jusitfy-center items-center text-text-color mt-9">
<h1 class="text-5xl font-bold"> Nebula Catalog </h1>
<p class="text-xl"> The Nebula Catalog is a place for you to find user-created themes and plugins. </p>
</div>
<CatalogCard client:only="svelte" page={page} lang={lang} />
<div class="flex flex-row pb-8 gap-4 font-roboto">
{/* The first page. If the user is on this page, or the one after it, don't show it. */}
{parseInt(page!) > 2 && (
<a href={`/${lang}/catalog/${1}`} class="w-8 h-8 bg-navbar-color items-center text-center content-center text-text-color rounded-md"> 1 </a>
)
}
{previousPage > 0 && (
<a href={`/${lang}/catalog/${previousPage}`} class="w-8 h-8 bg-navbar-color items-center text-center content-center text-text-color rounded-md">{previousPage}</a>
)
}
{/* The greyed out page the user is currently on */}
<a class="w-8 h-8 bg-lighter items-center text-center content-center text-text-color rounded-md">{page}</a>
{nextPage < lastPage && (
<a href={`/${lang}/catalog/${nextPage}`} class="w-8 h-8 bg-navbar-color items-center text-center content-center text-text-color rounded-md">{nextPage}</a>
)
}
{/* Pagnation input */}
<Pagnation />
{/* The last page. If the user is on this page, don't show it. */}
{page != lastPage && (
<a href={`/${lang}/catalog/${assetsJson.pages}`} class="w-8 h-8 bg-navbar-color items-center text-center content-center text-text-color rounded-md">
{assetsJson.pages}
</a>
)
}
</div>
</div>
</Layout>

View file

@ -1,88 +0,0 @@
---
const { packageName } = Astro.params;
import Layout from "@layouts/Layout.astro";
const response = await fetch(new URL("/api/packages/" + packageName, Astro.url));
const assetsJson = await response.json();
---
<Layout title={`Package: ${packageName}`}>
<div class="flex flex-wrap mt-16 w-full fixed inset-0 h-full md:h-[calc(100%-4rem)] z-0 bg-primary flex-col items-center content-center justify-center sm:pt-64 lg:pb-64 font-roboto max-md:p-4">
{assetsJson.error && <h1 class="text-text-color text-3xl font-bold"> Unexpected error. Is the name right? </h1>}
{!assetsJson.error &&
<div class="flex flex-col md:flex-row items-center text-text-color bg-navbar-color rounded-2xl">
{assetsJson.background_video &&
<video src={`/packages/${packageName}/${assetsJson.background_video}`} controls class="w-[44rem] h-[25rem] object-cover rounded-xl">
Your browser does not support the video tag.
</video>
}
{assetsJson.backgroundImage &&
<div style={{backgroundImage: `url(/packages/${packageName}/${assetsJson.backgroundImage})`}} class="w-[44rem] h-[25rem] bg-cover bg-center rounded-xl"/>
}
{!assetsJson.background_video && !assetsJson.backgroundImage && <img loading="lazy" src={`/packages/${packageName}/${assetsJson.image}`} alt={assetsJson.title} class="w-[44rem] h-[25rem] object-cover rounded-xl"/>}
<div class="flex flex-col ml-7 p-16">
<p class="text-xl">{assetsJson.type === "plugin-page" || assetsJson.type === "plugin-sw" ? "plugin": assetsJson.type}</p>
<h1 class="text-4xl font-roboto font-semibold">{assetsJson.title}</h1>
<p class="text-xl"> By: <strong>{assetsJson.author}</strong></p>
<p class="text-xl">{assetsJson.description}</p>
<button class="bg-primary text-text-color border border-transparent rounded-lg px-6 py-3 hover:bg-navbar-color transition-colors duration-300 mt-9" id="install">
Install
</button>
<button class="hidden bg-primary text-text-color border border-transparent rounded-lg px-6 py-3 hover:bg-navbar-color transition-colors duration-300 mt-9" id="uninstall">
Uninstall
</button>
</div>
</div>
}
</div>
<variable-define data-assets={JSON.stringify(assetsJson)} data-name={packageName} data-type={assetsJson.type} />
</Layout>
<script>
import { pageLoad } from "@utils/events";
import { settings, Settings, type PackageType } from "@utils/settings/index";
import type { SWPagePlugin } from "@utils/settings/types";
let packageName: string;
let assetsJson: any;
let packageType: string;
//some weird trickery to get the variables.
class VariableDefiner extends HTMLElement {
constructor() {
super();
assetsJson = this.dataset.assets;
packageName = this.dataset.name as string;
packageType = this.dataset.type as PackageType;
}
}
customElements.define('variable-define', VariableDefiner);
const fn = () => {
const cssItems = JSON.parse(localStorage.getItem(Settings.AppearanceSettings.themes) as string) || [];
const cssItemExists = cssItems.indexOf(packageName) !== -1;
const pluginItems = JSON.parse(localStorage.getItem(Settings.PluginSettings.plugins) as string) || [];
//@ts-ignore
const pluginItemExists = pluginItems.find(({ name, remove }) => name === packageName && remove !== true);
const installButton = document.getElementById("install") as HTMLButtonElement;
const uninstallButton = document.getElementById("uninstall") as HTMLButtonElement;
const payload = assetsJson ? JSON.parse(assetsJson) : undefined;
if (cssItemExists || pluginItemExists) {
uninstallButton.classList.remove("hidden");
installButton.classList.add("hidden");
}
installButton.addEventListener("click", () => {
console.log(payload);
settings.marketPlaceSettings.install(
payload.type === "theme" ? {theme: {payload: payload.payload, video: payload.background_video, bgImage: payload.background_image}} :
payload.type === "plugin-page" ? {plugin: {name: packageName, src: payload.payload, type: "page" }} : {plugin: { name: packageName, src: payload.payload, type: "serviceWorker" } },
packageName, payload.payload
).then(() => {
installButton.classList.add("hidden");
uninstallButton.classList.remove("hidden");
})
});
uninstallButton.addEventListener("click", () => {
settings.marketPlaceSettings.uninstall(packageType as PackageType, packageName).then(() => {
uninstallButton.classList.add("hidden")
installButton.classList.remove("hidden");
});
});
}
pageLoad(fn);
</script>

View file

@ -1,15 +0,0 @@
<input
class="w-10 h-8 bg-navbar-color items-center text-center content-center text-text-color rounded-md [appearance:textfield] [&::-webkit-outer-spin-button]:appearance-none [&::-webkit-inner-spin-button]:appearance-none"
type="number"
id="pagnation_input"
placeholder="..."
/>
<script is:inline data-astro-rerun>
document
.getElementById("pagnation_input")
.addEventListener("keyup", function (event) {
if (event.key === "Enter") {
window.location.href = document.getElementById("pagnation_input").value;
}
});
</script>

View file

@ -1,25 +0,0 @@
---
import Layout from "@layouts/Layout.astro";
export function getStaticPaths() {
const STATIC_PATHS = [{ params: { lang: "en_US" } }, { params: { lang: "jp" } }];
return STATIC_PATHS;
}
export const prerender = true;
---
<Layout title="Chango Games">
<iframe id="chango" class="w-full h-full"></iframe>
</Layout>
<script>
import { initSw, loadProxyScripts, setTransport } from "@utils/registerSW.ts"; //../../utils/registerSW.ts
import { Settings } from "@utils/settings/index";
import { pageLoad } from "@utils/events";
pageLoad(async () => {
const iframe = document.getElementById("chango") as HTMLIFrameElement;
const conn = await loadProxyScripts();
await setTransport(conn, localStorage.getItem(Settings.ProxySettings.transport) as string);
await initSw().then(() => {
if (iframe) return iframe.src = __uv$config!.prefix + __uv$config.encodeUrl!("https://radon.games");
});
});
</script>

View file

@ -1,210 +0,0 @@
---
import Logo from "@components/Logo.astro";
import Layout from "@layouts/Layout.astro";
import { getLangFromUrl, useTranslations } from "../../i18n/utils";
export function getStaticPaths() {
const STATIC_PATHS = [
{ params: { lang: "en_US" } },
{ params: { lang: "jp" } },
];
return STATIC_PATHS;
}
export const prerender = true;
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
import { VERSION } from "astro:env/client";
---
<Layout title="Nebula">
<div
class="flex flex-wrap mt-16 justify-center content-center w-full bg-primary fixed inset-0 h-[calc(100%-4rem)] z-0 flex-col items-center"
>
<div
class="w-full flex flex-col justify-center items-center content-center h-2/4"
>
<div class="flex flex-row items-center mb-8">
<div class="h-32 w-32 fill-navbar-text-color">
<Logo />
</div>
<h1
class="font-roboto whitespace-nowrap text-navbar-text-color sm:visible text-5xl sm:text-7xl roboto"
>
nebula.
</h1>
</div>
<input
id="nebula-input"
class="transition-all duration-300 font-roboto h-14 rounded-t-2xl w-10/12 rounded-b-2xl border border-input-border-color bg-input p-2 text-center text-xl text-input-text placeholder:text-input-text roboto focus:outline-none md:w-3/12"
placeholder={t("home.placeholder")}
/>
<div
id="omnibox"
class="hidden p-1 transition-all duration-300 flex flex-col w-10/12 md:w-3/12 h-full flex-grow bg-input text-center items-center rounded-b-2xl border-input-border-color border-b border-r border-l"
>
</div>
</div>
<iframe
id="neb-iframe"
class="hidden z-100 w-full h-full absolute top-0 bottom-0 bg-primary"
src="/loading"></iframe>
<div
id="version"
class="flex flex-row w-full absolute bottom-4 pr-4 pl-4 text-text-color h-6 justify-between font-roboto"
>
<p>Version: {VERSION}</p>
<p>&copy; Nebula Services 2024</p>
</div>
</div>
</Layout>
<script>
import { initSw, setTransport, loadProxyScripts } from "@utils/registerSW.ts"; //../../utils/registerSW.ts
import { pageLoad } from "@utils/events";
import { SupportedSites } from "@utils/siteSupport";
import {
SearchEngines,
Settings,
cloak,
settings,
} from "@utils/settings/index";
import { search } from "@utils/search.ts"; //../../utils/search.ts
import { client as libcurlClient } from "@utils/libcurl";
type Suggestion = {
phrase: string;
};
async function proxy(term: string) {
const searchEngine = localStorage.getItem(
Settings.ProxySettings.searchEngine
);
const openIn = localStorage.getItem(Settings.ProxySettings.openIn);
let proxyUrl: any =
__uv$config!.prefix +
__uv$config.encodeUrl!(
search(
term,
searchEngine ? SearchEngines[searchEngine] : SearchEngines.ddg
)
);
if (openIn === "a:b" || openIn === "blob") {
return cloak(
openIn as string,
"https://google.com",
`${window.location.origin}${proxyUrl}`
);
} else if (openIn === "direct") {
return (window.location.href = proxyUrl as string);
} else {
return proxyUrl;
}
}
async function uv(iframe: HTMLIFrameElement, term: string) {
const conn = await loadProxyScripts();
await setTransport(
conn,
localStorage.getItem(Settings.ProxySettings.transport) as string
);
const sw = await initSw();
await settings.marketPlaceSettings.handlePlugins(sw);
iframe.classList.remove("hidden");
const url = await proxy(term);
if (url) {
iframe.src = url;
}
}
//we need to rerun this on every page load
pageLoad(async () => {
const input = document.getElementById("nebula-input") as HTMLInputElement;
const iframe = document.getElementById("neb-iframe") as HTMLIFrameElement;
const omnibox = document.getElementById("omnibox") as HTMLDivElement;
const copyright = document.getElementById("version") as HTMLDivElement;
input?.addEventListener("keypress", async function (event: any) {
if (event.key === "Enter") {
copyright.classList.add("hidden");
if (
localStorage.getItem(Settings.ProxySettings.proxy) === "automatic"
) {
const key = SupportedSites[input?.value];
switch (key) {
case "uv":
uv(iframe, input?.value);
break;
default:
uv(iframe, input?.value);
break;
}
} else if (
localStorage.getItem(Settings.ProxySettings.proxy) === "uv"
) {
uv(iframe, input?.value);
}
}
});
input?.addEventListener("input", async function () {
omnibox.innerHTML = "";
const value = input?.value;
input.classList.remove("rounded-b-2xl");
omnibox.classList.remove("hidden");
if (value.length === 0) {
input.classList.add("rounded-b-2xl");
omnibox.classList.add("hidden");
}
if (value.length >= 3) {
await libcurlClient.initLibcurl();
const data = (await libcurlClient.fetchFromLibcurl(
`https://api.duckduckgo.com/ac?q=${encodeURIComponent(value)}&format=json`,
"json"
)) as [];
const filteredData = data.slice(0, 8); //Trim to only about 8 results. Any more and our omnibox dies
if (filteredData) {
omnibox.innerHTML = "";
filteredData.map((results: Suggestion) => {
let span = document.createElement("span");
let pTag = document.createElement("p");
span.classList.add(
"cursor-pointer",
"font-roboto",
"border-b",
"border-input-border-color",
"last:rounded-b-xl",
"last:border-none",
"text-ellipsis",
"whitespace-nowrap",
"w-full",
"text-input-text",
"h-9",
"text-xl",
"text-align-center",
"overflow-hidden",
"flex",
"items-center",
"justify-center",
"hover:bg-lighter",
"active:bg-primary"
);
span.addEventListener("click", function () {
//When the box is clicked, we want to fill the omnibox and hit enter. This allows us to re-use the existing event listener on the input.
const event = new KeyboardEvent("keypress", {
key: "Enter",
code: "Enter",
which: 13,
keyCode: 13,
});
input.value = results.phrase;
input.dispatchEvent(event);
});
pTag.classList.add(
"cursor-pointer",
"text-ellipsis",
"text-center",
"w-full",
"overflow-hidden",
"whitespace-nowrap"
);
pTag.innerText = results.phrase;
span.appendChild(pTag);
omnibox.appendChild(span);
});
}
}
});
});
</script>

View file

@ -1,41 +0,0 @@
---
import InstalledThemes from "@components/catalog/InstalledThemes.svelte";
import Layout from "@layouts/Layout.astro";
import SettingsLayout from "@layouts/SettingsLayout.astro";
import SettingsSection from "@layouts/SettingsSection.astro";
import { Icon } from "astro-icon/components";
import { getLangFromUrl, useTranslations } from "../../../i18n/utils";
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
export function getStaticPaths() {
const STATIC_PATHS = [{ params: { lang: "en_US" } }, { params: { lang: "jp" } }];
return STATIC_PATHS;
}
export const prerender = true;
import { MARKETPLACE_ENABLED } from "astro:env/client";
---
<Layout title="Settings">
<SettingsLayout title={t("settings.appearance")}>
<SettingsSection
title="Theme"
subtitle="Choose a theme so your eyes don't hate us.">
<div class="flex flex-row flex-wrap gap-4 items-center font-roboto">
<div class="justify-center flex flex-row gap-6 flex-wrap md:justify-normal">
<InstalledThemes client:only="svelte" />
{MARKETPLACE_ENABLED &&
<a href={`/${lang}/catalog/1`} class="rounded-3xl bg-navbar-color w-64 flex flex-col">
<div class="w-full items-center justify-center flex aspect-[16/9]">
<Icon name="ph:plus-bold" class="h-16 w-16" />
</div>
<div class="h-2/6 text-center content-center p-3 font-semibold">
Get more themes in the <strong>Nebula Catalog!</strong>
</div>
</a>
}
</div>
<div class="text-3xl font-roboto font-bold text-text-color"></div>
</div>
</SettingsSection>
</SettingsLayout>
</Layout>

View file

@ -1,39 +0,0 @@
---
import InstalledThemes from "@components/catalog/InstalledThemes.svelte";
import Layout from "@layouts/Layout.astro";
import SettingsLayout from "@layouts/SettingsLayout.astro";
import SettingsSection from "@layouts/SettingsSection.astro";
import { Icon } from "astro-icon/components";
import { getLangFromUrl, useTranslations } from "../../../i18n/utils";
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
export function getStaticPaths() {
const STATIC_PATHS = [{ params: { lang: "en_US" } }, { params: { lang: "jp" } }];
return STATIC_PATHS;
}
export const prerender = true;
import CreditsCard from "@components/settings/CreditsCard.astro";
---
<Layout title="Settings">
<SettingsLayout title="Credits">
<SettingsSection title="Site Developers & Maintainers" subtitle="Thank you to all of the devs and maintainers!">
<div class="flex flex-row flex-wrap gap-4 items-center font-roboto">
<div class="justify-center flex flex-row gap-6 flex-wrap md:justify-normal">
<CreditsCard image="/src/assets/credits/rift.jpeg" name="Rifting" link="https://github.com/rifting" />
<CreditsCard image="/src/assets/credits/motortruck1221.png" name="MotorTruck1221" link="https://motortruck1221.com" />
</div>
</div>
</SettingsSection>
<SettingsSection title="Tools & Projects" subtitle="Nebula wouldn't be possible without these tools & projects">
<div class="flex flex-row flex-wrap gap-6 items-center font-roboto">
<CreditsCard image="/src/assets/credits/uv.png" name="Ultraviolet" link="https://github.com/titaniumnetwork-dev/ultraviolet" />
<CreditsCard image="/src/assets/credits/rammerhead.png" name="Rammerhead" link="https://github.com/binary-person/rammerhead" />
<CreditsCard image="/src/assets/credits/libcurl.png" name="Libcurl.js" link="https://github.com/ading2210/libcurl.js" />
<CreditsCard image="/src/assets/credits/mercury.png" name="Epoxy TLS" link="https://github.com/mercuryworkshop/epoxy-tls" />
</div>
</SettingsSection>
<SettingsSection title="Translators" subtitle="Translations are made possible by these lovely individuals">
</SettingsSection>
</SettingsLayout>
</Layout>

View file

@ -1,105 +0,0 @@
---
import SettingsCard from "@components/settings/SettingsCard.astro";
import Toast from "@components/toasts/Toast.svelte";
import ToastWrapper from "@components/toasts/ToastWrapper.svelte";
import Layout from "@layouts/Layout.astro";
import SettingsLayout from "@layouts/SettingsLayout.astro";
import SettingsSection from "@layouts/SettingsSection.astro";
import { getLangFromUrl, useTranslations } from "../../../i18n/utils";
const origin = Astro.url.origin;
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
export function getStaticPaths() {
const STATIC_PATHS = [{ params: { lang: "en_US" } }, { params: { lang: "jp" } }];
return STATIC_PATHS;
}
export const prerender = true;
---
<Layout title="Settings">
<SettingsLayout title="Miscellaneous">
<SettingsSection title="Misc" subtitle="All of our miscellaneous settings">
<div class="w-full h-full flex flex-col items-center justify-center flex-wrap md:flex-row md:items-start md:justify-start gap-4">
<SettingsCard
title="Language"
description="Choose your language"
input={{input:false}}
button={{name: 'Change Language', id: 'setlang'}}
select={{
select: true,
name: 'lang',
options: [
{name: 'English', value: 'en_US', disabled: false},
{name: 'Japanese', value: 'jp', disabled: false},
]
}}
/>
<SettingsCard
title="Proxy Catalog"
description="Whether or not to proxy the Catalog"
input={{input: false}}
button={{name: 'Set', id: 'setmp' }}
select={{
select: true,
name: 'mp',
options: [
{name: 'False', value: 'false', disabled: false},
{name: 'True', value: 'true', disabled: false}
]
}}
/>
<SettingsCard
title="Catalog URL"
description="Change which marketplace your using"
input={{
input: true,
required: true,
placeholder: `${origin}/api/catalog-assets`
}}
button={{name: 'Change', id: 'setcataloghostname' }}
select={{select: false}}
/>
</SettingsSection>
</SettingsLayout>
<ToastWrapper client:load>
<Toast toastProp={{
toastType: 'success',
text: 'Catalog is now proxied',
class: 'mpMessage'
}}
client:load />
<Toast toastProp={{
toastType: 'success',
text: 'Catalog URL set!',
class: 'cataloghostnameSuccess'
}}
client:load />
<Toast toastProp={{
toastType: 'error',
text: "Catalog doesn't exist!",
class: 'cataloghosnameError'
}}
client:load/>
</ToastWrapper>
<script>
import { toast } from "@utils/toast.ts";
import { settings, Settings as SettingsEnum } from "@utils/settings/index";
import { pageLoad } from "@utils/events";
import { navigate } from "astro:transitions/client";
function setup() {
const lang = JSON.parse(localStorage.getItem('selectedLanguage') as string) || JSON.parse('{"value": "en_US"}');
const languageVal = document.getElementById('lang') as HTMLSelectElement;
languageVal!.value = lang.value;
}
pageLoad(() => {
setup();
const languageChange = document.getElementById('setlang') as HTMLButtonElement;
const languageVal = document.getElementById('lang') as HTMLSelectElement;
languageChange?.addEventListener('click', () => {
const language = {value: languageVal.value}
localStorage.setItem("selectedLanguage", JSON.stringify(language));
navigate(`${window.location.origin}/${languageVal.value}/settings/misc`)
});
})
</script>

View file

@ -1,261 +0,0 @@
---
import InstalledPlugins from "@components/catalog/InstalledPlugins.svelte";
import SettingsCard from "@components/settings/SettingsCard.astro";
import Toast from "@components/toasts/Toast.svelte";
import ToastWrapper from "@components/toasts/ToastWrapper.svelte";
import Layout from "@layouts/Layout.astro";
import SettingsLayout from "@layouts/SettingsLayout.astro";
import SettingsSection from "@layouts/SettingsSection.astro";
import { Icon } from "astro-icon/components";
import { getLangFromUrl, useTranslations } from "../../../i18n/utils";
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
export function getStaticPaths() {
const STATIC_PATHS = [
{ params: { lang: "en_US" } },
{ params: { lang: "jp" } },
];
return STATIC_PATHS;
}
export const prerender = true;
import { MARKETPLACE_ENABLED } from "astro:env/client";
---
<Layout title="Settings">
<SettingsLayout title={t("settings.proxy")}>
<SettingsSection
title="Proxy"
subtitle="A wide variety of settings for the proxy/rewriter itself."
>
<div
class="w-full h-full flex flex-col items-center justify-center flex-wrap md:flex-row md:items-start md:justify-start gap-4"
>
<SettingsCard
title="Proxy"
description="Choose the proxy/rewriter that fits your needs"
input={{ input: false }}
button={{ name: "Change", id: "setproxy" }}
select={{
select: true,
name: "proxy",
options: [
{ name: "Automatic", value: "automatic", disabled: false },
{ name: "Ultraviolet", value: "uv", disabled: false },
{ name: "Scramjet (COMING SOON)", value: "sj", disabled: true },
],
}}
/>
<SettingsCard
title="Open in"
description="Choose how to open your sites"
input={{ input: false }}
button={{ name: "Set", id: "setopenin" }}
select={{
select: true,
name: "openin",
options: [
{ name: "Embed", value: "embed", disabled: false },
{ name: "Direct", value: "direct", disabled: false },
{ name: "About:Blank", value: "a:b", disabled: false },
{ name: "Blob", value: "blob", disabled: false },
],
}}
/>
<SettingsCard
title="Search Engine"
description="Choose your search engine"
input={{ input: false }}
button={{ name: "Set Search Engine", id: "setsearchengine" }}
select={{
select: true,
name: "searchengine",
options: [
{ name: "DuckDuckGo", value: "ddg", disabled: false },
{ name: "Google", value: "google", disabled: false },
{ name: "Bing", value: "bing", disabled: false },
],
}}
/>
<SettingsCard
title="Wisp Server"
description="Choose the wisp server you feel is the fastest"
input={{ input: false }}
button={{ name: "Select", id: "setwispurl" }}
select={{
select: true,
name: "wispurl",
options: [
{ name: "Default", value: "default", disabled: false },
{ name: "Ruby Network (US)", value: "ruby", disabled: false },
],
}}
/>
<SettingsCard
title="Transport"
description="Select the transport to use"
input={{ input: false }}
button={{ name: "Set transport", id: "settransport" }}
select={{
select: true,
name: "transport",
options: [
{ name: "Libcurl", value: "libcurl", disabled: false },
{ name: "Epoxy", value: "epoxy", disabled: false },
],
}}
/>
</div>
</SettingsSection>
{
MARKETPLACE_ENABLED && (
<SettingsSection
title="Plugins"
subtitle="Plugins allow you to modify the way the proxy works (UV only, plugins are auto applied)"
>
<div class="flex flex-row gap-6 justify-center md:justify-normal">
<InstalledPlugins client:only="svelte" />
<a
href={`/${lang}/catalog/1`}
class="rounded-3xl bg-navbar-color w-64 flex flex-col"
>
<div class="w-full items-center justify-center flex aspect-[16/9]">
<Icon name="ph:plus-bold" class="h-16 w-16" />
</div>
<div class="h-2/6 text-center content-center p-3 font-semibold">
Get more plugins in the <strong>Nebula Catalog!</strong>
</div>
</a>
</div>
</SettingsSection>
)
}
</SettingsLayout>
<ToastWrapper client:load>
<Toast
toastProp={{
toastType: "success",
text: "Successfully changed proxy!",
class: "proxyMessage",
}}
client:load
/>
<Toast
toastProp={{
toastType: "success",
text: "Saved selection!",
class: "openInMessage",
}}
client:load
/>
<Toast
toastProp={{
toastType: "success",
text: "Saved Search Engine Selection!",
class: "searchEngineMessage",
}}
client:load
/>
<Toast
toastProp={{
toastType: "success",
text: "Wisp server selected!",
class: "wispUrlMessage",
}}
client:load
/>
<Toast
toastProp={{
toastType: "success",
text: "Transport set!",
class: "transportMessage",
}}
client:load
/>
</ToastWrapper>
</Layout>
<script>
import { toast } from "@utils/toast.ts";
import { settings, Settings as SettingsEnum } from "@utils/settings/index";
import { pageLoad } from "@utils/events";
function setup(
proxySelectVal: HTMLSelectElement,
openInVal: HTMLSelectElement,
searchEngineVal: HTMLSelectElement,
wispServerVal: HTMLSelectElement,
transportVal: HTMLSelectElement
) {
proxySelectVal.value = localStorage.getItem(
SettingsEnum.ProxySettings.proxy
) as string;
openInVal.value = localStorage.getItem(
SettingsEnum.ProxySettings.openIn
) as string;
searchEngineVal.value = localStorage.getItem(
SettingsEnum.ProxySettings.searchEngine
) as string;
wispServerVal.value = localStorage.getItem(
SettingsEnum.ProxySettings.wispServerURL
) as string;
transportVal.value = localStorage.getItem(
SettingsEnum.ProxySettings.transport
) as string;
}
pageLoad(() => {
const proxyButton = document.getElementById(
"setproxy"
) as HTMLButtonElement;
const proxySelectVal = document.getElementById(
"proxy"
) as HTMLSelectElement;
const openInButton = document.getElementById(
"setopenin"
) as HTMLButtonElement;
const openInVal = document.getElementById("openin") as HTMLSelectElement;
const searchEngineButton = document.getElementById(
"setsearchengine"
) as HTMLButtonElement;
const searchEngineVal = document.getElementById(
"searchengine"
) as HTMLSelectElement;
const wispServerButton = document.getElementById(
"setwispurl"
) as HTMLButtonElement;
const wispServerVal = document.getElementById(
"wispurl"
) as HTMLSelectElement;
const transportButton = document.getElementById(
"settransport"
) as HTMLButtonElement;
const transportVal = document.getElementById(
"transport"
) as HTMLSelectElement;
setup(
proxySelectVal,
openInVal,
searchEngineVal,
wispServerVal,
transportVal
);
proxyButton.addEventListener("click", () => {
settings.proxySettings.changeProxy(proxySelectVal.value);
toast(".proxyMessage");
});
openInButton.addEventListener("click", () => {
settings.proxySettings.openIn(openInVal.value);
toast(".openInMessage");
});
searchEngineButton.addEventListener("click", () => {
settings.proxySettings.setSearchEngine(searchEngineVal.value);
toast(".searchEngineMessage");
});
wispServerButton.addEventListener("click", () => {
settings.proxySettings.setWispURL(wispServerVal.value);
toast(".wispUrlMessage");
});
transportButton.addEventListener("click", () => {
settings.proxySettings.setTransport(transportVal.value);
toast(".transportMessage");
});
});
</script>

View file

@ -1,81 +0,0 @@
---
import SettingsCard from "@components/settings/SettingsCard.astro";
import Toast from "@components/toasts/Toast.svelte";
import ToastWrapper from "@components/toasts/ToastWrapper.svelte";
import Layout from "@layouts/Layout.astro";
import SettingsLayout from "@layouts/SettingsLayout.astro";
import SettingsSection from "@layouts/SettingsSection.astro";
import { getLangFromUrl, useTranslations } from "../../../i18n/utils";
const lang = getLangFromUrl(Astro.url);
const t = useTranslations(lang);
export function getStaticPaths() {
const STATIC_PATHS = [{ params: { lang: "en_US" } }, { params: { lang: "jp" } }];
return STATIC_PATHS;
}
export const prerender = true;
---
<Layout title="Settings">
<SettingsLayout title={t("settings.tab")}>
<SettingsSection title="Tab" subtitle="Customize and cloak your tab.">
<div class="w-full h-full flex flex-col items-center justify-center md:flex-row md:items-start md:justify-start gap-4">
<SettingsCard
title="Cloaking"
description="Choose how your tab looks"
input={{input: false}}
button={{name: "Cloak!", id: "cloak"}}
select={{ select: true, name: 'cloakselect', options: [
{name: 'Default', value: 'reset', disabled: false},
{name: 'Google', value: 'google', disabled: false },
{name: 'Wikipedia', value: 'wikipedia', disabled: false},
{name: 'Canvas', value: 'canvas', disabled: false},
{name: 'Google Classroom', value: 'classroom', disabled: false},
{name: 'Powerschool', value: 'powerschool', disabled: false}
] }}
/>
<SettingsCard
title="A:B & Blob"
description="Choose to open your tab in about:blank or blob"
input={{input: false}}
button={{name: "Go!", id: "aboutblankbutton"}}
select={{ select: true, name: 'aboutblank', options: [
{name: 'About:Blank', value: 'a:b', disabled: false},
{name: 'Blob', value: 'blob', disabled: false}
] }}
/>
</div>
</SettingsSection>
</SettingsLayout>
{ /* The toast notifications :D */ }
<ToastWrapper client:load>
<Toast toastProp={{toastType: "success", text: "Successfully set cloak!", class: "cloakMessageSuccess"}} client:load />
<Toast toastProp={{toastType: "success", text: "Saved your option for next time! \n Opening in new tab...", class: "abCloakMessage"}} client:load />
</ToastWrapper>
</Layout>
<script>
import { toast } from "@utils/toast.ts"; //A helper function so we don't have to run this logic everytime.
import { settings, Settings as SettingsEnum } from "@utils/settings/index";
import { pageLoad } from "@utils/events";
function setup(cloakValue: HTMLSelectElement, abValue: HTMLSelectElement) {
const cloakLocal = localStorage.getItem(SettingsEnum.TabSettings.tabCloak);
const abCloakLocal = localStorage.getItem(SettingsEnum.TabSettings.abblob);
cloakLocal === "default" ? cloakValue.value = "reset" : cloakValue.value = cloakLocal as string;
abValue.value = abCloakLocal as string || 'a:b';
}
pageLoad(() => {
const cloakButton = document.getElementById("cloak") as HTMLButtonElement;
const cloakValue = document.getElementById("cloakselect") as HTMLSelectElement;
const abButton = document.getElementById("aboutblankbutton") as HTMLButtonElement;
const abValue = document.getElementById("aboutblank") as HTMLSelectElement;
setup(cloakValue, abValue);
cloakButton.addEventListener("click", () => {
settings.tabSettings.cloakTab(cloakValue.value);
toast('.cloakMessageSuccess');
});
abButton.addEventListener("click", () => {
toast('.abCloakMessage');
settings.tabSettings.abCloak(abValue.value);
});
})
</script>

View file

@ -1,48 +0,0 @@
---
import Loading from "@components/Loading.astro";
import Layout from "@layouts/Layout.astro";
---
<Layout title="Loading..." noHeader="true">
<Loading />
</Layout>
<script>
import { pageLoad } from "@utils/events";
pageLoad(() => {
let currentLang = localStorage.getItem("selectedLanguage");
const redirect = (loc: any) => (window.location.href = loc);
if (currentLang) {
try {
let parsed = JSON.parse(currentLang).value;
switch (parsed) {
case "en_US":
redirect("/en_US/");
break;
case "jp":
redirect("/jp/");
break;
default:
redirect("/en_US/");
break;
}
} catch {
localStorage.clear();
window.location.reload();
}
} else {
if (navigator.language.includes("ja")) {
localStorage.setItem("selectedLanguage", JSON.stringify({ value: "jp" }));
redirect("/jp/");
} else {
localStorage.setItem(
"selectedLanguage",
JSON.stringify({ value: "en_US" })
);
redirect("/en_US/");
}
}
});
</script>
<noscript>
JavaScript is required to run this app.
</noscript>

View file

@ -1,27 +0,0 @@
---
import LoadingComponent from "@components/Loading.astro";
import Layout from "@layouts/Layout.astro";
---
<Layout title="Loading page..." noHeader="true">
<LoadingComponent />
</Layout>
<script>
import { pageLoad } from "@utils/events";
import { navigate } from "astro:transitions/client";
function isComingFromIframe() {
try {
return window.self !== window.top;
}
catch (e) {
return true;
}
}
pageLoad(() => {
const isIframe = isComingFromIframe();
if (!isIframe) {
console.log("Assuming request isn't coming from iFrame. Redirecting...");
navigate('/');
}
});
</script>

View file

@ -1,3 +0,0 @@
import { atom } from "nanostores";
export const isMobileNavOpen = atom(false);

View file

@ -1,13 +0,0 @@
function pageLoad(fn: () => void, logging?: boolean) {
document.addEventListener("astro:page-load", () => {
try {
fn();
} catch (err) {
if (logging) {
console.error(err);
}
}
});
}
export { pageLoad };

View file

@ -1,28 +0,0 @@
import { WispServerURLS } from "@utils/settings/index";
//helper for libcurl as we have to use it in multiple locations and we don't want to re-download the WASM every time
//@ts-expect-error No types, expected. See: https://github.com/ading2210/libcurl.js for docs on how to use.
import { libcurl } from "libcurl.js-new/bundled";
let clientExists: boolean = false;
async function initLibcurl() {
if (!clientExists) {
await libcurl.load_wasm();
libcurl.set_websocket(WispServerURLS.default);
console.debug("Libcurl ready?", libcurl.ready);
clientExists = true;
}
}
type fetchType = "json" | "text";
async function fetchFromLibcurl(url: string, type: fetchType): Promise<string | [] | {}> {
const res = await libcurl.fetch(url);
const data = type === "json" ? await res.json() : await res.text();
return data;
}
const client = {
initLibcurl,
fetchFromLibcurl
};
export { client };

View file

@ -1,60 +0,0 @@
import { BareMuxConnection } from "@mercuryworkshop/bare-mux";
import { Settings, WispServerURLS } from "./settings/index";
function loadProxyScripts() {
//wrap everything in a promise to avoid race conditions
return new Promise<BareMuxConnection>((resolve) => {
const conn = new BareMuxConnection("/baremux/worker.js");
if (typeof __uv$config !== "undefined") {
return resolve(conn);
}
const uvBundle = document.createElement("script");
uvBundle.src = "/uv/uv.bundle.js";
uvBundle.defer = true;
document.body.appendChild(uvBundle);
const uvConfig = document.createElement("script");
uvConfig.src = "/uv/uv.config.js";
uvConfig.defer = true;
document.body.appendChild(uvConfig);
const checkScript = setInterval(() => {
if (typeof __uv$config !== "undefined") {
clearInterval(checkScript);
resolve(conn);
}
}, 100);
});
}
function setTransport(conn: BareMuxConnection, transport?: string) {
//wrap in a promise so we don't register sw until a transport is set.
const wispServer = localStorage.getItem(Settings.ProxySettings.wispServerURL);
return new Promise<void>((resolve) => {
switch (transport) {
case "epoxy":
conn.setTransport("/epoxy/index.mjs", [
{ wisp: wispServer ? WispServerURLS[wispServer] : WispServerURLS.default }
]);
break;
case "libcurl":
conn.setTransport("/libcurl/index.mjs", [
{ wisp: wispServer ? WispServerURLS[wispServer] : WispServerURLS.default }
]);
break;
}
resolve();
});
}
function initSw() {
//this is wrapped in a promise to mostly solve the bare-mux v1 problems
return new Promise<ServiceWorkerRegistration>((resolve) => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready.then(async (reg) => {
console.debug("Service worker ready!");
resolve(reg);
});
navigator.serviceWorker.register("/sw.js", { scope: "/" });
}
});
}
export { initSw, setTransport, loadProxyScripts };

View file

@ -1,27 +0,0 @@
function search(input: string, template: string) {
try {
// input is a valid URL:
// eg: https://example.com, https://example.com/test?q=param
return new URL(input).toString();
} catch (err) {
// input was not a valid URL
}
try {
// input is a valid URL when http:// is added to the start:
// eg: example.com, https://example.com/test?q=param
const url = new URL(`http://${input}`);
// only if the hostname has a TLD/subdomain
if (url.hostname.includes(".")) return url.toString();
} catch (err) {
// input was not valid URL
}
// input may have been a valid URL, however the hostname was invalid
// Attempts to convert the input to a fully qualified URL have failed
// Treat the input as a search query
return template.replace("%s", encodeURIComponent(input));
}
export { search };

View file

@ -1,54 +0,0 @@
//Combine all of the other settings into one object. And export that (along with types and other things)
import {
AppearanceSettings,
MarketPlaceExtras,
PluginSettings,
marketPlaceSettings
} from "./marketplace/index";
import { ProxySettings, proxySettings } from "./proxy";
import { TabSettings, cloak, tabSettings } from "./tab";
import {
type AbCloaks,
type OpenIn,
type Package,
type PackageType,
type Proxy,
type SearchEngine,
SearchEngines,
type TabCloaks,
type Transport,
WispServerURLS,
wispUrl
} from "./types";
const Settings = {
AppearanceSettings,
TabSettings,
ProxySettings,
MarketPlaceExtras,
PluginSettings
};
const settings = {
marketPlaceSettings,
tabSettings,
proxySettings
};
//export all of the stuffs
export {
Settings,
settings,
SearchEngines,
WispServerURLS,
wispUrl,
cloak,
type TabCloaks,
type AbCloaks,
type OpenIn,
type Proxy,
type Transport,
type PackageType,
type Package,
type SearchEngine
};

View file

@ -1,224 +0,0 @@
//marketplace code & handlers
import { Settings } from "../index";
import {
type Package,
type PackageType,
type Plugin,
type PluginType,
type SWPagePlugin,
type SWPlugin
} from "../types";
const AppearanceSettings = {
themes: "nebula||themes",
themeName: "nebula||themeName",
stylePayload: "nebula||stylepayload",
video: "nebula||video",
image: "nebula||image"
};
const PluginSettings = {
plugins: "nebula||plugins"
};
const MarketPlaceExtras = {
proxy: "nebula||marketplaceProxy",
hostname: "nebula||marketplaceHostname"
};
const marketPlaceSettings = {
install: function (p: Package, packageName: string, payload?: any) {
return new Promise<void>((resolve) => {
if (p.theme) {
let themes = localStorage.getItem(AppearanceSettings.themes) as any;
themes ? (themes = JSON.parse(themes)) : (themes = []);
if (!themes.find((theme: any) => theme === packageName)) {
themes.push(packageName);
localStorage.setItem(AppearanceSettings.themes, JSON.stringify(themes));
this.changeTheme(false, payload, p.theme.video, p.theme.bgImage, packageName);
}
resolve();
}
if (p.plugin) {
let plugins = localStorage.getItem(PluginSettings.plugins) as any;
plugins ? (plugins = JSON.parse(plugins)) : (plugins = []);
//@ts-ignore
const plugin = plugins.find(({ name }) => name === packageName) as Plugin;
if (!plugin) {
plugins.push({
name: packageName,
src: p.plugin.src,
type: p.plugin.type
} as unknown as Plugin);
localStorage.setItem(PluginSettings.plugins, JSON.stringify(plugins));
} else if (plugin && plugin.remove) {
plugin.remove = false;
localStorage.setItem(Settings.PluginSettings.plugins, JSON.stringify(plugins));
}
resolve();
}
});
},
uninstall: function (p: PackageType, packageName: string) {
console.log(p);
return new Promise<void>((resolve) => {
if (p === "theme") {
let items = localStorage.getItem(AppearanceSettings.themes) as any;
items ? (items = JSON.parse(items)) : (items = []);
if (items.find((theme: any) => theme === packageName.toLowerCase())) {
const idx = items.indexOf(packageName.toLowerCase());
items.splice(idx, 1);
localStorage.setItem(AppearanceSettings.themes, JSON.stringify(items));
this.changeTheme(true);
}
resolve();
}
if (p === "plugin-page" || p === "plugin-sw") {
let plugins = localStorage.getItem(PluginSettings.plugins) as any;
plugins ? (plugins = JSON.parse(plugins)) : (plugins = []);
//@ts-ignore
const plugin = plugins.find(({ name }) => name === packageName.toLowerCase());
if (plugin) {
plugin.remove = true;
localStorage.setItem(PluginSettings.plugins, JSON.stringify(plugins));
}
resolve();
}
});
},
handlePlugins: function (worker: never | ServiceWorkerRegistration) {
return new Promise<void>((resolve) => {
let plugins =
JSON.parse(localStorage.getItem(Settings.PluginSettings.plugins) as string) || [];
const swPagePlugins: SWPagePlugin[] = [];
const swPlugins: SWPlugin[] = [];
if (plugins.length === 0) {
console.log("Plugin length is not greater then 0. Resolving.");
return resolve();
}
plugins.forEach(async (plugin: Plugin) => {
if (plugin.type === "page") {
const pluginScript = await fetch(
`/packages/${plugin.name.toLowerCase()}/${plugin.src}`
).then((res) => res.text());
const script = eval(pluginScript);
const inject = (await script()) as unknown as SWPagePlugin;
if (plugin.remove) {
plugins = plugins.filter(
//@ts-ignore freaking types BRO
({ name }) => name !== plugin.name.toLowerCase()
);
swPagePlugins.push({
remove: true,
host: inject.host,
html: inject.html,
injectTo: inject.injectTo,
type: "page"
});
} else {
swPagePlugins.push({
host: inject.host,
html: inject.html,
injectTo: inject.injectTo,
type: "page"
});
}
//only resolve AFTER we have postMessaged to the SW.
worker.active?.postMessage(swPagePlugins);
} else if (plugin.type === "serviceWorker") {
const pluginScript = await fetch(
`/packages/${plugin.name.toLowerCase()}/${plugin.src}`
).then((res) => res.text());
const script = eval(pluginScript);
const inject = (await script()) as unknown as SWPlugin;
if (plugin.remove) {
plugins = plugins.filter(
//@ts-ignore
({ name }) => name !== plugin.name.toLowerCase()
);
swPlugins.push({
remove: true,
function: inject.function.toString(),
name: plugin.name,
events: inject.events,
type: "serviceWorker"
});
} else {
swPlugins.push({
function: inject.function.toString(),
name: plugin.name,
events: inject.events,
type: "serviceWorker"
});
}
worker.active?.postMessage(swPlugins);
}
localStorage.setItem(Settings.PluginSettings.plugins, JSON.stringify(plugins));
resolve();
});
});
},
changeTheme: async function (
reset: Boolean,
payload?: any,
videoSource?: string,
bgSource?: string,
name?: string
) {
async function resetCSS() {
const stylesheet = document.getElementById("stylesheet")! as HTMLLinkElement;
localStorage.removeItem(AppearanceSettings.stylePayload);
localStorage.removeItem(AppearanceSettings.themeName);
stylesheet.href = "/nebula.css";
}
function resetVideo() {
localStorage.removeItem(AppearanceSettings.video);
const source = document.getElementById("nebulaVideo")! as HTMLVideoElement;
source.src = "";
}
function resetBGImage() {
localStorage.removeItem(AppearanceSettings.image);
const image = document.getElementById("nebulaImage")! as HTMLImageElement;
image.style.display = "none";
image.src = "";
}
if (reset === true) {
await resetCSS();
resetBGImage();
resetVideo();
}
if (videoSource || localStorage.getItem(AppearanceSettings.video)) {
resetBGImage();
resetVideo();
const source = document.getElementById("nebulaVideo")! as HTMLVideoElement;
if (!localStorage.getItem(AppearanceSettings.video)) {
localStorage.setItem(AppearanceSettings.video, videoSource as string);
}
source.src = `/packages/${name}/${videoSource ? videoSource : localStorage.getItem(AppearanceSettings.video)}`;
}
if (bgSource || localStorage.getItem(AppearanceSettings.image)) {
resetVideo();
resetBGImage();
const image = document.getElementById("nebulaImage")! as HTMLImageElement;
if (!localStorage.getItem(AppearanceSettings.image)) {
localStorage.setItem(AppearanceSettings.image, bgSource as string);
}
image.style.display = "block";
image.src = `/packages/${name}/${bgSource ? bgSource : localStorage.getItem(AppearanceSettings.image)}`;
}
if (payload) {
const stylesheet = document.getElementById("stylesheet")! as HTMLLinkElement;
if (localStorage.getItem(AppearanceSettings.stylePayload) !== payload) {
localStorage.setItem(AppearanceSettings.stylePayload, payload);
localStorage.setItem(AppearanceSettings.themeName, name as string);
}
stylesheet.href = `/packages/${name}/${localStorage.getItem(AppearanceSettings.stylePayload)}`;
} else {
if (localStorage.getItem(AppearanceSettings.stylePayload)) {
const stylesheet = document.getElementById("stylesheet")! as HTMLLinkElement;
stylesheet.href = `/packages/${localStorage.getItem(AppearanceSettings.themeName)}/${localStorage.getItem(AppearanceSettings.stylePayload)}`;
}
}
}
};
export { AppearanceSettings, PluginSettings, MarketPlaceExtras, marketPlaceSettings };

View file

@ -1,56 +0,0 @@
//Where all of our types live. Expect to see these exported and used in other files tons.
type PluginType = "page" | "serviceWorker";
type MarketplacePluginType = "plugin-page" | "plugin-sw";
type PackageType = "theme" | MarketplacePluginType;
interface Plug {
name: string;
src: string;
type: PluginType;
remove?: boolean;
}
interface SWPagePlugin extends Omit<Plug, "name" | "src"> {
host: string;
html: string;
injectTo: "head" | "body";
}
type SWPluginFunction<T extends unknown> = (args: T) => void | unknown;
type Events =
"abortpayment" |
"activate" |
"backgroundfetchabort" |
"backgroundfetchclick" |
"backgroundfetchfail" |
"backgroundfetchsuccess" |
"canmakepayment" |
"contentdelete" |
"cookiechange" |
"fetch" |
"install" |
"message" |
"messageerror" |
"notificationclick" |
"notificationclose" |
"paymentrequest" |
"periodicsync" |
"push" |
"pushsubscriptionchange" |
"sync"
interface SWPlugin extends Omit<Plug, "src"> {
function: string | SWPluginFunction<any>;
events: Events[]
}
interface Package {
theme?: {
payload: string;
video?: string;
bgImage?: string;
};
plugin?: Plug;
}
export { type PluginType, type MarketplacePluginType, type PackageType, type Plug as Plugin, type SWPagePlugin, type SWPlugin, type Package, type SWPluginFunction }

View file

@ -1,10 +0,0 @@
import { type SWPagePlugin, type SWPlugin, type SWPluginFunction as PluginFunction } from "./types";
interface PagePlugin extends Omit<SWPagePlugin, "type"> {};
interface ServiceWorkerPlugin extends Omit<SWPlugin, "type"> {};
declare global {
function entryFunc(): PagePlugin | ServiceWorkerPlugin;
}
export { type PagePlugin, type ServiceWorkerPlugin, type PluginFunction };

View file

@ -1,29 +0,0 @@
//Proxy specific settings.
import { type OpenIn, type Proxy, type SearchEngine, type Transport } from "./types";
const ProxySettings = {
proxy: "nebula||proxy",
openIn: "nebula||open",
searchEngine: "nebula||searchEngine",
wispServerURL: "nebula||wisp",
transport: "nebula||transport"
};
const proxySettings = {
changeProxy: function (proxy: Proxy | string) {
localStorage.setItem(ProxySettings.proxy, proxy);
},
openIn: function (type: OpenIn | string) {
localStorage.setItem(ProxySettings.openIn, type);
},
setSearchEngine: function (searchEngine: SearchEngine | string) {
localStorage.setItem(ProxySettings.searchEngine, searchEngine);
},
setWispURL: function (server: string) {
localStorage.setItem(ProxySettings.wispServerURL, server);
},
setTransport: function (transport: Transport | string) {
localStorage.setItem(ProxySettings.transport, transport);
}
};
export { ProxySettings, proxySettings };

Some files were not shown because too many files have changed in this diff Show more