Time to properly archive v7
|
|
@ -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": []
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
48
.github/workflows/docker.yml
vendored
|
|
@ -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
|
|
||||||
36
.github/workflows/release.yml
vendored
|
|
@ -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
|
|
@ -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
|
|
@ -1,3 +0,0 @@
|
||||||
[submodule "workerware"]
|
|
||||||
path = workerware
|
|
||||||
url = https://github.com/mercuryworkshop/workerware
|
|
||||||
4
.vscode/extensions.json
vendored
|
|
@ -1,4 +0,0 @@
|
||||||
{
|
|
||||||
"recommendations": ["astro-build.astro-vscode"],
|
|
||||||
"unwantedRecommendations": []
|
|
||||||
}
|
|
||||||
11
.vscode/launch.json
vendored
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"version": "0.2.0",
|
|
||||||
"configurations": [
|
|
||||||
{
|
|
||||||
"command": "./node_modules/.bin/astro dev",
|
|
||||||
"name": "Development server",
|
|
||||||
"request": "launch",
|
|
||||||
"type": "node-terminal"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
19
Dockerfile
|
|
@ -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
|
|
@ -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
|
|
||||||
|
|
||||||
[](https://discord.gg/unblocker)
|
|
||||||
[](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` |
|
|
||||||
|----------------------------| ----------------------------------------------------------------------------|------------|--------------|
|
|
||||||
118
astro.config.ts
|
|
@ -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"
|
|
||||||
})
|
|
||||||
});
|
|
||||||
32
biome.json
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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)
|
|
||||||
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 9.6 KiB |
|
|
@ -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%);
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 56 KiB |
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 2.7 KiB |
|
|
@ -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");
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 50 KiB |
|
|
@ -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
|
|
||||||
|
|
@ -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
|
|
||||||
68
package.json
|
|
@ -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
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 615 B |
|
Before Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 2.7 KiB |
|
|
@ -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 |
|
|
@ -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;
|
|
||||||
}
|
|
||||||
57
public/sw.js
|
|
@ -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);
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
@ -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"
|
|
||||||
};
|
|
||||||
|
|
@ -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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
@ -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
|
|
@ -1 +0,0 @@
|
||||||
declare module "@rubynetwork/rammerhead/src/server/index.js";
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ES2020",
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"noEmit": false,
|
|
||||||
"esModuleInterop": true,
|
|
||||||
"skipLibCheck": true,
|
|
||||||
"paths": {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
|
@ -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>→</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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
||||||
|
|
@ -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 |
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
<!-- Legit just so we can use $destroy() -->
|
|
||||||
<slot />
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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
|
|
@ -1,3 +0,0 @@
|
||||||
/// <reference path="../.astro/types.d.ts" />
|
|
||||||
/// <reference types="astro/client" />
|
|
||||||
/// <reference types="@titaniumnetwork-dev/ultraviolet/client" />
|
|
||||||
|
|
@ -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"
|
|
||||||
}
|
|
||||||
|
|
@ -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"
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
};
|
|
||||||
|
|
@ -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];
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>© 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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -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>
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
import { atom } from "nanostores";
|
|
||||||
|
|
||||||
export const isMobileNavOpen = atom(false);
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
@ -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
|
|
||||||
};
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
@ -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 }
|
|
||||||
|
|
@ -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 };
|
|
||||||
|
|
@ -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 };
|
|
||||||